David Liu

participant events

  1 +package io.livekit.android.events
  2 +
  3 +import io.livekit.android.room.participant.LocalParticipant
  4 +import io.livekit.android.room.participant.Participant
  5 +import io.livekit.android.room.participant.RemoteParticipant
  6 +import io.livekit.android.room.track.LocalTrackPublication
  7 +import io.livekit.android.room.track.RemoteTrackPublication
  8 +import io.livekit.android.room.track.Track
  9 +import io.livekit.android.room.track.TrackPublication
  10 +
  11 +sealed class ParticipantEvent(open val participant: Participant) : Event() {
  12 + // all participants
  13 + /**
  14 + * When a participant's metadata is updated, fired for all participants
  15 + */
  16 + class MetadataChanged(participant: Participant, val prevMetadata: String?) : ParticipantEvent(participant)
  17 +
  18 + /**
  19 + * Fired when the current participant's isSpeaking property changes. (including LocalParticipant)
  20 + */
  21 + class SpeakingChanged(participant: Participant, val isSpeaking: Boolean) : ParticipantEvent(participant)
  22 +
  23 + /**
  24 + * The participant was muted.
  25 + *
  26 + * For the local participant, the callback will be called if setMute was called on the
  27 + * [LocalTrackPublication], or if the server has requested the participant to be muted
  28 + */
  29 + class TrackMuted(participant: Participant, val publication: TrackPublication) : ParticipantEvent(participant)
  30 +
  31 + /**
  32 + * The participant was unmuted.
  33 + *
  34 + * For the local participant, the callback will be called if setMute was called on the
  35 + * [LocalTrackPublication], or if the server has requested the participant to be muted
  36 + */
  37 + class TrackUnmuted(participant: Participant, val publication: TrackPublication) : ParticipantEvent(participant)
  38 +
  39 +
  40 + // local participants
  41 + /**
  42 + * When a new track is published by the local participant.
  43 + */
  44 + class LocalTrackPublished(override val participant: LocalParticipant, val publication: LocalTrackPublication) :
  45 + ParticipantEvent(participant)
  46 +
  47 + /**
  48 + * A [LocalParticipant] has unpublished a track
  49 + */
  50 + class LocalTrackUnpublished(override val participant: LocalParticipant, val publication: LocalTrackPublication) :
  51 + ParticipantEvent(participant)
  52 +
  53 + // remote participants
  54 + /**
  55 + * When a new track is published to room after the local participant has joined.
  56 + *
  57 + * It will not fire for tracks that are already published
  58 + */
  59 + class TrackPublished(override val participant: RemoteParticipant, val publication: RemoteTrackPublication) :
  60 + ParticipantEvent(participant)
  61 +
  62 + /**
  63 + * A [RemoteParticipant] has unpublished a track
  64 + */
  65 + class TrackUnpublished(override val participant: RemoteParticipant, val publication: RemoteTrackPublication) :
  66 + ParticipantEvent(participant)
  67 +
  68 + /**
  69 + * Subscribed to a new track
  70 + */
  71 + class TrackSubscribed(
  72 + override val participant: RemoteParticipant,
  73 + val track: Track,
  74 + val publication: RemoteTrackPublication,
  75 + ) :
  76 + ParticipantEvent(participant)
  77 +
  78 + /**
  79 + * Error had occurred while subscribing to a track
  80 + */
  81 + class TrackSubscriptionFailed(
  82 + override val participant: RemoteParticipant,
  83 + val sid: String,
  84 + val exception: Exception,
  85 + ) : ParticipantEvent(participant)
  86 +
  87 + /**
  88 + * A subscribed track is no longer available.
  89 + * Clients should listen to this event and handle cleanup
  90 + */
  91 + class TrackUnsubscribed(
  92 + override val participant: RemoteParticipant,
  93 + val track: Track,
  94 + val publication: RemoteTrackPublication
  95 + ) : ParticipantEvent(participant)
  96 +
  97 + /**
  98 + * Received data published by another participant
  99 + */
  100 + class DataReceived(override val participant: RemoteParticipant, val data: ByteArray) : ParticipantEvent(participant)
  101 +}
