David Zhao

feature: mute support, selective track handling

@@ -180,6 +180,32 @@ constructor( @@ -180,6 +180,32 @@ constructor(
180 sendRequest(request) 180 sendRequest(request)
181 } 181 }
182 182
  183 + fun sendUpdateTrackSettings(sid: String, disabled: Boolean, videoQuality: LivekitRtc.VideoQuality) {
  184 + val trackSettings = LivekitRtc.UpdateTrackSettings.newBuilder()
  185 + .setTrackSids(0, sid)
  186 + .setDisabled(disabled)
  187 + .setQuality(videoQuality)
  188 +
  189 + val request = LivekitRtc.SignalRequest.newBuilder()
  190 + .setTrackSetting(trackSettings)
  191 + .build()
  192 +
  193 + sendRequest(request)
  194 + }
  195 +
  196 + fun sendUpdateSubscription(sid: String, subscribe: Boolean, videoQuality: LivekitRtc.VideoQuality) {
  197 + val subscription = LivekitRtc.UpdateSubscription.newBuilder()
  198 + .setTrackSids(0, sid)
  199 + .setSubscribe(subscribe)
  200 + .setQuality(videoQuality)
  201 +
  202 + val request = LivekitRtc.SignalRequest.newBuilder()
  203 + .setSubscription(subscription)
  204 + .build()
  205 +
  206 + sendRequest(request)
  207 + }
  208 +
