davidliu
Committed by GitHub

handle StreamStateUpdate (#29)

* Update protocol submodule commit

* StreamStateUpdate

* default track streamstate paused

* fix tests
@@ -7,13 +7,17 @@ import kotlinx.coroutines.flow.asSharedFlow @@ -7,13 +7,17 @@ import kotlinx.coroutines.flow.asSharedFlow
7 import kotlinx.coroutines.launch 7 import kotlinx.coroutines.launch
8 8
9 class BroadcastEventBus<T> : EventListenable<T> { 9 class BroadcastEventBus<T> : EventListenable<T> {
10 - private val mutableEvents = MutableSharedFlow<T>() 10 + private val mutableEvents = MutableSharedFlow<T>(extraBufferCapacity = Int.MAX_VALUE)
11 override val events = mutableEvents.asSharedFlow() 11 override val events = mutableEvents.asSharedFlow()
12 12
13 suspend fun postEvent(event: T) { 13 suspend fun postEvent(event: T) {
14 mutableEvents.emit(event) 14 mutableEvents.emit(event)
15 } 15 }
16 16
  17 + fun tryPostEvent(event: T) {
  18 + mutableEvents.tryEmit(event)
  19 + }
  20 +
17 fun postEvent(event: T, scope: CoroutineScope): Job { 21 fun postEvent(event: T, scope: CoroutineScope): Job {
18 return scope.launch { postEvent(event) } 22 return scope.launch { postEvent(event) }
19 } 23 }
@@ -98,4 +98,13 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() { @@ -98,4 +98,13 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
98 * Received data published by another participant 98 * Received data published by another participant
99 */ 99 */
100 class DataReceived(override val participant: RemoteParticipant, val data: ByteArray) : ParticipantEvent(participant) 100 class DataReceived(override val participant: RemoteParticipant, val data: ByteArray) : ParticipantEvent(participant)
  101 +
  102 + /**
  103 + * A track's stream state has changed.
  104 + */
  105 + class TrackStreamStateChanged(
  106 + override val participant: Participant,
  107 + val trackPublication: TrackPublication,
  108 + val streamState: Track.StreamState
  109 + ) : ParticipantEvent(participant)
101 } 110 }
@@ -122,6 +122,15 @@ sealed class RoomEvent(val room: Room) : Event() { @@ -122,6 +122,15 @@ sealed class RoomEvent(val room: Room) : Event() {
122 ) : RoomEvent(room) 122 ) : RoomEvent(room)
123 123
124 /** 124 /**
  125 + * A track's stream state has changed.
  126 + */
  127 + class TrackStreamStateChanged(
  128 + room: Room,
  129 + val trackPublication: TrackPublication,
  130 + val streamState: Track.StreamState
  131 + ) : RoomEvent(room)
  132 +
  133 + /**
125 * Received data published by another participant 134 * Received data published by another participant
126 */ 135 */
127 class DataReceived(room: Room, val data: ByteArray, val participant: RemoteParticipant) : RoomEvent(room) 136 class DataReceived(room: Room, val data: ByteArray, val participant: RemoteParticipant) : RoomEvent(room)
@@ -2,7 +2,8 @@ package io.livekit.android.events @@ -2,7 +2,8 @@ package io.livekit.android.events
2 2
3 import io.livekit.android.room.track.Track 3 import io.livekit.android.room.track.Track
4 4
5 -sealed class TrackEvent : Event() {  
6 - class VisibilityChanged(val isVisible: Boolean) : TrackEvent()  
7 - class VideoDimensionsChanged(val newDimensions: Track.Dimensions) : TrackEvent() 5 +sealed class TrackEvent(val track: Track) : Event() {
  6 + class VisibilityChanged(track: Track, val isVisible: Boolean) : TrackEvent(track)
  7 + class VideoDimensionsChanged(track: Track, val newDimensions: Track.Dimensions) : TrackEvent(track)
  8 + class StreamStateChanged(track: Track, val streamState: Track.StreamState) : TrackEvent(track)
8 } 9 }
@@ -387,6 +387,7 @@ internal constructor( @@ -387,6 +387,7 @@ internal constructor(
387 fun onDisconnect(reason: String) 387 fun onDisconnect(reason: String)
388 fun onFailToConnect(error: Exception) 388 fun onFailToConnect(error: Exception)
389 fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind) 389 fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind)
  390 + fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
390 } 391 }
391 392
392 companion object { 393 companion object {
@@ -524,6 +525,10 @@ internal constructor( @@ -524,6 +525,10 @@ internal constructor(
524 listener?.onFailToConnect(error) 525 listener?.onFailToConnect(error)
525 } 526 }
526 527
  528 + override fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) {
  529 + listener?.onStreamStateUpdate(streamStates)
  530 + }
  531 +
527 //--------------------------------- DataChannel.Observer ------------------------------------// 532 //--------------------------------- DataChannel.Observer ------------------------------------//
528 533
529 override fun onBufferedAmountChange(previousAmount: Long) { 534 override fun onBufferedAmountChange(previousAmount: Long) {
@@ -12,7 +12,9 @@ import io.livekit.android.ConnectOptions @@ -12,7 +12,9 @@ import io.livekit.android.ConnectOptions
12 import io.livekit.android.Version 12 import io.livekit.android.Version
13 import io.livekit.android.dagger.InjectionNames 13 import io.livekit.android.dagger.InjectionNames
14 import io.livekit.android.events.BroadcastEventBus 14 import io.livekit.android.events.BroadcastEventBus
  15 +import io.livekit.android.events.ParticipantEvent
15 import io.livekit.android.events.RoomEvent 16 import io.livekit.android.events.RoomEvent
  17 +import io.livekit.android.events.collect
16 import io.livekit.android.renderer.TextureViewRenderer 18 import io.livekit.android.renderer.TextureViewRenderer
17 import io.livekit.android.room.participant.* 19 import io.livekit.android.room.participant.*
18 import io.livekit.android.room.track.* 20 import io.livekit.android.room.track.*
@@ -204,6 +206,14 @@ constructor( @@ -204,6 +206,14 @@ constructor(
204 } 206 }
205 participant.internalListener = this 207 participant.internalListener = this
206 208
  209 + coroutineScope.launch {
  210 + participant.events.collect {
  211 + when(it){
  212 + is ParticipantEvent.TrackStreamStateChanged -> eventBus.postEvent(RoomEvent.TrackStreamStateChanged(this@Room, it.trackPublication, it.streamState))
  213 + }
  214 + }
  215 + }
  216 +
207 val newRemoteParticipants = mutableRemoteParticipants.toMutableMap() 217 val newRemoteParticipants = mutableRemoteParticipants.toMutableMap()
208 newRemoteParticipants[sid] = participant 218 newRemoteParticipants[sid] = participant
209 mutableRemoteParticipants = newRemoteParticipants 219 mutableRemoteParticipants = newRemoteParticipants
@@ -435,6 +445,15 @@ constructor( @@ -435,6 +445,15 @@ constructor(
435 participant.onDataReceived(data) 445 participant.onDataReceived(data)
436 } 446 }
437 447
  448 + override fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) {
  449 + for(streamState in streamStates){
  450 + val participant = getParticipant(streamState.participantSid) ?: continue
  451 + val track = participant.tracks[streamState.trackSid] ?: continue
  452 +
  453 + track.track?.streamState = Track.StreamState.fromProto(streamState.state)
  454 + }
  455 + }
  456 +
438 /** 457 /**
439 * @suppress 458 * @suppress
440 */ 459 */
@@ -292,7 +292,7 @@ constructor( @@ -292,7 +292,7 @@ constructor(
292 sid: String, 292 sid: String,
293 disabled: Boolean, 293 disabled: Boolean,
294 videoDimensions: Track.Dimensions?, 294 videoDimensions: Track.Dimensions?,
295 - videoQuality: LivekitRtc.VideoQuality?, 295 + videoQuality: LivekitModels.VideoQuality?,
296 ) { 296 ) {
297 val trackSettings = LivekitRtc.UpdateTrackSettings.newBuilder() 297 val trackSettings = LivekitRtc.UpdateTrackSettings.newBuilder()
298 .addTrackSids(sid) 298 .addTrackSids(sid)
@@ -305,7 +305,7 @@ constructor( @@ -305,7 +305,7 @@ constructor(
305 quality = videoQuality 305 quality = videoQuality
306 } else { 306 } else {
307 // default to HIGH 307 // default to HIGH
308 - quality = LivekitRtc.VideoQuality.HIGH 308 + quality = LivekitModels.VideoQuality.HIGH
309 } 309 }
310 } 310 }
311 311
@@ -415,8 +415,8 @@ constructor( @@ -415,8 +415,8 @@ constructor(
415 LivekitRtc.SignalResponse.MessageCase.CONNECTION_QUALITY -> { 415 LivekitRtc.SignalResponse.MessageCase.CONNECTION_QUALITY -> {
416 listener?.onConnectionQuality(response.connectionQuality.updatesList) 416 listener?.onConnectionQuality(response.connectionQuality.updatesList)
417 } 417 }
418 - LivekitRtc.SignalResponse.MessageCase.STREAMED_TRACKS_UPDATE -> {  
419 - // TODO 418 + LivekitRtc.SignalResponse.MessageCase.STREAM_STATE_UPDATE -> {
  419 + listener?.onStreamStateUpdate(response.streamStateUpdate.streamStatesList)
420 } 420 }
421 LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET, 421 LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET,
422 null -> { 422 null -> {
@@ -444,6 +444,7 @@ constructor( @@ -444,6 +444,7 @@ constructor(
444 fun onConnectionQuality(updates: List<LivekitRtc.ConnectionQualityInfo>) 444 fun onConnectionQuality(updates: List<LivekitRtc.ConnectionQualityInfo>)
445 fun onLeave() 445 fun onLeave()
446 fun onError(error: Exception) 446 fun onError(error: Exception)
  447 + fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
447 } 448 }
448 449
449 companion object { 450 companion object {
@@ -3,6 +3,7 @@ package io.livekit.android.room.participant @@ -3,6 +3,7 @@ package io.livekit.android.room.participant
3 import io.livekit.android.dagger.InjectionNames 3 import io.livekit.android.dagger.InjectionNames
4 import io.livekit.android.events.BroadcastEventBus 4 import io.livekit.android.events.BroadcastEventBus
5 import io.livekit.android.events.ParticipantEvent 5 import io.livekit.android.events.ParticipantEvent
  6 +import io.livekit.android.events.TrackEvent
6 import io.livekit.android.room.track.LocalTrackPublication 7 import io.livekit.android.room.track.LocalTrackPublication
7 import io.livekit.android.room.track.RemoteTrackPublication 8 import io.livekit.android.room.track.RemoteTrackPublication
8 import io.livekit.android.room.track.Track 9 import io.livekit.android.room.track.Track
@@ -234,6 +235,11 @@ open class Participant( @@ -234,6 +235,11 @@ open class Participant(
234 eventBus.postEvent(ParticipantEvent.TrackUnmuted(this, trackPublication), scope) 235 eventBus.postEvent(ParticipantEvent.TrackUnmuted(this, trackPublication), scope)
235 } 236 }
236 237
  238 + internal fun onTrackStreamStateChanged(trackEvent: TrackEvent.StreamStateChanged) {
  239 + val trackPublication = tracks[trackEvent.track.sid] ?: return
  240 + eventBus.postEvent(ParticipantEvent.TrackStreamStateChanged(this, trackPublication, trackEvent.streamState), scope)
  241 + }
  242 +
237 } 243 }
238 244
239 @Deprecated("Use Participant.events instead.") 245 @Deprecated("Use Participant.events instead.")
@@ -8,7 +8,6 @@ import io.livekit.android.util.debounce @@ -8,7 +8,6 @@ import io.livekit.android.util.debounce
8 import io.livekit.android.util.invoke 8 import io.livekit.android.util.invoke
9 import kotlinx.coroutines.* 9 import kotlinx.coroutines.*
10 import livekit.LivekitModels 10 import livekit.LivekitModels
11 -import livekit.LivekitRtc  
12 import javax.inject.Named 11 import javax.inject.Named
13 12
14 class RemoteTrackPublication( 13 class RemoteTrackPublication(
@@ -36,6 +35,7 @@ class RemoteTrackPublication( @@ -36,6 +35,7 @@ class RemoteTrackPublication(
36 when (it) { 35 when (it) {
37 is TrackEvent.VisibilityChanged -> handleVisibilityChanged(it) 36 is TrackEvent.VisibilityChanged -> handleVisibilityChanged(it)
38 is TrackEvent.VideoDimensionsChanged -> handleVideoDimensionsChanged(it) 37 is TrackEvent.VideoDimensionsChanged -> handleVideoDimensionsChanged(it)
  38 + is TrackEvent.StreamStateChanged -> handleStreamStateChanged(it)
39 } 39 }
40 } 40 }
41 } 41 }
@@ -52,11 +52,15 @@ class RemoteTrackPublication( @@ -52,11 +52,15 @@ class RemoteTrackPublication(
52 sendUpdateTrackSettings.invoke() 52 sendUpdateTrackSettings.invoke()
53 } 53 }
54 54
  55 + private fun handleStreamStateChanged(trackEvent: TrackEvent.StreamStateChanged) {
  56 + participant.get()?.onTrackStreamStateChanged(trackEvent)
  57 + }
  58 +
55 private var trackJob: Job? = null 59 private var trackJob: Job? = null
56 60
57 private var unsubscribed: Boolean = false 61 private var unsubscribed: Boolean = false
58 private var disabled: Boolean = false 62 private var disabled: Boolean = false
59 - private var videoQuality: LivekitRtc.VideoQuality? = LivekitRtc.VideoQuality.HIGH 63 + private var videoQuality: LivekitModels.VideoQuality? = LivekitModels.VideoQuality.HIGH
60 private var videoDimensions: Track.Dimensions? = null 64 private var videoDimensions: Track.Dimensions? = null
61 65
62 val isAutoManaged: Boolean 66 val isAutoManaged: Boolean
@@ -113,7 +117,7 @@ class RemoteTrackPublication( @@ -113,7 +117,7 @@ class RemoteTrackPublication(
113 * this indicates the highest quality the client can accept. if network bandwidth does not 117 * this indicates the highest quality the client can accept. if network bandwidth does not
114 * allow, server will automatically reduce quality to optimize for uninterrupted video 118 * allow, server will automatically reduce quality to optimize for uninterrupted video
115 */ 119 */
116 - fun setVideoQuality(quality: LivekitRtc.VideoQuality) { 120 + fun setVideoQuality(quality: LivekitModels.VideoQuality) {
117 if (isAutoManaged 121 if (isAutoManaged
118 || !subscribed 122 || !subscribed
119 || quality == videoQuality 123 || quality == videoQuality
@@ -83,11 +83,11 @@ class RemoteVideoTrack( @@ -83,11 +83,11 @@ class RemoteVideoTrack(
83 val eventsToPost = mutableListOf<TrackEvent>() 83 val eventsToPost = mutableListOf<TrackEvent>()
84 if (isVisible != lastVisibility) { 84 if (isVisible != lastVisibility) {
85 lastVisibility = isVisible 85 lastVisibility = isVisible
86 - eventsToPost.add(TrackEvent.VisibilityChanged(isVisible)) 86 + eventsToPost.add(TrackEvent.VisibilityChanged(this, isVisible))
87 } 87 }
88 if (newDimensions != lastDimensions) { 88 if (newDimensions != lastDimensions) {
89 lastDimensions = newDimensions 89 lastDimensions = newDimensions
90 - eventsToPost.add(TrackEvent.VideoDimensionsChanged(newDimensions)) 90 + eventsToPost.add(TrackEvent.VideoDimensionsChanged(this, newDimensions))
91 } 91 }
92 92
93 if (eventsToPost.any()) { 93 if (eventsToPost.any()) {
@@ -2,7 +2,9 @@ package io.livekit.android.room.track @@ -2,7 +2,9 @@ package io.livekit.android.room.track
2 2
3 import io.livekit.android.events.BroadcastEventBus 3 import io.livekit.android.events.BroadcastEventBus
4 import io.livekit.android.events.TrackEvent 4 import io.livekit.android.events.TrackEvent
  5 +import io.livekit.android.util.flowDelegate
5 import livekit.LivekitModels 6 import livekit.LivekitModels
  7 +import livekit.LivekitRtc
6 import org.webrtc.MediaStreamTrack 8 import org.webrtc.MediaStreamTrack
7 9
8 open class Track( 10 open class Track(
@@ -12,17 +14,24 @@ open class Track( @@ -12,17 +14,24 @@ open class Track(
12 ) { 14 ) {
13 protected val eventBus = BroadcastEventBus<TrackEvent>() 15 protected val eventBus = BroadcastEventBus<TrackEvent>()
14 val events = eventBus.readOnly() 16 val events = eventBus.readOnly()
15 -  
16 var name = name 17 var name = name
17 internal set 18 internal set
  19 +
18 var kind = kind 20 var kind = kind
19 internal set 21 internal set
20 var sid: String? = null 22 var sid: String? = null
21 internal set 23 internal set
  24 + var streamState: StreamState by flowDelegate(StreamState.PAUSED) { newValue, oldValue ->
  25 + if (newValue != oldValue) {
  26 + eventBus.tryPostEvent(TrackEvent.StreamStateChanged(this, newValue))
  27 + }
  28 + }
  29 + internal set
22 30
23 enum class Kind(val value: String) { 31 enum class Kind(val value: String) {
24 AUDIO("audio"), 32 AUDIO("audio"),
25 VIDEO("video"), 33 VIDEO("video"),
  34 +
26 // unknown 35 // unknown
27 UNRECOGNIZED("unrecognized"); 36 UNRECOGNIZED("unrecognized");
28 37
@@ -77,6 +86,30 @@ open class Track( @@ -77,6 +86,30 @@ open class Track(
77 } 86 }
78 } 87 }
79 88
  89 + enum class StreamState {
  90 + ACTIVE,
  91 + PAUSED,
  92 + UNKNOWN;
  93 +
  94 + fun toProto(): LivekitRtc.StreamState {
  95 + return when (this) {
  96 + ACTIVE -> LivekitRtc.StreamState.ACTIVE
  97 + PAUSED -> LivekitRtc.StreamState.PAUSED
  98 + UNKNOWN -> LivekitRtc.StreamState.UNRECOGNIZED
  99 + }
  100 + }
  101 +
  102 + companion object {
  103 + fun fromProto(state: LivekitRtc.StreamState): StreamState {
  104 + return when (state) {
  105 + LivekitRtc.StreamState.ACTIVE -> ACTIVE
  106 + LivekitRtc.StreamState.PAUSED -> PAUSED
  107 + LivekitRtc.StreamState.UNRECOGNIZED -> UNKNOWN
  108 + }
  109 + }
  110 + }
  111 + }
  112 +
80 data class Dimensions(val width: Int, val height: Int) 113 data class Dimensions(val width: Int, val height: Int)
81 114
82 open fun start() { 115 open fun start() {
  1 +package io.livekit.android.mock
  2 +
  3 +import org.webrtc.AudioTrack
  4 +
  5 +class MockAudioStreamTrack(
  6 + val id: String = "id",
  7 + val kind: String = AUDIO_TRACK_KIND,
  8 + var enabled: Boolean = true,
  9 + var state: State = State.LIVE,
  10 +) : AudioTrack(1L) {
  11 + override fun id(): String = id
  12 +
  13 + override fun kind(): String = kind
  14 +
  15 + override fun enabled(): Boolean = enabled
  16 +
  17 + override fun setEnabled(enable: Boolean): Boolean {
  18 + enabled = enable
  19 + return true
  20 + }
  21 +
  22 + override fun state(): State {
  23 + return state
  24 + }
  25 +
  26 + override fun dispose() {
  27 + }
  28 +
  29 + override fun setVolume(volume: Double) {
  30 + }
  31 +}
  1 +package io.livekit.android.mock
  2 +
  3 +import org.webrtc.AudioTrack
  4 +import org.webrtc.MediaStream
  5 +import org.webrtc.VideoTrack
  6 +
  7 +class MockMediaStream(private val id: String = "id") : MediaStream(1L) {
  8 +
  9 + override fun addTrack(track: AudioTrack): Boolean {
  10 + return audioTracks.add(track)
  11 + }
  12 +
  13 + override fun addTrack(track: VideoTrack?): Boolean {
  14 + return videoTracks.add(track)
  15 + }
  16 +
  17 + override fun addPreservedTrack(track: VideoTrack?): Boolean {
  18 + return preservedVideoTracks.add(track)
  19 + }
  20 +
  21 + override fun removeTrack(track: AudioTrack?): Boolean {
  22 + return audioTracks.remove(track)
  23 + }
  24 +
  25 + override fun removeTrack(track: VideoTrack?): Boolean {
  26 + return videoTracks.remove(track)
  27 + }
  28 +
  29 + override fun dispose() {
  30 + // Don't do anything in this stubbed class
  31 + }
  32 +
  33 + override fun getId(): String = id
  34 +}
  1 +package io.livekit.android.mock
  2 +
  3 +import org.webrtc.MediaStreamTrack
  4 +
  5 +class MockMediaStreamTrack(
  6 + val id: String = "id",
  7 + val kind: String = AUDIO_TRACK_KIND,
  8 + var enabled: Boolean = true,
  9 + var state: State = State.LIVE,
  10 +) : MediaStreamTrack(1L) {
  11 + override fun id(): String = id
  12 +
  13 + override fun kind(): String = kind
  14 +
  15 + override fun enabled(): Boolean = enabled
  16 +
  17 + override fun setEnabled(enable: Boolean): Boolean {
  18 + enabled = enable
  19 + return true
  20 + }
  21 +
  22 + override fun state(): State {
  23 + return state
  24 + }
  25 +
  26 + override fun dispose() {
  27 + }
  28 +}
@@ -5,13 +5,15 @@ import androidx.test.core.app.ApplicationProvider @@ -5,13 +5,15 @@ import androidx.test.core.app.ApplicationProvider
5 import io.livekit.android.coroutines.TestCoroutineRule 5 import io.livekit.android.coroutines.TestCoroutineRule
6 import io.livekit.android.events.EventCollector 6 import io.livekit.android.events.EventCollector
7 import io.livekit.android.events.RoomEvent 7 import io.livekit.android.events.RoomEvent
8 -import io.livekit.android.mock.MockWebsocketFactory 8 +import io.livekit.android.mock.*
9 import io.livekit.android.mock.dagger.DaggerTestLiveKitComponent 9 import io.livekit.android.mock.dagger.DaggerTestLiveKitComponent
10 import io.livekit.android.room.participant.ConnectionQuality 10 import io.livekit.android.room.participant.ConnectionQuality
  11 +import io.livekit.android.room.track.Track
11 import io.livekit.android.util.toOkioByteString 12 import io.livekit.android.util.toOkioByteString
12 import kotlinx.coroutines.ExperimentalCoroutinesApi 13 import kotlinx.coroutines.ExperimentalCoroutinesApi
13 import kotlinx.coroutines.launch 14 import kotlinx.coroutines.launch
14 import kotlinx.coroutines.test.runBlockingTest 15 import kotlinx.coroutines.test.runBlockingTest
  16 +import livekit.LivekitRtc
15 import org.junit.Assert 17 import org.junit.Assert
16 import org.junit.Before 18 import org.junit.Before
17 import org.junit.Rule 19 import org.junit.Rule
@@ -166,6 +168,35 @@ class RoomMockE2ETest { @@ -166,6 +168,35 @@ class RoomMockE2ETest {
166 } 168 }
167 169
168 @Test 170 @Test
  171 + fun trackStreamStateChanged() {
  172 + connect()
  173 +
  174 + wsFactory.listener.onMessage(
  175 + wsFactory.ws,
  176 + SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
  177 + )
  178 +
  179 + // We intentionally don't emit if the track isn't subscribed, so need to
  180 + // add track.
  181 + room.onAddTrack(
  182 + MockAudioStreamTrack(),
  183 + arrayOf(MockMediaStream(id = "${TestData.REMOTE_PARTICIPANT.sid}|${TestData.REMOTE_AUDIO_TRACK.sid}"))
  184 + )
  185 + val eventCollector = EventCollector(room.events, coroutineRule.scope)
  186 + wsFactory.listener.onMessage(
  187 + wsFactory.ws,
  188 + SignalClientTest.STREAM_STATE_UPDATE.toOkioByteString()
  189 + )
  190 + val events = eventCollector.stopCollecting()
  191 +
  192 + Assert.assertEquals(1, events.size)
  193 + Assert.assertEquals(true, events[0] is RoomEvent.TrackStreamStateChanged)
  194 +
  195 + val event = events[0] as RoomEvent.TrackStreamStateChanged
  196 + Assert.assertEquals(Track.StreamState.ACTIVE, event.streamState)
  197 + }
  198 +
  199 + @Test
169 fun leave() { 200 fun leave() {
170 connect() 201 connect()
171 val eventCollector = EventCollector(room.events, coroutineRule.scope) 202 val eventCollector = EventCollector(room.events, coroutineRule.scope)
@@ -229,6 +229,18 @@ class SignalClientTest { @@ -229,6 +229,18 @@ class SignalClientTest {
229 build() 229 build()
230 } 230 }
231 231
  232 + val STREAM_STATE_UPDATE = with(LivekitRtc.SignalResponse.newBuilder()) {
  233 + streamStateUpdate = with(LivekitRtc.StreamStateUpdate.newBuilder()) {
  234 + addStreamStates(with(LivekitRtc.StreamStateInfo.newBuilder()) {
  235 + participantSid = TestData.REMOTE_PARTICIPANT.sid
  236 + trackSid = TestData.REMOTE_AUDIO_TRACK.sid
  237 + state = LivekitRtc.StreamState.ACTIVE
  238 + build()
  239 + })
  240 + build()
  241 + }
  242 + build()
  243 + }
232 val LEAVE = with(LivekitRtc.SignalResponse.newBuilder()) { 244 val LEAVE = with(LivekitRtc.SignalResponse.newBuilder()) {
233 leave = with(leaveBuilder) { 245 leave = with(leaveBuilder) {
234 build() 246 build()
1 -Subproject commit 8de3224f9217f50b781e3a7f2b0e635711217fde 1 +Subproject commit 8785fbf5c143612bf002dbbf6ca74db4e22f2f77