@@ -9,45 +9,45 @@ import io.livekit.android.room.track.LocalTrackPublication @@ -9,45 +9,45 @@ import io.livekit.android.room.track.LocalTrackPublication
9 import io.livekit.android.room.track.Track 9 import io.livekit.android.room.track.Track
10 import io.livekit.android.room.track.TrackPublication 10 import io.livekit.android.room.track.TrackPublication
11 11
12 -sealed class RoomEvent : Event() { 12 +sealed class RoomEvent(val room: Room) : Event() {
13 /** 13 /**
14 * A network change has been detected and LiveKit attempts to reconnect to the room 14 * A network change has been detected and LiveKit attempts to reconnect to the room
15 * When reconnect attempts succeed, the room state will be kept, including tracks that are subscribed/published 15 * When reconnect attempts succeed, the room state will be kept, including tracks that are subscribed/published
16 */ 16 */
17 - class Reconnecting(val room: Room): RoomEvent() 17 + class Reconnecting(room: Room) : RoomEvent(room)
18 18
19 /** 19 /**
20 * The reconnect attempt had been successful 20 * The reconnect attempt had been successful
21 */ 21 */
22 - class Reconnected(val room: Room): RoomEvent() 22 + class Reconnected(room: Room) : RoomEvent(room)
23 23
24 /** 24 /**
25 * Disconnected from room 25 * Disconnected from room
26 */ 26 */
27 - class Disconnected(val room: Room, val error: Exception?): RoomEvent() 27 + class Disconnected(room: Room, val error: Exception?) : RoomEvent(room)
28 28
29 /** 29 /**
30 * When a [RemoteParticipant] joins after the local participant. It will not emit events 30 * When a [RemoteParticipant] joins after the local participant. It will not emit events
31 * for participants that are already in the room 31 * for participants that are already in the room
32 */ 32 */
33 - class ParticipantConnected(val room: Room, val participant: RemoteParticipant): RoomEvent() 33 + class ParticipantConnected(room: Room, val participant: RemoteParticipant) : RoomEvent(room)
34 34
35 /** 35 /**
36 * When a [RemoteParticipant] leaves after the local participant has joined. 36 * When a [RemoteParticipant] leaves after the local participant has joined.
37 */ 37 */
38 - class ParticipantDisconnected(val room: Room, val participant: RemoteParticipant) : RoomEvent() 38 + class ParticipantDisconnected(room: Room, val participant: RemoteParticipant) : RoomEvent(room)
39 39
40 /** 40 /**
41 * Active speakers changed. List of speakers are ordered by their audio level. loudest 41 * Active speakers changed. List of speakers are ordered by their audio level. loudest
42 * speakers first. This will include the [LocalParticipant] too. 42 * speakers first. This will include the [LocalParticipant] too.
43 */ 43 */
44 - class ActiveSpeakersChanged(val room: Room, val speakers: List<Participant>) : RoomEvent() 44 + class ActiveSpeakersChanged(room: Room, val speakers: List<Participant>) : RoomEvent(room)
45 45
46 class RoomMetadataChanged( 46 class RoomMetadataChanged(
47 - val room: Room, 47 + room: Room,
48 val newMetadata: String?, 48 val newMetadata: String?,
49 val prevMetadata: String? 49 val prevMetadata: String?
50 - ) : RoomEvent() 50 + ) : RoomEvent(room)
51 51
52 // Participant callbacks 52 // Participant callbacks
53 /** 53 /**
@@ -56,10 +56,10 @@ sealed class RoomEvent : Event() { @@ -56,10 +56,10 @@ sealed class RoomEvent : Event() {
56 * this event will be fired for all clients in the room. 56 * this event will be fired for all clients in the room.
57 */ 57 */
58 class ParticipantMetadataChanged( 58 class ParticipantMetadataChanged(
59 - val room: Room, 59 + room: Room,
60 val participant: Participant, 60 val participant: Participant,
61 val prevMetadata: String? 61 val prevMetadata: String?
62 - ) : RoomEvent() 62 + ) : RoomEvent(room)
63 63
64 /** 64 /**
65 * The participant was muted. 65 * The participant was muted.
@@ -67,7 +67,7 @@ sealed class RoomEvent : Event() { @@ -67,7 +67,7 @@ sealed class RoomEvent : Event() {
67 * For the local participant, the callback will be called if setMute was called on the 67 * For the local participant, the callback will be called if setMute was called on the
68 * [LocalTrackPublication], or if the server has requested the participant to be muted 68 * [LocalTrackPublication], or if the server has requested the participant to be muted
69 */ 69 */
70 - class TrackMuted(val room: Room, val publication: TrackPublication, val participant: Participant): RoomEvent() 70 + class TrackMuted(room: Room, val publication: TrackPublication, val participant: Participant) : RoomEvent(room)
71 71
72 /** 72 /**
73 * The participant was unmuted. 73 * The participant was unmuted.
@@ -75,40 +75,56 @@ sealed class RoomEvent : Event() { @@ -75,40 +75,56 @@ sealed class RoomEvent : Event() {
75 * For the local participant, the callback will be called if setMute was called on the 75 * For the local participant, the callback will be called if setMute was called on the
76 * [LocalTrackPublication], or if the server has requested the participant to be muted 76 * [LocalTrackPublication], or if the server has requested the participant to be muted
77 */ 77 */
78 - class TrackUnmuted(val room: Room, val publication: TrackPublication, val participant: Participant): RoomEvent() 78 + class TrackUnmuted(room: Room, val publication: TrackPublication, val participant: Participant) : RoomEvent(room)
79 79
80 /** 80 /**
81 * When a new track is published to room after the local participant has joined. It will 81 * When a new track is published to room after the local participant has joined. It will
82 * not fire for tracks that are already published 82 * not fire for tracks that are already published
83 */ 83 */
84 - class TrackPublished(val room: Room, val publication: TrackPublication, val participant: Participant): RoomEvent() 84 + class TrackPublished(room: Room, val publication: TrackPublication, val participant: Participant) : RoomEvent(room)
85 85
86 /** 86 /**
87 * A [Participant] has unpublished a track 87 * A [Participant] has unpublished a track
88 */ 88 */
89 - class TrackUnpublished(val room: Room, val publication: TrackPublication, val participant: Participant): RoomEvent() 89 + class TrackUnpublished(room: Room, val publication: TrackPublication, val participant: Participant) :
  90 + RoomEvent(room)
90 91
91 /** 92 /**
92 * The [LocalParticipant] has subscribed to a new track. This event will always fire as 93 * The [LocalParticipant] has subscribed to a new track. This event will always fire as
93 * long as new tracks are ready for use. 94 * long as new tracks are ready for use.
94 */ 95 */
95 - class TrackSubscribed(val room: Room, val track: Track, val publication: TrackPublication, val participant: RemoteParticipant): RoomEvent() 96 + class TrackSubscribed(
  97 + room: Room,
  98 + val track: Track,
  99 + val publication: TrackPublication,
  100 + val participant: RemoteParticipant
  101 + ) : RoomEvent(room)
96 102
97 /** 103 /**
98 * Could not subscribe to a track 104 * Could not subscribe to a track
99 */ 105 */
100 - class TrackSubscriptionFailed(val room: Room, val sid: String, val exception: Exception, val participant: RemoteParticipant): RoomEvent() 106 + class TrackSubscriptionFailed(
  107 + room: Room,
  108 + val sid: String,
  109 + val exception: Exception,
  110 + val participant: RemoteParticipant
  111 + ) : RoomEvent(room)
101 112
102 /** 113 /**
103 * A subscribed track is no longer available. Clients should listen to this event and ensure 114 * A subscribed track is no longer available. Clients should listen to this event and ensure
104 * the track removes all renderers 115 * the track removes all renderers
105 */ 116 */
106 - class TrackUnsubscribed(val room: Room, val track: Track, val publications: TrackPublication, val participant: RemoteParticipant): RoomEvent() 117 + class TrackUnsubscribed(
  118 + room: Room,
  119 + val track: Track,
  120 + val publications: TrackPublication,
  121 + val participant: RemoteParticipant
  122 + ) : RoomEvent(room)
107 123
108 /** 124 /**
109 * Received data published by another participant 125 * Received data published by another participant
110 */ 126 */
111 - class DataReceived(val room: Room, val data: ByteArray, val participant: RemoteParticipant): RoomEvent() 127 + class DataReceived(room: Room, val data: ByteArray, val participant: RemoteParticipant) : RoomEvent(room)
112 128
113 /** 129 /**
114 * The connection quality for a participant has changed. 130 * The connection quality for a participant has changed.
@@ -116,6 +132,7 @@ sealed class RoomEvent : Event() { @@ -116,6 +132,7 @@ sealed class RoomEvent : Event() {
116 * @param participant Either a remote participant or [Room.localParticipant] 132 * @param participant Either a remote participant or [Room.localParticipant]
117 * @param quality the new connection quality 133 * @param quality the new connection quality
118 */ 134 */
119 - class ConnectionQualityChanged(val room: Room, val participant: Participant, val quality: ConnectionQuality): RoomEvent() 135 + class ConnectionQualityChanged(room: Room, val participant: Participant, val quality: ConnectionQuality) :
  136 + RoomEvent(room)
120 137
121 } 138 }
@@ -149,9 +149,9 @@ constructor( @@ -149,9 +149,9 @@ constructor(
149 } 149 }
150 150
151 participant = if (info != null) { 151 participant = if (info != null) {
152 - RemoteParticipant(info, engine.client, ioDispatcher) 152 + RemoteParticipant(info, engine.client, ioDispatcher, defaultDispatcher)
153 } else { 153 } else {
154 - RemoteParticipant(sid, null, engine.client, ioDispatcher) 154 + RemoteParticipant(sid, null, engine.client, ioDispatcher, defaultDispatcher)
155 } 155 }
156 participant.internalListener = this 156 participant.internalListener = this
157 mutableRemoteParticipants[sid] = participant 157 mutableRemoteParticipants[sid] = participant
@@ -381,7 +381,7 @@ constructor( @@ -381,7 +381,7 @@ constructor(
381 381
382 listener?.onDataReceived(data, participant, this) 382 listener?.onDataReceived(data, participant, this)
383 eventBus.postEvent(RoomEvent.DataReceived(this, data, participant), coroutineScope) 383 eventBus.postEvent(RoomEvent.DataReceived(this, data, participant), coroutineScope)
384 - participant.listener?.onDataReceived(data, participant) 384 + participant.onDataReceived(data)
385 } 385 }
386 386
387 /** 387 /**
@@ -7,16 +7,20 @@ import com.google.protobuf.ByteString @@ -7,16 +7,20 @@ import com.google.protobuf.ByteString
7 import dagger.assisted.Assisted 7 import dagger.assisted.Assisted
8 import dagger.assisted.AssistedFactory 8 import dagger.assisted.AssistedFactory
9 import dagger.assisted.AssistedInject 9 import dagger.assisted.AssistedInject
  10 +import io.livekit.android.dagger.InjectionNames
  11 +import io.livekit.android.events.ParticipantEvent
10 import io.livekit.android.room.DefaultsManager 12 import io.livekit.android.room.DefaultsManager
11 import io.livekit.android.room.RTCEngine 13 import io.livekit.android.room.RTCEngine
12 import io.livekit.android.room.track.* 14 import io.livekit.android.room.track.*
13 import io.livekit.android.util.LKLog 15 import io.livekit.android.util.LKLog
  16 +import kotlinx.coroutines.CoroutineDispatcher
14 import livekit.LivekitModels 17 import livekit.LivekitModels
15 import livekit.LivekitRtc 18 import livekit.LivekitRtc
16 import org.webrtc.EglBase 19 import org.webrtc.EglBase
17 import org.webrtc.PeerConnectionFactory 20 import org.webrtc.PeerConnectionFactory
18 import org.webrtc.RtpParameters 21 import org.webrtc.RtpParameters
19 import org.webrtc.RtpTransceiver 22 import org.webrtc.RtpTransceiver
  23 +import javax.inject.Named
20 import kotlin.math.abs 24 import kotlin.math.abs
21 import kotlin.math.roundToInt 25 import kotlin.math.roundToInt
22 26
@@ -31,8 +35,10 @@ internal constructor( @@ -31,8 +35,10 @@ internal constructor(
31 private val eglBase: EglBase, 35 private val eglBase: EglBase,
32 private val screencastVideoTrackFactory: LocalScreencastVideoTrack.Factory, 36 private val screencastVideoTrackFactory: LocalScreencastVideoTrack.Factory,
33 private val videoTrackFactory: LocalVideoTrack.Factory, 37 private val videoTrackFactory: LocalVideoTrack.Factory,
34 - private val defaultsManager: DefaultsManager  
35 -) : Participant(info.sid, info.identity) { 38 + private val defaultsManager: DefaultsManager,
  39 + @Named(InjectionNames.DISPATCHER_DEFAULT)
  40 + coroutineDispatcher: CoroutineDispatcher,
  41 +) : Participant(info.sid, info.identity, coroutineDispatcher) {
36 42
37 var audioTrackCaptureDefaults: LocalAudioTrackOptions by defaultsManager::audioTrackCaptureDefaults 43 var audioTrackCaptureDefaults: LocalAudioTrackOptions by defaultsManager::audioTrackCaptureDefaults
38 var audioTrackPublishDefaults: AudioTrackPublishDefaults by defaultsManager::audioTrackPublishDefaults 44 var audioTrackPublishDefaults: AudioTrackPublishDefaults by defaultsManager::audioTrackPublishDefaults
@@ -212,6 +218,7 @@ internal constructor( @@ -212,6 +218,7 @@ internal constructor(
212 addTrackPublication(publication) 218 addTrackPublication(publication)
213 publishListener?.onPublishSuccess(publication) 219 publishListener?.onPublishSuccess(publication)
214 internalListener?.onTrackPublished(publication, this) 220 internalListener?.onTrackPublished(publication, this)
  221 + eventBus.postEvent(ParticipantEvent.LocalTrackPublished(this, publication), scope)
215 } 222 }
216 223
217 suspend fun publishVideoTrack( 224 suspend fun publishVideoTrack(
@@ -260,6 +267,7 @@ internal constructor( @@ -260,6 +267,7 @@ internal constructor(
260 addTrackPublication(publication) 267 addTrackPublication(publication)
261 publishListener?.onPublishSuccess(publication) 268 publishListener?.onPublishSuccess(publication)
262 internalListener?.onTrackPublished(publication, this) 269 internalListener?.onTrackPublished(publication, this)
  270 + eventBus.postEvent(ParticipantEvent.LocalTrackPublished(this, publication), scope)
263 } 271 }
264 272
265 private fun computeVideoEncodings( 273 private fun computeVideoEncodings(
@@ -374,6 +382,7 @@ internal constructor( @@ -374,6 +382,7 @@ internal constructor(
374 } 382 }
375 track.stop() 383 track.stop()
376 internalListener?.onTrackUnpublished(publication, this) 384 internalListener?.onTrackUnpublished(publication, this)
  385 + eventBus.postEvent(ParticipantEvent.LocalTrackUnpublished(this, publication), scope)
377 } 386 }
378 387
379 /** 388 /**
@@ -420,11 +429,15 @@ internal constructor( @@ -420,11 +429,15 @@ internal constructor(
420 } 429 }
421 } 430 }
422 431
  432 + /**
  433 + * @suppress
  434 + */