183 fun sendRequest(request: LivekitRtc.SignalRequest) { 209 fun sendRequest(request: LivekitRtc.SignalRequest) {
184 Timber.v { "sending request: $request" } 210 Timber.v { "sending request: $request" }
185 if (!isConnected || currentWs == null) { 211 if (!isConnected || currentWs == null) {
@@ -9,9 +9,7 @@ import io.livekit.android.room.participant.LocalParticipant @@ -9,9 +9,7 @@ import io.livekit.android.room.participant.LocalParticipant
9 import io.livekit.android.room.participant.Participant 9 import io.livekit.android.room.participant.Participant
10 import io.livekit.android.room.participant.ParticipantListener 10 import io.livekit.android.room.participant.ParticipantListener
11 import io.livekit.android.room.participant.RemoteParticipant 11 import io.livekit.android.room.participant.RemoteParticipant
12 -import io.livekit.android.room.track.DataTrack  
13 -import io.livekit.android.room.track.Track  
14 -import io.livekit.android.room.track.TrackPublication 12 +import io.livekit.android.room.track.*
15 import io.livekit.android.room.util.unpackedTrackLabel 13 import io.livekit.android.room.util.unpackedTrackLabel
16 import livekit.LivekitModels 14 import livekit.LivekitModels
17 import livekit.LivekitRtc 15 import livekit.LivekitRtc
@@ -92,9 +90,9 @@ constructor( @@ -92,9 +90,9 @@ constructor(
92 } 90 }
93 91
94 participant = if (info != null) { 92 participant = if (info != null) {
95 - RemoteParticipant(info) 93 + RemoteParticipant(engine.client, info)
96 } else { 94 } else {
97 - RemoteParticipant(sid, null) 95 + RemoteParticipant(engine.client, sid, null)
98 } 96 }
99 participant.internalListener = this 97 participant.internalListener = this
100 mutableRemoteParticipants[sid] = participant 98 mutableRemoteParticipants[sid] = participant
@@ -258,24 +256,34 @@ constructor( @@ -258,24 +256,34 @@ constructor(
258 listener?.onMetadataChanged(participant, prevMetadata, this) 256 listener?.onMetadataChanged(participant, prevMetadata, this)
259 } 257 }
260 258
  259 + /** @suppress */
  260 + override fun onTrackMuted(publication: TrackPublication, participant: Participant) {
  261 + listener?.onTrackMuted(publication, participant, this)
  262 + }
  263 +
  264 + /** @suppress */
  265 + override fun onTrackUnmuted(publication: TrackPublication, participant: Participant) {
  266 + listener?.onTrackUnmuted(publication, participant, this)
  267 + }
  268 +
261 /** 269 /**
262 * @suppress 270 * @suppress
263 */ 271 */
264 - override fun onTrackPublished(publication: TrackPublication, participant: RemoteParticipant) { 272 + override fun onTrackPublished(publication: RemoteTrackPublication, participant: RemoteParticipant) {
265 listener?.onTrackPublished(publication, participant, this) 273 listener?.onTrackPublished(publication, participant, this)
266 } 274 }
267 275
268 /** 276 /**
269 * @suppress 277 * @suppress
270 */ 278 */
271 - override fun onTrackUnpublished(publication: TrackPublication, participant: RemoteParticipant) { 279 + override fun onTrackUnpublished(publication: RemoteTrackPublication, participant: RemoteParticipant) {
272 listener?.onTrackUnpublished(publication, participant, this) 280 listener?.onTrackUnpublished(publication, participant, this)
273 } 281 }
274 282
275 /** 283 /**
276 * @suppress 284 * @suppress
277 */ 285 */
278 - override fun onTrackSubscribed(track: Track, publication: TrackPublication, participant: RemoteParticipant) { 286 + override fun onTrackSubscribed(track: Track, publication: RemoteTrackPublication, participant: RemoteParticipant) {
279 listener?.onTrackSubscribed(track, publication, participant, this) 287 listener?.onTrackSubscribed(track, publication, participant, this)
280 } 288 }
281 289
@@ -295,7 +303,7 @@ constructor( @@ -295,7 +303,7 @@ constructor(
295 */ 303 */
296 override fun onTrackUnsubscribed( 304 override fun onTrackUnsubscribed(
297 track: Track, 305 track: Track,
298 - publication: TrackPublication, 306 + publication: RemoteTrackPublication,
299 participant: RemoteParticipant 307 participant: RemoteParticipant
300 ) { 308 ) {
301 listener?.onTrackUnsubscribed(track, publication, participant, this) 309 listener?.onTrackUnsubscribed(track, publication, participant, this)
@@ -366,6 +374,22 @@ interface RoomListener { @@ -366,6 +374,22 @@ interface RoomListener {
366 fun onMetadataChanged(participant: Participant, prevMetadata: String?, room: Room) {} 374 fun onMetadataChanged(participant: Participant, prevMetadata: String?, room: Room) {}
367 375
368 /** 376 /**
  377 + * The participant was muted.
  378 + *
  379 + * For the local participant, the callback will be called if setMute was called on the
  380 + * [LocalTrackPublication], or if the server has requested the participant to be muted
  381 + */
  382 + fun onTrackMuted(publication: TrackPublication, participant: Participant, room: Room) {}
  383 +
  384 + /**
  385 + * The participant was unmuted.
  386 + *
  387 + * For the local participant, the callback will be called if setMute was called on the
  388 + * [LocalTrackPublication], or if the server has requested the participant to be muted
  389 + */
  390 + fun onTrackUnmuted(publication: TrackPublication, participant: Participant, room: Room) {}
  391 +
  392 + /**
369 * When a new track is published to room after the local participant has joined. It will 393 * When a new track is published to room after the local participant has joined. It will
370 * not fire for tracks that are already published 394 * not fire for tracks that are already published
371 */ 395 */
@@ -15,7 +15,7 @@ class LocalParticipant @@ -15,7 +15,7 @@ class LocalParticipant
15 internal constructor( 15 internal constructor(
16 @Assisted 16 @Assisted
17 info: LivekitModels.ParticipantInfo, 17 info: LivekitModels.ParticipantInfo,
18 - private val engine: RTCEngine, 18 + internal val engine: RTCEngine,
19 private val peerConnectionFactory: PeerConnectionFactory, 19 private val peerConnectionFactory: PeerConnectionFactory,
20 private val context: Context, 20 private val context: Context,
21 private val eglBase: EglBase, 21 private val eglBase: EglBase,
@@ -72,7 +72,7 @@ internal constructor( @@ -72,7 +72,7 @@ internal constructor(
72 return 72 return
73 } 73 }
74 74
75 - val publication = TrackPublication(trackInfo, track) 75 + val publication = LocalTrackPublication(trackInfo, track, this)
76 addTrackPublication(publication) 76 addTrackPublication(publication)
77 publishListener?.onPublishSuccess(publication) 77 publishListener?.onPublishSuccess(publication)
78 } 78 }
@@ -102,7 +102,7 @@ internal constructor( @@ -102,7 +102,7 @@ internal constructor(
102 return 102 return
103 } 103 }
104 104
105 - val publication = TrackPublication(trackInfo, track) 105 + val publication = LocalTrackPublication(trackInfo, track, this)
106 addTrackPublication(publication) 106 addTrackPublication(publication)
107 publishListener?.onPublishSuccess(publication) 107 publishListener?.onPublishSuccess(publication)
108 } 108 }
@@ -120,7 +120,7 @@ internal constructor( @@ -120,7 +120,7 @@ internal constructor(
120 val cid = track.name 120 val cid = track.name
121 val trackInfo = 121 val trackInfo =
122 engine.addTrack(cid = cid, name = track.name, track.kind) 122 engine.addTrack(cid = cid, name = track.name, track.kind)
123 - val publication = TrackPublication(trackInfo, track) 123 + val publication = LocalTrackPublication(trackInfo, track, this)
124 124
125 val config = DataChannel.Init().apply { 125 val config = DataChannel.Init().apply {
126 ordered = track.options.ordered 126 ordered = track.options.ordered
@@ -159,15 +159,27 @@ internal constructor( @@ -159,15 +159,27 @@ internal constructor(
159 } 159 }
160 } 160 }
161 161
  162 + override fun updateFromInfo(info: LivekitModels.ParticipantInfo) {
  163 + super.updateFromInfo(info)
  164 +
  165 + // detect tracks that have been muted on the server side, apply those changes
  166 + for (ti in info.tracksList) {
  167 + val publication = this.tracks[ti.sid] as? LocalTrackPublication ?: continue
  168 + if (ti.muted != publication.muted) {
  169 + publication.setMuted(ti.muted)
  170 + }
  171 + }
  172 + }
  173 +
162 private fun <T> unpublishMediaTrack( 174 private fun <T> unpublishMediaTrack(
163 track: T, 175 track: T,
164 sid: String 176 sid: String
165 ) where T : MediaTrack { 177 ) where T : MediaTrack {
166 - val senders = engine?.publisher?.peerConnection?.senders ?: return 178 + val senders = engine.publisher?.peerConnection?.senders ?: return
167 for (sender in senders) { 179 for (sender in senders) {
168 val t = sender.track() ?: continue 180 val t = sender.track() ?: continue
169 if (t == track.rtcTrack) { 181 if (t == track.rtcTrack) {
170 - engine?.publisher?.peerConnection?.removeTrack(sender) 182 + engine.publisher?.peerConnection?.removeTrack(sender)
171 } 183 }
172 } 184 }
173 } 185 }
@@ -60,7 +60,7 @@ open class Participant(var sid: String, identity: String? = null) { @@ -60,7 +60,7 @@ open class Participant(var sid: String, identity: String? = null) {
60 /** 60 /**
61 * @suppress 61 * @suppress
62 */ 62 */
63 - open fun updateFromInfo(info: LivekitModels.ParticipantInfo) { 63 + internal open fun updateFromInfo(info: LivekitModels.ParticipantInfo) {
64 sid = info.sid 64 sid = info.sid
65 identity = info.identity 65 identity = info.identity
66 participantInfo = info 66 participantInfo = info
@@ -92,6 +92,7 @@ open class Participant(var sid: String, identity: String? = null) { @@ -92,6 +92,7 @@ open class Participant(var sid: String, identity: String? = null) {
92 92
93 93
94 interface ParticipantListener { 94 interface ParticipantListener {
  95 + // all participants
95 /** 96 /**
96 * When a participant's metadata is updated, fired for all participants 97 * When a participant's metadata is updated, fired for all participants
97 */ 98 */
@@ -102,13 +103,44 @@ interface ParticipantListener { @@ -102,13 +103,44 @@ interface ParticipantListener {
102 */ 103 */
103 fun onSpeakingChanged(participant: Participant) {} 104 fun onSpeakingChanged(participant: Participant) {}
104 105
105 - fun onTrackPublished(publication: TrackPublication, participant: RemoteParticipant) {}  
106 - fun onTrackUnpublished(publication: TrackPublication, participant: RemoteParticipant) {} 106 + /**
  107 + * The participant was muted.
  108 + *
  109 + * For the local participant, the callback will be called if setMute was called on the
  110 + * [LocalTrackPublication], or if the server has requested the participant to be muted
  111 + */
  112 + fun onTrackMuted(publication: TrackPublication, participant: Participant) {}
  113 +
  114 + /**
  115 + * The participant was unmuted.
  116 + *
  117 + * For the local participant, the callback will be called if setMute was called on the
  118 + * [LocalTrackPublication], or if the server has requested the participant to be muted
  119 + */
  120 + fun onTrackUnmuted(publication: TrackPublication, participant: Participant) {}
  121 +
107 122
108 - fun onEnable(publication: TrackPublication, participant: RemoteParticipant) {}  
109 - fun onDisable(publication: TrackPublication, participant: RemoteParticipant) {} 123 + // remote participants
  124 + /**
  125 + * When a new track is published to room after the local participant has joined.
  126 + *
  127 + * It will not fire for tracks that are already published
  128 + */
  129 + fun onTrackPublished(publication: RemoteTrackPublication, participant: RemoteParticipant) {}
  130 +
  131 + /**
  132 + * A [RemoteParticipant] has unpublished a track
  133 + */
  134 + fun onTrackUnpublished(publication: RemoteTrackPublication, participant: RemoteParticipant) {}
110 135
111 - fun onTrackSubscribed(track: Track, publication: TrackPublication, participant: RemoteParticipant) {} 136 + /**
  137 + * Subscribed to a new track
  138 + */
  139 + fun onTrackSubscribed(track: Track, publication: RemoteTrackPublication, participant: RemoteParticipant) {}
  140 +
  141 + /**
  142 + * Error had occurred while subscribing to a track
  143 + */
112 fun onTrackSubscriptionFailed( 144 fun onTrackSubscriptionFailed(
113 sid: String, 145 sid: String,
114 exception: Exception, 146 exception: Exception,
@@ -116,20 +148,24 @@ interface ParticipantListener { @@ -116,20 +148,24 @@ interface ParticipantListener {
116 ) { 148 ) {
117 } 149 }
118 150
  151 + /**
  152 + * A subscribed track is no longer available.
  153 + * Clients should listen to this event and handle cleanup
  154 + */
119 fun onTrackUnsubscribed( 155 fun onTrackUnsubscribed(
120 track: Track, 156 track: Track,
121 - publication: TrackPublication, 157 + publication: RemoteTrackPublication,
122 participant: RemoteParticipant 158 participant: RemoteParticipant
123 ) { 159 ) {
124 } 160 }
125 161
  162 + /**
  163 + * Data was received on a data track
  164 + */
126 fun onDataReceived( 165 fun onDataReceived(
127 data: ByteBuffer, 166 data: ByteBuffer,
128 dataTrack: DataTrack, 167 dataTrack: DataTrack,
129 participant: RemoteParticipant 168 participant: RemoteParticipant
130 ) { 169 ) {
131 } 170 }
132 -  
133 - fun switchedOffVideo(track: VideoTrack, publication: TrackPublication, participant: RemoteParticipant) {}  
134 - fun switchedOnVideo(track: VideoTrack, publication: TrackPublication, participant: RemoteParticipant) {}  
135 } 171 }
1 package io.livekit.android.room.participant 1 package io.livekit.android.room.participant
2 2
3 import com.github.ajalt.timberkt.Timber 3 import com.github.ajalt.timberkt.Timber
  4 +import io.livekit.android.room.RTCClient
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
6 import kotlinx.coroutines.SupervisorJob 7 import kotlinx.coroutines.SupervisorJob
@@ -11,21 +12,21 @@ import org.webrtc.AudioTrack @@ -11,21 +12,21 @@ import org.webrtc.AudioTrack
11 import org.webrtc.DataChannel 12 import org.webrtc.DataChannel
12 import org.webrtc.MediaStreamTrack 13 import org.webrtc.MediaStreamTrack
13 import org.webrtc.VideoTrack 14 import org.webrtc.VideoTrack
14 -import java.nio.ByteBuffer  
15 15
16 class RemoteParticipant( 16 class RemoteParticipant(
17 - sid: String, name: String? = null 17 + val rtcClient: RTCClient,
  18 + sid: String, name: String? = null,
18 ) : Participant(sid, name) { 19 ) : Participant(sid, name) {
19 /** 20 /**
20 * @suppress 21 * @suppress
21 */ 22 */
22 - constructor(info: LivekitModels.ParticipantInfo) : this(info.sid, info.identity) { 23 + constructor(rtcClient: RTCClient, info: LivekitModels.ParticipantInfo) : this(rtcClient, info.sid, info.identity) {
23 updateFromInfo(info) 24 updateFromInfo(info)
24 } 25 }
25 26
26 private val coroutineScope = CloseableCoroutineScope(SupervisorJob()) 27 private val coroutineScope = CloseableCoroutineScope(SupervisorJob())
27 28
28 - fun getTrackPublication(sid: String): TrackPublication? = tracks[sid] 29 + fun getTrackPublication(sid: String): RemoteTrackPublication? = tracks[sid] as? RemoteTrackPublication
29 30
30 /** 31 /**
31 * @suppress 32 * @suppress
@@ -34,15 +35,15 @@ class RemoteParticipant( @@ -34,15 +35,15 @@ class RemoteParticipant(
34 val hadInfo = hasInfo 35 val hadInfo = hasInfo
35 super.updateFromInfo(info) 36 super.updateFromInfo(info)
36 37
37 - val validTrackPublication = mutableMapOf<String, TrackPublication>()  
38 - val newTrackPublications = mutableMapOf<String, TrackPublication>() 38 + val validTrackPublication = mutableMapOf<String, RemoteTrackPublication>()
  39 + val newTrackPublications = mutableMapOf<String, RemoteTrackPublication>()
39 40
40 for (trackInfo in info.tracksList) { 41 for (trackInfo in info.tracksList) {
41 val trackSid = trackInfo.sid 42 val trackSid = trackInfo.sid
42 var publication = getTrackPublication(trackSid) 43 var publication = getTrackPublication(trackSid)
43 44
44 if (publication == null) { 45 if (publication == null) {
45 - publication = TrackPublication(trackInfo) 46 + publication = RemoteTrackPublication(trackInfo, participant = this)
46 47
47 newTrackPublications[trackSid] = publication 48 newTrackPublications[trackSid] = publication
48 addTrackPublication(publication) 49 addTrackPublication(publication)
@@ -119,7 +120,7 @@ class RemoteParticipant( @@ -119,7 +120,7 @@ class RemoteParticipant(
119 .setName(name) 120 .setName(name)
120 .setType(LivekitModels.TrackType.DATA) 121 .setType(LivekitModels.TrackType.DATA)
121 .build() 122 .build()
122 - publication = TrackPublication(info = trackInfo) 123 + publication = RemoteTrackPublication(info = trackInfo, participant = this)
123 addTrackPublication(publication) 124 addTrackPublication(publication)
124 if (hasInfo) { 125 if (hasInfo) {
125 internalListener?.onTrackPublished(publication, this) 126 internalListener?.onTrackPublished(publication, this)
@@ -151,7 +152,7 @@ class RemoteParticipant( @@ -151,7 +152,7 @@ class RemoteParticipant(
151 } 152 }
152 153
153 fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) { 154 fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) {
154 - val publication = tracks.remove(trackSid) ?: return 155 + val publication = tracks.remove(trackSid) as? RemoteTrackPublication ?: return
155 when (publication.kind) { 156 when (publication.kind) {
156 LivekitModels.TrackType.AUDIO -> audioTracks.remove(trackSid) 157 LivekitModels.TrackType.AUDIO -> audioTracks.remove(trackSid)
157 LivekitModels.TrackType.VIDEO -> videoTracks.remove(trackSid) 158 LivekitModels.TrackType.VIDEO -> videoTracks.remove(trackSid)
  1 +package io.livekit.android.room.track
  2 +
  3 +import io.livekit.android.room.participant.LocalParticipant
  4 +import livekit.LivekitModels
  5 +
  6 +class LocalTrackPublication(info: LivekitModels.TrackInfo,
  7 + track: Track? = null,
  8 + participant: LocalParticipant? = null) : TrackPublication(info, track, participant) {
  9 +
  10 + /**
  11 + * Mute or unmute the current track. Muting the track would stop audio or video from being
  12 + * transmitted to the server, and notify other participants in the room.
  13 + */
  14 + fun setMuted(muted: Boolean) {
  15 + if (muted == this.muted) {
  16 + return
  17 + }
  18 +
  19 + val mediaTrack = track as? MediaTrack ?: return
  20 +
  21 + mediaTrack.rtcTrack.setEnabled(!muted)
  22 + this.muted = muted
  23 +
  24 + // send updates to server
  25 + val participant = this.participant.get() as? LocalParticipant ?: return
  26 +
  27 + participant.engine.updateMuteStatus(sid, muted)
  28 +
  29 + if (muted) {
  30 + participant.listener?.onTrackMuted(this, participant)
  31 + participant.internalListener?.onTrackMuted(this, participant)
  32 + } else {
  33 + participant.listener?.onTrackUnmuted(this, participant)
  34 + participant.internalListener?.onTrackUnmuted(this, participant)
  35 + }
  36 + }
  37 +}
  1 +package io.livekit.android.room.track
  2 +
  3 +import io.livekit.android.room.participant.RemoteParticipant
  4 +import livekit.LivekitModels
  5 +import livekit.LivekitRtc
  6 +
  7 +class RemoteTrackPublication(info: LivekitModels.TrackInfo,
  8 + track: Track? = null, participant:
  9 + RemoteParticipant? = null): TrackPublication(info, track, participant) {
  10 +
  11 + private var unsubscribed: Boolean = false
  12 + private var disabled: Boolean = false
  13 + private var videoQuality: LivekitRtc.VideoQuality = LivekitRtc.VideoQuality.HIGH
  14 +
  15 + override val subscribed: Boolean
  16 + get() {
  17 + if (unsubscribed) {
  18 + return false
  19 + }
  20 + return super.subscribed
  21 + }
  22 + override var muted: Boolean = false
  23 + set(v) {
  24 + if (field == v) {
  25 + return
  26 + }
  27 + field = v
  28 + val participant = this.participant.get() as? RemoteParticipant ?: return
  29 + if (v) {
  30 + participant.listener?.onTrackMuted(this, participant)
  31 + participant.internalListener?.onTrackMuted(this, participant)
  32 + } else {
  33 + participant.listener?.onTrackUnmuted(this, participant)
  34 + participant.internalListener?.onTrackUnmuted(this, participant)
  35 + }
  36 + }
  37 +
  38 + /**
  39 + * subscribe or unsubscribe from this track
  40 + */
  41 + fun setSubscribed(subscribed: Boolean) {
  42 + unsubscribed = !subscribed
  43 + val participant = this.participant.get() as? RemoteParticipant ?: return
  44 +
  45 + participant.rtcClient.sendUpdateSubscription(sid, !unsubscribed, videoQuality)
  46 + }
  47 +
  48 + /**
  49 + * disable server from sending down data for this track
  50 + *
  51 + * this is useful when the participant is off screen, you may disable streaming down their
  52 + * video to reduce bandwidth requirements
  53 + */
  54 + fun setEnabled(enabled: Boolean) {
  55 + disabled = !enabled
  56 + sendUpdateTrackSettings()
  57 + }
  58 +
  59 + /**
  60 + * for tracks that support simulcasting, adjust subscribed quality
  61 + *
  62 + * this indicates the highest quality the client can accept. if network bandwidth does not
  63 + * allow, server will automatically reduce quality to optimize for uninterrupted video
  64 + */
  65 + fun setVideoQuality(quality: LivekitRtc.VideoQuality) {
  66 + videoQuality = quality
  67 + sendUpdateTrackSettings()
  68 + }
  69 +
  70 + private fun sendUpdateTrackSettings() {
  71 + val participant = this.participant.get() as? RemoteParticipant ?: return
  72 +
  73 + participant.rtcClient.sendUpdateTrackSettings(sid, disabled, videoQuality)
  74 + }
  75 +}
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
  3 +import io.livekit.android.room.participant.Participant
3 import livekit.LivekitModels 4 import livekit.LivekitModels
  5 +import java.lang.ref.WeakReference
4 6
5 -open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) { 7 +open class TrackPublication(info: LivekitModels.TrackInfo, track: Track?, participant: Participant?) {
6 var track: Track? = track 8 var track: Track? = track
7 internal set 9 internal set
8 var name: String 10 var name: String
@@ -11,14 +13,21 @@ open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) @@ -11,14 +13,21 @@ open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null)
11 private set 13 private set
12 var kind: LivekitModels.TrackType 14 var kind: LivekitModels.TrackType
13 private set 15 private set
14 - var muted: Boolean  
15 - private set 16 + open var muted: Boolean = false
  17 + internal set
  18 + open val subscribed: Boolean
  19 + get() {
  20 + return track != null
  21 + }
  22 +
  23 + var participant: WeakReference<Participant>;
16 24
17 init { 25 init {
18 sid = info.sid 26 sid = info.sid
19 name = info.name 27 name = info.name
20 kind = info.type 28 kind = info.type
21 muted = info.muted 29 muted = info.muted
  30 + this.participant = WeakReference(participant)
22 } 31 }
23 32
24 fun updateFromInfo(info: LivekitModels.TrackInfo) { 33 fun updateFromInfo(info: LivekitModels.TrackInfo) {
@@ -26,7 +35,6 @@ open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) @@ -26,7 +35,6 @@ open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null)
26 name = info.name 35 name = info.name
27 kind = info.type 36 kind = info.type
28 37
29 - // TODO: forward mute status to listener  
30 muted = info.muted 38 muted = info.muted
31 } 39 }
32 } 40 }
@@ -30,7 +30,7 @@ class ParticipantItem( @@ -30,7 +30,7 @@ class ParticipantItem(
30 remoteParticipant.listener = object : ParticipantListener { 30 remoteParticipant.listener = object : ParticipantListener {
31 override fun onTrackSubscribed( 31 override fun onTrackSubscribed(
32 track: Track, 32 track: Track,
33 - publication: TrackPublication, 33 + publication: RemoteTrackPublication,
34 participant: RemoteParticipant 34 participant: RemoteParticipant
35 ) { 35 ) {
36 if (track is VideoTrack) { 36 if (track is VideoTrack) {