davidliu
Committed by GitHub

Clean up room and local participant between sessions (#196)

@@ -458,6 +458,10 @@ constructor( @@ -458,6 +458,10 @@ constructor(
458 localParticipant.cleanup() 458 localParticipant.cleanup()
459 remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications. 459 remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications.
460 .forEach { sid -> handleParticipantDisconnect(sid) } 460 .forEach { sid -> handleParticipantDisconnect(sid) }
  461 +
  462 + sid = null
  463 + metadata = null
  464 + name = null
461 } 465 }
462 466
463 private fun handleDisconnect(reason: DisconnectReason) { 467 private fun handleDisconnect(reason: DisconnectReason) {
@@ -143,15 +143,19 @@ open class Participant( @@ -143,15 +143,19 @@ open class Participant(
143 143
144 private fun Flow<Map<String, TrackPublication>>.trackUpdateFlow(): Flow<List<Pair<TrackPublication, Track?>>> { 144 private fun Flow<Map<String, TrackPublication>>.trackUpdateFlow(): Flow<List<Pair<TrackPublication, Track?>>> {
145 return flatMapLatest { videoTracks -> 145 return flatMapLatest { videoTracks ->
146 - combine(  
147 - videoTracks.values  
148 - .map { trackPublication ->  
149 - // Re-emit when track changes  
150 - trackPublication::track.flow  
151 - .map { trackPublication to trackPublication.track }  
152 - }  
153 - ) { trackPubs ->  
154 - trackPubs.toList() 146 + if (videoTracks.isEmpty()) {
  147 + flowOf(emptyList())
  148 + } else {
  149 + combine(
  150 + videoTracks.values
  151 + .map { trackPublication ->
  152 + // Re-emit when track changes
  153 + trackPublication::track.flow
  154 + .map { trackPublication to trackPublication.track }
  155 + }
  156 + ) { trackPubs ->
  157 + trackPubs.toList()
  158 + }
155 } 159 }
156 } 160 }
157 } 161 }
@@ -309,6 +313,14 @@ open class Participant( @@ -309,6 +313,14 @@ open class Participant(
309 313
310 internal open fun dispose() { 314 internal open fun dispose() {
311 scope.cancel() 315 scope.cancel()
  316 +
  317 + sid = ""
  318 + name = null
  319 + identity = null
  320 + metadata = null
  321 + participantInfo = null
  322 + permissions = null
  323 + connectionQuality = ConnectionQuality.UNKNOWN
312 } 324 }
313 } 325 }
314 326
@@ -16,6 +16,7 @@ import io.livekit.android.room.track.Track @@ -16,6 +16,7 @@ import io.livekit.android.room.track.Track
16 import io.livekit.android.util.flow 16 import io.livekit.android.util.flow
17 import io.livekit.android.util.toOkioByteString 17 import io.livekit.android.util.toOkioByteString
18 import junit.framework.Assert.assertEquals 18 import junit.framework.Assert.assertEquals
  19 +import junit.framework.Assert.assertNull
19 import kotlinx.coroutines.ExperimentalCoroutinesApi 20 import kotlinx.coroutines.ExperimentalCoroutinesApi
20 import kotlinx.coroutines.launch 21 import kotlinx.coroutines.launch
21 import org.junit.Assert 22 import org.junit.Assert
@@ -290,8 +291,9 @@ class RoomMockE2ETest : MockE2ETest() { @@ -290,8 +291,9 @@ class RoomMockE2ETest : MockE2ETest() {
290 ) 291 )
291 val events = eventCollector.stopCollecting() 292 val events = eventCollector.stopCollecting()
292 293
293 - Assert.assertEquals(1, events.size)  
294 - Assert.assertEquals(true, events[0] is RoomEvent.Disconnected) 294 + assertEquals(1, events.size)
  295 + assertEquals(true, events[0] is RoomEvent.Disconnected)
  296 +
295 } 297 }
296 298
297 @Test 299 @Test
@@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow @@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
16 import kotlinx.coroutines.flow.SharedFlow 16 import kotlinx.coroutines.flow.SharedFlow
17 import kotlinx.coroutines.test.runTest 17 import kotlinx.coroutines.test.runTest
18 import org.junit.Assert 18 import org.junit.Assert
19 -import org.junit.Assert.assertEquals 19 +import org.junit.Assert.*
20 import org.junit.Before 20 import org.junit.Before
21 import org.junit.Rule 21 import org.junit.Rule
22 import org.junit.Test 22 import org.junit.Test
@@ -128,7 +128,7 @@ class RoomTest { @@ -128,7 +128,7 @@ class RoomTest {
128 } 128 }
129 129
130 @Test 130 @Test
131 - fun onDisconnect() = runTest { 131 + fun onServerLeave() = runTest {
132 connect() 132 connect()
133 133
134 val eventCollector = EventCollector(room.events, coroutineRule.scope) 134 val eventCollector = EventCollector(room.events, coroutineRule.scope)
@@ -138,6 +138,32 @@ class RoomTest { @@ -138,6 +138,32 @@ class RoomTest {
138 assertEquals(1, events.size) 138 assertEquals(1, events.size)
139 assertEquals(true, events[0] is RoomEvent.Disconnected) 139 assertEquals(true, events[0] is RoomEvent.Disconnected)
140 assertEquals(DisconnectReason.SERVER_SHUTDOWN, (events[0] as RoomEvent.Disconnected).reason) 140 assertEquals(DisconnectReason.SERVER_SHUTDOWN, (events[0] as RoomEvent.Disconnected).reason)
  141 +
  142 + // Verify Room state
  143 + assertEquals(Room.State.DISCONNECTED, room.state)
  144 + assertNull(room.sid)
  145 + assertNull(room.metadata)
  146 + assertNull(room.name)
  147 + }
  148 +
  149 + @Test
  150 + fun onDisconnect() = runTest {
  151 +
  152 + connect()
  153 + val eventCollector = EventCollector(room.events, coroutineRule.scope)
  154 + room.disconnect()
  155 + val events = eventCollector.stopCollecting()
  156 +
  157 + assertEquals(1, events.size)
  158 + assertEquals(true, events[0] is RoomEvent.Disconnected)
  159 + assertEquals(DisconnectReason.CLIENT_INITIATED, (events[0] as RoomEvent.Disconnected).reason)
  160 +
  161 + // Verify Room state
  162 + assertEquals(Room.State.DISCONNECTED, room.state)
  163 + assertNull(room.sid)
  164 + assertNull(room.metadata)
  165 + assertNull(room.name)
  166 +
141 } 167 }
142 168
143 @Test 169 @Test
  1 +package io.livekit.android.room.participant
  2 +
  3 +import io.livekit.android.MockE2ETest
  4 +import io.livekit.android.mock.MockAudioStreamTrack
  5 +import io.livekit.android.room.SignalClientTest
  6 +import io.livekit.android.room.track.LocalAudioTrack
  7 +import io.livekit.android.util.toOkioByteString
  8 +import kotlinx.coroutines.ExperimentalCoroutinesApi
  9 +import kotlinx.coroutines.launch
  10 +import kotlinx.coroutines.test.advanceUntilIdle
  11 +import kotlinx.coroutines.test.runCurrent
  12 +import org.junit.Assert.*
  13 +import org.junit.Test
  14 +import org.junit.runner.RunWith
  15 +import org.robolectric.RobolectricTestRunner
  16 +
  17 +@ExperimentalCoroutinesApi
  18 +@RunWith(RobolectricTestRunner::class)
  19 +class LocalParticipantMockE2ETest : MockE2ETest() {
  20 +
  21 + @Test
  22 + fun disconnectCleansLocalParticipant() = runTest {
  23 +
  24 + connect()
  25 +
  26 + val publishJob = launch {
  27 + room.localParticipant.publishAudioTrack(
  28 + LocalAudioTrack(
  29 + "",
  30 + MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid)
  31 + )
  32 + )
  33 + }
  34 + wsFactory.listener.onMessage(wsFactory.ws, SignalClientTest.LOCAL_TRACK_PUBLISHED.toOkioByteString())
  35 + publishJob.join()
  36 +
  37 + room.disconnect()
  38 +
  39 + assertEquals("", room.localParticipant.sid)
  40 + assertNull(room.localParticipant.name)
  41 + assertNull(room.localParticipant.identity)
  42 + assertNull(room.localParticipant.metadata)
  43 + assertNull(room.localParticipant.permissions)
  44 + assertNull(room.localParticipant.participantInfo)
  45 + assertFalse(room.localParticipant.isSpeaking)
  46 + assertEquals(ConnectionQuality.UNKNOWN, room.localParticipant.connectionQuality)
  47 +
  48 + assertEquals(0, room.localParticipant.tracks.values.size)
  49 + assertEquals(0, room.localParticipant.audioTracks.size)
  50 + assertEquals(0, room.localParticipant.videoTracks.size)
  51 + }
  52 +}
@@ -7,6 +7,7 @@ import io.livekit.android.room.track.TrackPublication @@ -7,6 +7,7 @@ import io.livekit.android.room.track.TrackPublication
7 import kotlinx.coroutines.ExperimentalCoroutinesApi 7 import kotlinx.coroutines.ExperimentalCoroutinesApi
8 import kotlinx.coroutines.test.runTest 8 import kotlinx.coroutines.test.runTest
9 import livekit.LivekitModels 9 import livekit.LivekitModels
  10 +import org.junit.Assert
10 import org.junit.Assert.assertEquals 11 import org.junit.Assert.assertEquals
11 import org.junit.Assert.assertTrue 12 import org.junit.Assert.assertTrue
12 import org.junit.Before 13 import org.junit.Before
@@ -117,6 +118,22 @@ class ParticipantTest { @@ -117,6 +118,22 @@ class ParticipantTest {
117 assertEquals(audioPublication, participant.audioTracks.first().first) 118 assertEquals(audioPublication, participant.audioTracks.first().first)
118 } 119 }
119 120
  121 + @Test
  122 + fun dispose() = runTest {
  123 + val audioPublication = TrackPublication(TRACK_INFO, null, participant)
  124 + participant.addTrackPublication(audioPublication)
  125 +
  126 + participant.dispose()
  127 + assertEquals("", participant.sid)
  128 + Assert.assertNull(participant.name)
  129 + Assert.assertNull(participant.identity)
  130 + Assert.assertNull(participant.metadata)
  131 + Assert.assertNull(participant.permissions)
  132 + Assert.assertNull(participant.participantInfo)
  133 + Assert.assertFalse(participant.isSpeaking)
  134 + assertEquals(ConnectionQuality.UNKNOWN, participant.connectionQuality)
  135 + }
  136 +
120 companion object { 137 companion object {
121 val INFO = LivekitModels.ParticipantInfo.newBuilder() 138 val INFO = LivekitModels.ParticipantInfo.newBuilder()
122 .setSid("sid") 139 .setSid("sid")