423 fun onRemoteMuteChanged(trackSid: String, muted: Boolean) { 435 fun onRemoteMuteChanged(trackSid: String, muted: Boolean) {
424 val pub = tracks[trackSid] 436 val pub = tracks[trackSid]
425 pub?.muted = muted 437 pub?.muted = muted
426 } 438 }
427 439
  440 +
428 interface PublishListener { 441 interface PublishListener {
429 fun onPublishSuccess(publication: TrackPublication) {} 442 fun onPublishSuccess(publication: TrackPublication) {}
430 fun onPublishFailure(exception: Exception) {} 443 fun onPublishFailure(exception: Exception) {}
1 package io.livekit.android.room.participant 1 package io.livekit.android.room.participant
2 2
  3 +import io.livekit.android.dagger.InjectionNames
  4 +import io.livekit.android.events.BroadcastEventBus
  5 +import io.livekit.android.events.ParticipantEvent
3 import io.livekit.android.room.track.LocalTrackPublication 6 import io.livekit.android.room.track.LocalTrackPublication
4 import io.livekit.android.room.track.RemoteTrackPublication 7 import io.livekit.android.room.track.RemoteTrackPublication
5 import io.livekit.android.room.track.Track 8 import io.livekit.android.room.track.Track
6 import io.livekit.android.room.track.TrackPublication 9 import io.livekit.android.room.track.TrackPublication
  10 +import kotlinx.coroutines.CoroutineDispatcher
  11 +import kotlinx.coroutines.CoroutineScope
  12 +import kotlinx.coroutines.SupervisorJob
7 import livekit.LivekitModels 13 import livekit.LivekitModels
  14 +import javax.inject.Named
  15 +
  16 +open class Participant(
  17 + var sid: String,
  18 + identity: String? = null,
  19 + @Named(InjectionNames.DISPATCHER_DEFAULT)
  20 + coroutineDispatcher: CoroutineDispatcher,
  21 +) {
  22 + protected val scope = CoroutineScope(coroutineDispatcher + SupervisorJob())
  23 +
  24 + protected val eventBus = BroadcastEventBus<ParticipantEvent>()
  25 + val events = eventBus.readOnly()
8 26
9 -open class Participant(var sid: String, identity: String? = null) {  
10 var participantInfo: LivekitModels.ParticipantInfo? = null 27 var participantInfo: LivekitModels.ParticipantInfo? = null
11 private set 28 private set
12 var identity: String? = identity 29 var identity: String? = identity
@@ -15,11 +32,12 @@ open class Participant(var sid: String, identity: String? = null) { @@ -15,11 +32,12 @@ open class Participant(var sid: String, identity: String? = null) {
15 internal set 32 internal set
16 var isSpeaking: Boolean = false 33 var isSpeaking: Boolean = false
17 internal set(v) { 34 internal set(v) {
18 - val changed = v == field 35 + val changed = v != field
19 field = v 36 field = v
20 if (changed) { 37 if (changed) {
21 listener?.onSpeakingChanged(this) 38 listener?.onSpeakingChanged(this)
22 internalListener?.onSpeakingChanged(this) 39 internalListener?.onSpeakingChanged(this)
  40 + eventBus.postEvent(ParticipantEvent.SpeakingChanged(this, v), scope)
23 } 41 }
24 } 42 }
25 var metadata: String? = null 43 var metadata: String? = null
@@ -29,6 +47,7 @@ open class Participant(var sid: String, identity: String? = null) { @@ -29,6 +47,7 @@ open class Participant(var sid: String, identity: String? = null) {
29 if (prevMetadata != v) { 47 if (prevMetadata != v) {
30 listener?.onMetadataChanged(this, prevMetadata) 48 listener?.onMetadataChanged(this, prevMetadata)
31 internalListener?.onMetadataChanged(this, prevMetadata) 49 internalListener?.onMetadataChanged(this, prevMetadata)
  50 + eventBus.postEvent(ParticipantEvent.MetadataChanged(this, prevMetadata), scope)
32 } 51 }
33 } 52 }
34 var connectionQuality: ConnectionQuality = ConnectionQuality.UNKNOWN 53 var connectionQuality: ConnectionQuality = ConnectionQuality.UNKNOWN
@@ -37,11 +56,13 @@ open class Participant(var sid: String, identity: String? = null) { @@ -37,11 +56,13 @@ open class Participant(var sid: String, identity: String? = null) {
37 /** 56 /**
38 * Listener for when participant properties change 57 * Listener for when participant properties change
39 */ 58 */
  59 + @Deprecated("Use events instead")
40 var listener: ParticipantListener? = null 60 var listener: ParticipantListener? = null
41 61
42 /** 62 /**
43 * @suppress 63 * @suppress
44 */ 64 */
  65 + @Deprecated("Use events instead")
45 internal var internalListener: ParticipantListener? = null 66 internal var internalListener: ParticipantListener? = null
46 67
47 val hasInfo 68 val hasInfo
@@ -152,9 +173,24 @@ open class Participant(var sid: String, identity: String? = null) { @@ -152,9 +173,24 @@ open class Participant(var sid: String, identity: String? = null) {
152 override fun hashCode(): Int { 173 override fun hashCode(): Int {
153 return sid.hashCode() 174 return sid.hashCode()
154 } 175 }
155 -}  
156 176
157 177
  178 + // Internal methods just for posting events.
  179 + internal fun onTrackMuted(trackPublication: TrackPublication) {
  180 + listener?.onTrackMuted(trackPublication, this)
  181 + internalListener?.onTrackMuted(trackPublication, this)
  182 + eventBus.postEvent(ParticipantEvent.TrackMuted(this, trackPublication), scope)
  183 + }
  184 +
  185 + internal fun onTrackUnmuted(trackPublication: TrackPublication) {
  186 + listener?.onTrackUnmuted(trackPublication, this)
  187 + internalListener?.onTrackUnmuted(trackPublication, this)
  188 + eventBus.postEvent(ParticipantEvent.TrackUnmuted(this, trackPublication), scope)
  189 + }
  190 +
  191 +}
  192 +
  193 +@Deprecated("Use Participant.events instead.")
158 interface ParticipantListener { 194 interface ParticipantListener {
159 // all participants 195 // all participants
160 /** 196 /**
1 package io.livekit.android.room.participant 1 package io.livekit.android.room.participant
2 2
  3 +import io.livekit.android.events.ParticipantEvent
3 import io.livekit.android.room.SignalClient 4 import io.livekit.android.room.SignalClient
4 import io.livekit.android.room.track.* 5 import io.livekit.android.room.track.*
5 import io.livekit.android.util.CloseableCoroutineScope 6 import io.livekit.android.util.CloseableCoroutineScope
@@ -18,19 +19,22 @@ class RemoteParticipant( @@ -18,19 +19,22 @@ class RemoteParticipant(
18 identity: String? = null, 19 identity: String? = null,
19 val signalClient: SignalClient, 20 val signalClient: SignalClient,
20 private val ioDispatcher: CoroutineDispatcher, 21 private val ioDispatcher: CoroutineDispatcher,
21 -) : Participant(sid, identity) { 22 + defaultdispatcher: CoroutineDispatcher,
  23 +) : Participant(sid, identity, defaultdispatcher) {
22 /** 24 /**
23 * @suppress 25 * @suppress
24 */ 26 */
25 constructor( 27 constructor(
26 info: LivekitModels.ParticipantInfo, 28 info: LivekitModels.ParticipantInfo,
27 signalClient: SignalClient, 29 signalClient: SignalClient,
28 - ioDispatcher: CoroutineDispatcher 30 + ioDispatcher: CoroutineDispatcher,
  31 + defaultdispatcher: CoroutineDispatcher,
29 ) : this( 32 ) : this(
30 info.sid, 33 info.sid,
31 info.identity, 34 info.identity,
32 signalClient, 35 signalClient,
33 ioDispatcher, 36 ioDispatcher,
  37 + defaultdispatcher
34 ) { 38 ) {
35 updateFromInfo(info) 39 updateFromInfo(info)
36 } 40 }
@@ -73,6 +77,7 @@ class RemoteParticipant( @@ -73,6 +77,7 @@ class RemoteParticipant(
73 for (publication in newTrackPublications.values) { 77 for (publication in newTrackPublications.values) {
74 internalListener?.onTrackPublished(publication, this) 78 internalListener?.onTrackPublished(publication, this)
75 listener?.onTrackPublished(publication, this) 79 listener?.onTrackPublished(publication, this)
  80 + eventBus.postEvent(ParticipantEvent.TrackPublished(this, publication), scope)
76 } 81 }
77 } 82 }
78 83
@@ -112,6 +117,7 @@ class RemoteParticipant( @@ -112,6 +117,7 @@ class RemoteParticipant(
112 117
113 internalListener?.onTrackSubscriptionFailed(sid, exception, this) 118 internalListener?.onTrackSubscriptionFailed(sid, exception, this)
114 listener?.onTrackSubscriptionFailed(sid, exception, this) 119 listener?.onTrackSubscriptionFailed(sid, exception, this)
  120 + eventBus.postEvent(ParticipantEvent.TrackSubscriptionFailed(this, sid, exception), scope)
115 } else { 121 } else {
116 coroutineScope.launch { 122 coroutineScope.launch {
117 delay(150) 123 delay(150)
@@ -131,6 +137,7 @@ class RemoteParticipant( @@ -131,6 +137,7 @@ class RemoteParticipant(
131 137
132 internalListener?.onTrackSubscribed(track, publication, this) 138 internalListener?.onTrackSubscribed(track, publication, this)
133 listener?.onTrackSubscribed(track, publication, this) 139 listener?.onTrackSubscribed(track, publication, this)
  140 + eventBus.postEvent(ParticipantEvent.TrackSubscribed(this, track, publication), scope)
134 } 141 }
135 142
136 fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) { 143 fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) {
@@ -146,13 +153,21 @@ class RemoteParticipant( @@ -146,13 +153,21 @@ class RemoteParticipant(
146 track.stop() 153 track.stop()
147 internalListener?.onTrackUnsubscribed(track, publication, this) 154 internalListener?.onTrackUnsubscribed(track, publication, this)
148 listener?.onTrackUnsubscribed(track, publication, this) 155 listener?.onTrackUnsubscribed(track, publication, this)
  156 + eventBus.postEvent(ParticipantEvent.TrackUnsubscribed(this, track, publication), scope)
149 } 157 }
150 if (sendUnpublish) { 158 if (sendUnpublish) {
151 internalListener?.onTrackUnpublished(publication, this) 159 internalListener?.onTrackUnpublished(publication, this)
152 listener?.onTrackUnpublished(publication, this) 160 listener?.onTrackUnpublished(publication, this)
  161 + eventBus.postEvent(ParticipantEvent.TrackUnpublished(this, publication), scope)
153 } 162 }
154 } 163 }
155 164
  165 + // Internal methods just for posting events.
  166 + internal fun onDataReceived(data: ByteArray) {
  167 + listener?.onDataReceived(data, this)
  168 + eventBus.postEvent(ParticipantEvent.DataReceived(this, data), scope)
  169 + }
  170 +
156 companion object { 171 companion object {
157 private const val KIND_AUDIO = "audio" 172 private const val KIND_AUDIO = "audio"
158 private const val KIND_VIDEO = "video" 173 private const val KIND_VIDEO = "video"
@@ -31,11 +31,9 @@ class LocalTrackPublication( @@ -31,11 +31,9 @@ class LocalTrackPublication(
31 participant.engine.updateMuteStatus(sid, muted) 31 participant.engine.updateMuteStatus(sid, muted)
32 32
33 if (muted) { 33 if (muted) {
34 - participant.listener?.onTrackMuted(this, participant)  
35 - participant.internalListener?.onTrackMuted(this, participant) 34 + participant.onTrackMuted(this)
36 } else { 35 } else {
37 - participant.listener?.onTrackUnmuted(this, participant)  
38 - participant.internalListener?.onTrackUnmuted(this, participant) 36 + participant.onTrackUnmuted(this)
39 } 37 }
40 } 38 }
41 } 39 }
@@ -77,11 +77,9 @@ class RemoteTrackPublication( @@ -77,11 +77,9 @@ class RemoteTrackPublication(
77 field = v 77 field = v
78 val participant = this.participant.get() as? RemoteParticipant ?: return 78 val participant = this.participant.get() as? RemoteParticipant ?: return
79 if (v) { 79 if (v) {
80 - participant.listener?.onTrackMuted(this, participant)  
81 - participant.internalListener?.onTrackMuted(this, participant) 80 + participant.onTrackMuted(this)
82 } else { 81 } else {
83 - participant.listener?.onTrackUnmuted(this, participant)  
84 - participant.internalListener?.onTrackUnmuted(this, participant) 82 + participant.onTrackUnmuted(this)
85 } 83 }
86 } 84 }
87 85
1 package io.livekit.android.room.participant 1 package io.livekit.android.room.participant
2 2
  3 +import io.livekit.android.coroutines.TestCoroutineRule
  4 +import io.livekit.android.events.EventCollector
  5 +import io.livekit.android.events.ParticipantEvent
3 import io.livekit.android.room.track.TrackPublication 6 import io.livekit.android.room.track.TrackPublication
4 import livekit.LivekitModels 7 import livekit.LivekitModels
5 import org.junit.Assert.assertEquals 8 import org.junit.Assert.assertEquals
6 import org.junit.Assert.assertTrue 9 import org.junit.Assert.assertTrue
7 import org.junit.Before 10 import org.junit.Before
  11 +import org.junit.Rule
8 import org.junit.Test 12 import org.junit.Test
9 13
10 class ParticipantTest { 14 class ParticipantTest {
11 15
  16 + @get:Rule
  17 + var coroutineRule = TestCoroutineRule()
  18 +
12 lateinit var participant: Participant 19 lateinit var participant: Participant
13 20
14 @Before 21 @Before
15 fun setup() { 22 fun setup() {
16 - participant = Participant("", null) 23 + participant = Participant("", null, coroutineRule.dispatcher)
17 } 24 }
18 25
19 @Test 26 @Test
@@ -58,7 +65,41 @@ class ParticipantTest { @@ -58,7 +65,41 @@ class ParticipantTest {
58 65
59 checkValues(publicListener) 66 checkValues(publicListener)
60 checkValues(internalListener) 67 checkValues(internalListener)
  68 + }
  69 +
  70 + @Test
  71 + fun setMetadataChangedEvent() {
  72 + val eventCollector = EventCollector(participant.events, coroutineRule.scope)
  73 + val prevMetadata = participant.metadata
  74 + val metadata = "metadata"
  75 + participant.metadata = metadata
  76 +
  77 + val events = eventCollector.stopCollectingEvents()
  78 +
  79 + assertEquals(1, events.size)
  80 + assertEquals(true, events[0] is ParticipantEvent.MetadataChanged)
  81 +
  82 + val event = events[0] as ParticipantEvent.MetadataChanged
  83 +
  84 + assertEquals(prevMetadata, event.prevMetadata)
  85 + assertEquals(participant, event.participant)
  86 + }
  87 +
  88 + @Test
  89 + fun setIsSpeakingChangedEvent() {
  90 + val eventCollector = EventCollector(participant.events, coroutineRule.scope)
  91 + val newIsSpeaking = !participant.isSpeaking
  92 + participant.isSpeaking = newIsSpeaking
  93 +
  94 + val events = eventCollector.stopCollectingEvents()
  95 +
  96 + assertEquals(1, events.size)
  97 + assertEquals(true, events[0] is ParticipantEvent.SpeakingChanged)
  98 +
  99 + val event = events[0] as ParticipantEvent.SpeakingChanged
61 100
  101 + assertEquals(participant, event.participant)
  102 + assertEquals(newIsSpeaking, event.isSpeaking)
62 } 103 }
63 104
64 @Test 105 @Test
@@ -23,7 +23,8 @@ class RemoteParticipantTest { @@ -23,7 +23,8 @@ class RemoteParticipantTest {
23 participant = RemoteParticipant( 23 participant = RemoteParticipant(
24 "sid", 24 "sid",
25 signalClient = signalClient, 25 signalClient = signalClient,
26 - ioDispatcher = coroutineRule.dispatcher 26 + ioDispatcher = coroutineRule.dispatcher,
  27 + defaultdispatcher = coroutineRule.dispatcher,
27 ) 28 )
28 } 29 }
29 30
@@ -33,7 +34,12 @@ class RemoteParticipantTest { @@ -33,7 +34,12 @@ class RemoteParticipantTest {
33 .addTracks(TRACK_INFO) 34 .addTracks(TRACK_INFO)
34 .build() 35 .build()
35 36
36 - participant = RemoteParticipant(info, signalClient, ioDispatcher = coroutineRule.dispatcher) 37 + participant = RemoteParticipant(
  38 + info,
  39 + signalClient,
  40 + ioDispatcher = coroutineRule.dispatcher,
  41 + defaultdispatcher = coroutineRule.dispatcher,
  42 + )
37 43
38 assertEquals(1, participant.tracks.values.size) 44 assertEquals(1, participant.tracks.values.size)
39 assertNotNull(participant.getTrackPublication(TRACK_INFO.sid)) 45 assertNotNull(participant.getTrackPublication(TRACK_INFO.sid))