David Zhao

major refactor, simplified TrackPublication hierarchy

正在显示 35 个修改的文件 包含 236 行增加497 行删除
@@ -153,9 +153,9 @@ constructor( @@ -153,9 +153,9 @@ constructor(
153 sendRequest(request) 153 sendRequest(request)
154 } 154 }
155 155
156 - fun sendMuteTrack(trackSid: Track.Sid, muted: Boolean) { 156 + fun sendMuteTrack(trackSid: String, muted: Boolean) {
157 val muteRequest = LivekitRtc.MuteTrackRequest.newBuilder() 157 val muteRequest = LivekitRtc.MuteTrackRequest.newBuilder()
158 - .setSid(trackSid.sid) 158 + .setSid(trackSid)
159 .setMuted(muted) 159 .setMuted(muted)
160 .build() 160 .build()
161 161
@@ -166,9 +166,9 @@ constructor( @@ -166,9 +166,9 @@ constructor(
166 sendRequest(request) 166 sendRequest(request)
167 } 167 }
168 168
169 - fun sendAddTrack(cid: Track.Cid, name: String, type: LivekitModels.TrackType) { 169 + fun sendAddTrack(cid: String, name: String, type: LivekitModels.TrackType) {
170 val addTrackRequest = LivekitRtc.AddTrackRequest.newBuilder() 170 val addTrackRequest = LivekitRtc.AddTrackRequest.newBuilder()
171 - .setCid(cid.cid) 171 + .setCid(cid)
172 .setName(name) 172 .setName(name)
173 .setType(type) 173 .setType(type)
174 .build() 174 .build()
@@ -45,7 +45,7 @@ constructor( @@ -45,7 +45,7 @@ constructor(
45 } 45 }
46 } 46 }
47 val pendingCandidates = mutableListOf<IceCandidate>() 47 val pendingCandidates = mutableListOf<IceCandidate>()
48 - private val pendingTrackResolvers: MutableMap<Track.Cid, Continuation<LivekitModels.TrackInfo>> = 48 + private val pendingTrackResolvers: MutableMap<String, Continuation<LivekitModels.TrackInfo>> =
49 mutableMapOf() 49 mutableMapOf()
50 50
51 private val publisherObserver = PublisherTransportObserver(this) 51 private val publisherObserver = PublisherTransportObserver(this)
@@ -76,7 +76,7 @@ constructor( @@ -76,7 +76,7 @@ constructor(
76 client.join(url, token) 76 client.join(url, token)
77 } 77 }
78 78
79 - suspend fun addTrack(cid: Track.Cid, name: String, kind: LivekitModels.TrackType): LivekitModels.TrackInfo { 79 + suspend fun addTrack(cid: String, name: String, kind: LivekitModels.TrackType): LivekitModels.TrackInfo {
80 if (pendingTrackResolvers[cid] != null) { 80 if (pendingTrackResolvers[cid] != null) {
81 throw TrackException.DuplicateTrackException("Track with same ID $cid has already been published!") 81 throw TrackException.DuplicateTrackException("Track with same ID $cid has already been published!")
82 } 82 }
@@ -87,7 +87,7 @@ constructor( @@ -87,7 +87,7 @@ constructor(
87 } 87 }
88 } 88 }
89 89
90 - fun updateMuteStatus(sid: Track.Sid, muted: Boolean) { 90 + fun updateMuteStatus(sid: String, muted: Boolean) {
91 client.sendMuteTrack(sid, muted) 91 client.sendMuteTrack(sid, muted)
92 } 92 }
93 93
@@ -137,7 +137,7 @@ constructor( @@ -137,7 +137,7 @@ constructor(
137 interface Listener { 137 interface Listener {
138 fun onJoin(response: LivekitRtc.JoinResponse) 138 fun onJoin(response: LivekitRtc.JoinResponse)
139 fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>) 139 fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>)
140 - fun onPublishLocalTrack(cid: Track.Cid, track: LivekitModels.TrackInfo) 140 + fun onPublishLocalTrack(cid: String, track: LivekitModels.TrackInfo)
141 fun onAddDataChannel(channel: DataChannel) 141 fun onAddDataChannel(channel: DataChannel)
142 fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>) 142 fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>)
143 fun onUpdateSpeakers(speakers: List<LivekitRtc.SpeakerInfo>) 143 fun onUpdateSpeakers(speakers: List<LivekitRtc.SpeakerInfo>)
@@ -253,7 +253,7 @@ constructor( @@ -253,7 +253,7 @@ constructor(
253 Timber.e { "local track published with null cid?" } 253 Timber.e { "local track published with null cid?" }
254 return 254 return
255 } 255 }
256 - val cid = Track.Cid(signalCid) 256 + val cid = signalCid
257 257
258 val track = response.track 258 val track = response.track
259 if (track == null) { 259 if (track == null) {
@@ -9,7 +9,6 @@ import io.livekit.android.ConnectOptions @@ -9,7 +9,6 @@ import io.livekit.android.ConnectOptions
9 import io.livekit.android.room.participant.LocalParticipant 9 import io.livekit.android.room.participant.LocalParticipant
10 import io.livekit.android.room.participant.Participant 10 import io.livekit.android.room.participant.Participant
11 import io.livekit.android.room.participant.RemoteParticipant 11 import io.livekit.android.room.participant.RemoteParticipant
12 -import io.livekit.android.room.track.Track  
13 import io.livekit.android.room.util.unpackedTrackLabel 12 import io.livekit.android.room.util.unpackedTrackLabel
14 import livekit.LivekitModels 13 import livekit.LivekitModels
15 import livekit.LivekitRtc 14 import livekit.LivekitRtc
@@ -24,7 +23,7 @@ constructor( @@ -24,7 +23,7 @@ constructor(
24 @Assisted private val connectOptions: ConnectOptions, 23 @Assisted private val connectOptions: ConnectOptions,
25 private val engine: RTCEngine, 24 private val engine: RTCEngine,
26 private val eglBase: EglBase, 25 private val eglBase: EglBase,
27 -) : RTCEngine.Listener, RemoteParticipant.Listener, LocalParticipant.Listener { 26 +) : RTCEngine.Listener, RemoteParticipant.Listener {
28 init { 27 init {
29 engine.listener = this 28 engine.listener = this
30 } 29 }
@@ -48,8 +47,8 @@ constructor( @@ -48,8 +47,8 @@ constructor(
48 private set 47 private set
49 var localParticipant: LocalParticipant? = null 48 var localParticipant: LocalParticipant? = null
50 private set 49 private set
51 - private val mutableRemoteParticipants = mutableMapOf<Participant.Sid, RemoteParticipant>()  
52 - val remoteParticipants: Map<Participant.Sid, RemoteParticipant> 50 + private val mutableRemoteParticipants = mutableMapOf<String, RemoteParticipant>()
  51 + val remoteParticipants: Map<String, RemoteParticipant>
53 get() = mutableRemoteParticipants 52 get() = mutableRemoteParticipants
54 53
55 private val mutableActiveSpeakers = mutableListOf<Participant>() 54 private val mutableActiveSpeakers = mutableListOf<Participant>()
@@ -73,17 +72,17 @@ constructor( @@ -73,17 +72,17 @@ constructor(
73 listener?.onDisconnect(this, null) 72 listener?.onDisconnect(this, null)
74 } 73 }
75 74
76 - private fun handleParticipantDisconnect(sid: Participant.Sid, participant: RemoteParticipant) { 75 + private fun handleParticipantDisconnect(sid: String, participant: RemoteParticipant) {
77 val removedParticipant = mutableRemoteParticipants.remove(sid) ?: return 76 val removedParticipant = mutableRemoteParticipants.remove(sid) ?: return
78 removedParticipant.tracks.values.forEach { publication -> 77 removedParticipant.tracks.values.forEach { publication ->
79 - removedParticipant.unpublishTrack(publication.trackSid) 78 + removedParticipant.unpublishTrack(publication.sid)
80 } 79 }
81 80
82 listener?.onParticipantDisconnected(this, removedParticipant) 81 listener?.onParticipantDisconnected(this, removedParticipant)
83 } 82 }
84 83
85 private fun getOrCreateRemoteParticipant( 84 private fun getOrCreateRemoteParticipant(
86 - sid: Participant.Sid, 85 + sid: String,
87 info: LivekitModels.ParticipantInfo? = null 86 info: LivekitModels.ParticipantInfo? = null
88 ): RemoteParticipant { 87 ): RemoteParticipant {
89 var participant = remoteParticipants[sid] 88 var participant = remoteParticipants[sid]
@@ -103,10 +102,10 @@ constructor( @@ -103,10 +102,10 @@ constructor(
103 102
104 private fun handleSpeakerUpdate(speakerInfos: List<LivekitRtc.SpeakerInfo>) { 103 private fun handleSpeakerUpdate(speakerInfos: List<LivekitRtc.SpeakerInfo>) {
105 val speakers = mutableListOf<Participant>() 104 val speakers = mutableListOf<Participant>()
106 - val seenSids = mutableSetOf<Participant.Sid>() 105 + val seenSids = mutableSetOf<String>()
107 val localParticipant = localParticipant 106 val localParticipant = localParticipant
108 speakerInfos.forEach { speakerInfo -> 107 speakerInfos.forEach { speakerInfo ->
109 - val speakerSid = Participant.Sid(speakerInfo.sid) 108 + val speakerSid = speakerInfo.sid!!
110 seenSids.add(speakerSid) 109 seenSids.add(speakerSid)
111 110
112 if (speakerSid == localParticipant?.sid) { 111 if (speakerSid == localParticipant?.sid) {
@@ -185,7 +184,7 @@ constructor( @@ -185,7 +184,7 @@ constructor(
185 } 184 }
186 if (response.otherParticipantsList.isNotEmpty()) { 185 if (response.otherParticipantsList.isNotEmpty()) {
187 response.otherParticipantsList.forEach { 186 response.otherParticipantsList.forEach {
188 - getOrCreateRemoteParticipant(Participant.Sid(it.sid), it) 187 + getOrCreateRemoteParticipant(it.sid, it)
189 } 188 }
190 } 189 }
191 190
@@ -203,8 +202,8 @@ constructor( @@ -203,8 +202,8 @@ constructor(
203 return 202 return
204 } 203 }
205 204
206 - val participantSid = Participant.Sid(streams.first().id)  
207 - val trackSid = Track.Sid(track.id()) 205 + val participantSid = streams.first().id
  206 + val trackSid = track.id()
208 val participant = getOrCreateRemoteParticipant(participantSid) 207 val participant = getOrCreateRemoteParticipant(participantSid)
209 participant.addSubscribedMediaTrack(track, trackSid) 208 participant.addSubscribedMediaTrack(track, trackSid)
210 } 209 }
@@ -222,7 +221,7 @@ constructor( @@ -222,7 +221,7 @@ constructor(
222 /** 221 /**
223 * @suppress 222 * @suppress
224 */ 223 */
225 - override fun onPublishLocalTrack(cid: Track.Cid, track: LivekitModels.TrackInfo) { 224 + override fun onPublishLocalTrack(cid: String, track: LivekitModels.TrackInfo) {
226 } 225 }
227 226
228 /** 227 /**
@@ -230,7 +229,7 @@ constructor( @@ -230,7 +229,7 @@ constructor(
230 */ 229 */
231 override fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>) { 230 override fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>) {
232 for (info in updates) { 231 for (info in updates) {
233 - val participantSid = Participant.Sid(info.sid) 232 + val participantSid = info.sid
234 233
235 if(localParticipant?.sid == participantSid) { 234 if(localParticipant?.sid == participantSid) {
236 localParticipant?.updateFromInfo(info) 235 localParticipant?.updateFromInfo(info)
@@ -8,28 +8,16 @@ import org.webrtc.DataChannel @@ -8,28 +8,16 @@ import org.webrtc.DataChannel
8 import org.webrtc.RtpTransceiver 8 import org.webrtc.RtpTransceiver
9 import java.util.* 9 import java.util.*
10 10
11 -class LocalParticipant(sid: Sid, name: String? = null) :  
12 - Participant(sid, name) {  
13 -  
14 - /**  
15 - * @suppress  
16 - */  
17 - constructor(info: LivekitModels.ParticipantInfo, engine: RTCEngine) : this(  
18 - Sid(info.sid),  
19 - info.identity  
20 - ) {  
21 - metadata = info.metadata  
22 - this.engine = engine 11 +class LocalParticipant(info: LivekitModels.ParticipantInfo, private val engine: RTCEngine) :
  12 + Participant(info.sid, info.identity) {
  13 +
  14 + init {
  15 + updateFromInfo(info)
23 } 16 }
24 17
25 - val localAudioTrackPublications  
26 - get() = audioTracks.values.toList()  
27 - val localVideoTrackPublications  
28 - get() = videoTracks.values.toList()  
29 - val localDataTrackPublications  
30 - get() = dataTracks.values.toList() 18 + val localTrackPublications
  19 + get() = tracks.values.toList()
31 20
32 - private var engine: RTCEngine? = null  
33 var listener: Listener? = null 21 var listener: Listener? = null
34 set(v) { 22 set(v) {
35 field = v 23 field = v
@@ -38,98 +26,77 @@ class LocalParticipant(sid: Sid, name: String? = null) : @@ -38,98 +26,77 @@ class LocalParticipant(sid: Sid, name: String? = null) :
38 26
39 suspend fun publishAudioTrack( 27 suspend fun publishAudioTrack(
40 track: LocalAudioTrack, 28 track: LocalAudioTrack,
41 - options: LocalTrackPublicationOptions? = null 29 + publishListener: PublishListener? = null
42 ) { 30 ) {
43 - if (localAudioTrackPublications.any { it.track == track }) {  
44 - listener?.onFailToPublishAudioTrack(TrackException.PublishException("Track has already been published")) 31 + if (localTrackPublications.any { it.track == track }) {
  32 + publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
45 return 33 return
46 } 34 }
47 35
48 val cid = track.rtcTrack.id() 36 val cid = track.rtcTrack.id()
49 - val engine = this.engine ?: run {  
50 - listener?.onFailToPublishAudioTrack(IllegalStateException("engine is null!"))  
51 - return  
52 - }  
53 -  
54 val trackInfo = 37 val trackInfo =
55 - engine.addTrack(cid = Track.Cid(cid), name = track.name, kind = LivekitModels.TrackType.AUDIO) 38 + engine.addTrack(cid = cid, name = track.name, kind = track.kind)
56 val transInit = RtpTransceiver.RtpTransceiverInit( 39 val transInit = RtpTransceiver.RtpTransceiverInit(
57 RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, 40 RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
58 - listOf(this.sid.sid) 41 + listOf(this.sid)
59 ) 42 )
  43 + // TODO: sendEncodings to customize
60 val transceiver = 44 val transceiver =
61 engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) 45 engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
62 46
63 if (transceiver == null) { 47 if (transceiver == null) {
64 - listener?.onFailToPublishAudioTrack(TrackException.PublishException("null sender returned from peer connection")) 48 + publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection"))
65 return 49 return
66 } 50 }
67 51
68 - val publication = LocalAudioTrackPublication(trackInfo)  
69 - val trackSid = Track.Sid(trackInfo.sid)  
70 - track.sid = trackSid  
71 - audioTracks[trackSid] = publication  
72 - listener?.onPublishAudioTrack(track) 52 + val publication = TrackPublication(trackInfo, track)
  53 + addTrackPublication(publication)
  54 + publishListener?.onPublishSuccess(publication)
73 } 55 }
74 56
75 suspend fun publishVideoTrack( 57 suspend fun publishVideoTrack(
76 track: LocalVideoTrack, 58 track: LocalVideoTrack,
77 - options: LocalTrackPublicationOptions? = null 59 + publishListener: PublishListener? = null
78 ) { 60 ) {
79 -  
80 - if (localVideoTrackPublications.any { it.track == track }) {  
81 - listener?.onFailToPublishVideoTrack(TrackException.PublishException("Track has already been published")) 61 + if (localTrackPublications.any { it.track == track }) {
  62 + publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
82 return 63 return
83 } 64 }
84 65
85 val cid = track.rtcTrack.id() 66 val cid = track.rtcTrack.id()
86 - val engine = this.engine ?: run {  
87 - listener?.onFailToPublishVideoTrack(IllegalStateException("engine is null!"))  
88 - return  
89 - }  
90 -  
91 val trackInfo = 67 val trackInfo =
92 - engine.addTrack(cid = Track.Cid(cid), name = track.name, kind = LivekitModels.TrackType.VIDEO) 68 + engine.addTrack(cid = cid, name = track.name, kind = LivekitModels.TrackType.VIDEO)
93 val transInit = RtpTransceiver.RtpTransceiverInit( 69 val transInit = RtpTransceiver.RtpTransceiverInit(
94 RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, 70 RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
95 - listOf(this.sid.sid) 71 + listOf(this.sid)
96 ) 72 )
  73 + // TODO: video encodings & simulcast
97 val transceiver = 74 val transceiver =
98 engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) 75 engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
99 76
100 if (transceiver == null) { 77 if (transceiver == null) {
101 - listener?.onFailToPublishVideoTrack(TrackException.PublishException("null sender returned from peer connection")) 78 + publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection"))
102 return 79 return
103 } 80 }
104 81
105 - val publication = LocalVideoTrackPublication(trackInfo)  
106 - val trackSid = Track.Sid(trackInfo.sid)  
107 - track.sid = trackSid  
108 - videoTracks[trackSid] = publication  
109 - listener?.onPublishVideoTrack(track) 82 + val publication = TrackPublication(trackInfo, track)
  83 + addTrackPublication(publication)
  84 + publishListener?.onPublishSuccess(publication)
110 } 85 }
111 86
112 suspend fun publishDataTrack( 87 suspend fun publishDataTrack(
113 track: LocalDataTrack, 88 track: LocalDataTrack,
114 - options: LocalTrackPublicationOptions? = null 89 + publishListener: PublishListener? = null
115 ) { 90 ) {
116 -  
117 - if (localDataTrackPublications.any { it.track == track }) {  
118 - listener?.onFailToPublishDataTrack(TrackException.PublishException("Track has already been published")) 91 + if (localTrackPublications.any { it.track == track }) {
  92 + publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
119 return 93 return
120 } 94 }
121 95
122 val cid = track.cid 96 val cid = track.cid
123 - val engine = this.engine ?: run {  
124 - listener?.onFailToPublishDataTrack(IllegalStateException("engine is null!"))  
125 - return  
126 - }  
127 -  
128 val trackInfo = 97 val trackInfo =
129 - engine.addTrack(cid = cid, name = track.name, kind = LivekitModels.TrackType.DATA)  
130 - val publication = LocalDataTrackPublication(trackInfo, track)  
131 - val trackSid = Track.Sid(trackInfo.sid)  
132 - track.sid = trackSid 98 + engine.addTrack(cid = cid, name = track.name, track.kind)
  99 + val publication = TrackPublication(trackInfo, track)
133 100
134 val config = DataChannel.Init().apply { 101 val config = DataChannel.Init().apply {
135 ordered = track.options.ordered 102 ordered = track.options.ordered
@@ -138,76 +105,51 @@ class LocalParticipant(sid: Sid, name: String? = null) : @@ -138,76 +105,51 @@ class LocalParticipant(sid: Sid, name: String? = null) :
138 } 105 }
139 106
140 val dataChannel = engine.publisher.peerConnection.createDataChannel(track.name, config) 107 val dataChannel = engine.publisher.peerConnection.createDataChannel(track.name, config)
141 - if (dataChannel != null) {  
142 - track.rtcTrack = dataChannel  
143 - track.updateConfig(config)  
144 - dataTracks[trackSid] = publication  
145 - listener?.onPublishDataTrack(track)  
146 - } else {  
147 - Timber.d { "error creating data channel with name: $name" }  
148 - unpublishDataTrack(track)  
149 - }  
150 - }  
151 -  
152 - fun unpublishAudioTrack(track: LocalAudioTrack) {  
153 - val sid = track.sid ?: run {  
154 - Timber.d { "this track was never published." } 108 + if (dataChannel == null) {
  109 + publishListener?.onPublishFailure(TrackException.PublishException("could not create data channel"))
155 return 110 return
156 } 111 }
157 - unpublishMediaTrack(track, sid, audioTracks)  
158 - } 112 + track.dataChannel = dataChannel
  113 + track.updateConfig(config)
  114 + addTrackPublication(publication)
159 115
160 - fun unpublishVideoTrack(track: LocalVideoTrack) {  
161 - val sid = track.sid ?: run {  
162 - Timber.d { "this track was never published." }  
163 - return  
164 - }  
165 - unpublishMediaTrack(track, sid, audioTracks) 116 + publishListener?.onPublishSuccess(publication)
166 } 117 }
167 118
168 - fun unpublishDataTrack(track: LocalDataTrack) {  
169 - val sid = track.sid ?: run { 119 + fun unpublishTrack(track: Track) {
  120 + val publication = localTrackPublications.firstOrNull { it.track == track }
  121 + if (publication === null) {
170 Timber.d { "this track was never published." } 122 Timber.d { "this track was never published." }
171 return 123 return
172 } 124 }
173 -  
174 - val publication = dataTracks.remove(sid) as? LocalDataTrackPublication  
175 - if (publication == null) {  
176 - Timber.d { "track was not published with sid: $sid" }  
177 - return 125 + track.stop()
  126 + if (track is MediaTrack) {
  127 + unpublishMediaTrack(track, sid)
  128 + }
  129 + val sid = publication.sid
  130 + tracks.remove(sid)
  131 + when (publication.kind) {
  132 + LivekitModels.TrackType.AUDIO -> audioTracks.remove(sid)
  133 + LivekitModels.TrackType.VIDEO -> videoTracks.remove(sid)
  134 + LivekitModels.TrackType.DATA -> dataTracks.remove(sid)
178 } 135 }
179 - publication.dataTrack?.rtcTrack?.dispose()  
180 } 136 }
181 137
182 private fun <T> unpublishMediaTrack( 138 private fun <T> unpublishMediaTrack(
183 track: T, 139 track: T,
184 - sid: Track.Sid,  
185 - publications: MutableMap<Track.Sid, TrackPublication>  
186 - ) where T : Track, T : MediaTrack {  
187 - val removed = publications.remove(sid)  
188 - if (removed != null) {  
189 - Timber.d { "track was not published with sid: $sid" }  
190 - return  
191 - }  
192 -  
193 - track.mediaTrack.setEnabled(false) 140 + sid: String
  141 + ) where T : MediaTrack {
194 val senders = engine?.publisher?.peerConnection?.senders ?: return 142 val senders = engine?.publisher?.peerConnection?.senders ?: return
195 for (sender in senders) { 143 for (sender in senders) {
196 val t = sender.track() ?: continue 144 val t = sender.track() ?: continue
197 - if (t == track.mediaTrack) { 145 + if (t == track.rtcTrack) {
198 engine?.publisher?.peerConnection?.removeTrack(sender) 146 engine?.publisher?.peerConnection?.removeTrack(sender)
199 } 147 }
200 } 148 }
201 } 149 }
202 150
203 - interface Listener : Participant.Listener {  
204 - // TODO: can we move these to exceptions? instead of callbacks  
205 - fun onPublishAudioTrack(track: LocalAudioTrack) {}  
206 - fun onFailToPublishAudioTrack(exception: Exception) {}  
207 - fun onPublishVideoTrack(track: LocalVideoTrack) {}  
208 - fun onFailToPublishVideoTrack(exception: Exception) {}  
209 - fun onPublishDataTrack(track: LocalDataTrack) {}  
210 - fun onFailToPublishDataTrack(exception: Exception) {}  
211 - //fun onNetworkQualityLevelChange 151 + interface PublishListener {
  152 + fun onPublishSuccess(publication: TrackPublication) {}
  153 + fun onPublishFailure(exception: Exception) {}
212 } 154 }
213 } 155 }
@@ -3,12 +3,10 @@ package io.livekit.android.room.participant @@ -3,12 +3,10 @@ package io.livekit.android.room.participant
3 import io.livekit.android.room.track.* 3 import io.livekit.android.room.track.*
4 import livekit.LivekitModels 4 import livekit.LivekitModels
5 5
6 -open class Participant(var sid: Sid, name: String? = null) {  
7 - inline class Sid(val sid: String)  
8 - 6 +open class Participant(var sid: String, identity: String? = null) {
9 var participantInfo: LivekitModels.ParticipantInfo? = null 7 var participantInfo: LivekitModels.ParticipantInfo? = null
10 private set 8 private set
11 - var name: String? = name 9 + var identity: String? = identity
12 internal set 10 internal set
13 var audioLevel: Float = 0f 11 var audioLevel: Float = 0f
14 internal set 12 internal set
@@ -17,23 +15,23 @@ open class Participant(var sid: Sid, name: String? = null) { @@ -17,23 +15,23 @@ open class Participant(var sid: Sid, name: String? = null) {
17 val hasInfo 15 val hasInfo
18 get() = participantInfo != null 16 get() = participantInfo != null
19 17
20 - var tracks = mutableMapOf<Track.Sid, TrackPublication>()  
21 - var audioTracks = mutableMapOf<Track.Sid, TrackPublication>() 18 + var tracks = mutableMapOf<String, TrackPublication>()
  19 + var audioTracks = mutableMapOf<String, TrackPublication>()
22 private set 20 private set
23 - var videoTracks = mutableMapOf<Track.Sid, TrackPublication>() 21 + var videoTracks = mutableMapOf<String, TrackPublication>()
24 private set 22 private set
25 - var dataTracks = mutableMapOf<Track.Sid, TrackPublication>() 23 + var dataTracks = mutableMapOf<String, TrackPublication>()
26 private set 24 private set
27 25
28 /** 26 /**
29 * @suppress 27 * @suppress
30 */ 28 */
31 - fun addTrack(publication: TrackPublication) {  
32 - tracks[publication.trackSid] = publication  
33 - when (publication) {  
34 - is RemoteAudioTrackPublication -> audioTracks[publication.trackSid] = publication  
35 - is RemoteVideoTrackPublication -> videoTracks[publication.trackSid] = publication  
36 - is RemoteDataTrackPublication -> dataTracks[publication.trackSid] = publication 29 + fun addTrackPublication(publication: TrackPublication) {
  30 + tracks[publication.sid] = publication
  31 + when (publication.kind) {
  32 + LivekitModels.TrackType.AUDIO -> audioTracks[publication.sid] = publication
  33 + LivekitModels.TrackType.VIDEO -> videoTracks[publication.sid] = publication
  34 + LivekitModels.TrackType.DATA -> dataTracks[publication.sid] = publication
37 } 35 }
38 } 36 }
39 37
@@ -41,8 +39,8 @@ open class Participant(var sid: Sid, name: String? = null) { @@ -41,8 +39,8 @@ open class Participant(var sid: Sid, name: String? = null) {
41 * @suppress 39 * @suppress
42 */ 40 */
43 open fun updateFromInfo(info: LivekitModels.ParticipantInfo) { 41 open fun updateFromInfo(info: LivekitModels.ParticipantInfo) {
44 - sid = Sid(info.sid)  
45 - name = info.identity 42 + sid = info.sid
  43 + identity = info.identity
46 participantInfo = info 44 participantInfo = info
47 45
48 val prevMetadata = metadata 46 val prevMetadata = metadata
@@ -14,22 +14,15 @@ import org.webrtc.VideoTrack @@ -14,22 +14,15 @@ import org.webrtc.VideoTrack
14 import java.nio.ByteBuffer 14 import java.nio.ByteBuffer
15 15
16 class RemoteParticipant( 16 class RemoteParticipant(
17 - sid: Sid, name: String? = null 17 + sid: String, name: String? = null
18 ) : Participant(sid, name), RemoteDataTrack.Listener { 18 ) : Participant(sid, name), RemoteDataTrack.Listener {
19 /** 19 /**
20 * @suppress 20 * @suppress
21 */ 21 */
22 - constructor(info: LivekitModels.ParticipantInfo) : this(Sid(info.sid), info.identity) { 22 + constructor(info: LivekitModels.ParticipantInfo) : this(info.sid, info.identity) {
23 updateFromInfo(info) 23 updateFromInfo(info)
24 } 24 }
25 25
26 - val remoteAudioTracks  
27 - get() = audioTracks.values.toList()  
28 - val remoteVideoTracks  
29 - get() = videoTracks.values.toList()  
30 - val remoteDataTracks  
31 - get() = dataTracks.values.toList()  
32 -  
33 var listener: Listener? = null 26 var listener: Listener? = null
34 set(v) { 27 set(v) {
35 field = v 28 field = v
@@ -38,8 +31,8 @@ class RemoteParticipant( @@ -38,8 +31,8 @@ class RemoteParticipant(
38 31
39 private val coroutineScope = CloseableCoroutineScope(SupervisorJob()) 32 private val coroutineScope = CloseableCoroutineScope(SupervisorJob())
40 33
41 - fun getTrackPublication(sid: Track.Sid): RemoteTrackPublication? =  
42 - tracks[sid] as? RemoteTrackPublication 34 + fun getTrackPublication(sid: String): TrackPublication? =
  35 + tracks[sid]
43 36
44 /** 37 /**
45 * @suppress 38 * @suppress
@@ -48,24 +41,18 @@ class RemoteParticipant( @@ -48,24 +41,18 @@ class RemoteParticipant(
48 val hadInfo = hasInfo 41 val hadInfo = hasInfo
49 super.updateFromInfo(info) 42 super.updateFromInfo(info)
50 43
51 - val validTrackPublication = mutableMapOf<Track.Sid, RemoteTrackPublication>()  
52 - val newTrackPublications = mutableMapOf<Track.Sid, RemoteTrackPublication>() 44 + val validTrackPublication = mutableMapOf<String, TrackPublication>()
  45 + val newTrackPublications = mutableMapOf<String, TrackPublication>()
53 46
54 for (trackInfo in info.tracksList) { 47 for (trackInfo in info.tracksList) {
55 - val trackSid = Track.Sid(trackInfo.sid) 48 + val trackSid = trackInfo.sid
56 var publication = getTrackPublication(trackSid) 49 var publication = getTrackPublication(trackSid)
57 50
58 if (publication == null) { 51 if (publication == null) {
59 - publication = when (trackInfo.type) {  
60 - LivekitModels.TrackType.AUDIO -> RemoteAudioTrackPublication(trackInfo)  
61 - LivekitModels.TrackType.VIDEO -> RemoteVideoTrackPublication(trackInfo)  
62 - LivekitModels.TrackType.DATA -> RemoteDataTrackPublication(trackInfo)  
63 - LivekitModels.TrackType.UNRECOGNIZED -> throw TrackException.InvalidTrackTypeException()  
64 - null -> throw NullPointerException("trackInfo.type is null")  
65 - } 52 + publication = TrackPublication(trackInfo)
66 53
67 newTrackPublications[trackSid] = publication 54 newTrackPublications[trackSid] = publication
68 - addTrack(publication) 55 + addTrackPublication(publication)
69 } else { 56 } else {
70 publication.updateFromInfo(trackInfo) 57 publication.updateFromInfo(trackInfo)
71 } 58 }
@@ -75,25 +62,25 @@ class RemoteParticipant( @@ -75,25 +62,25 @@ class RemoteParticipant(
75 62
76 if (hadInfo) { 63 if (hadInfo) {
77 for (publication in newTrackPublications.values) { 64 for (publication in newTrackPublications.values) {
78 - sendTrackPublishedEvent(publication) 65 + listener?.onPublish(publication, this)
79 } 66 }
80 } 67 }
81 68
82 val invalidKeys = tracks.keys - validTrackPublication.keys 69 val invalidKeys = tracks.keys - validTrackPublication.keys
83 for (invalidKey in invalidKeys) { 70 for (invalidKey in invalidKeys) {
84 val publication = tracks[invalidKey] ?: continue 71 val publication = tracks[invalidKey] ?: continue
85 - unpublishTrack(publication.trackSid, true) 72 + unpublishTrack(publication.sid, true)
86 } 73 }
87 } 74 }
88 75
89 /** 76 /**
90 * @suppress 77 * @suppress
91 */ 78 */
92 - fun addSubscribedMediaTrack(rtcTrack: MediaStreamTrack, sid: Track.Sid, triesLeft: Int = 20) { 79 + fun addSubscribedMediaTrack(mediaTrack: MediaStreamTrack, sid: String, triesLeft: Int = 20) {
93 val publication = getTrackPublication(sid) 80 val publication = getTrackPublication(sid)
94 - val track: Track = when (val kind = rtcTrack.kind()) {  
95 - KIND_AUDIO -> RemoteAudioTrack(sid = sid, rtcTrack = rtcTrack as AudioTrack, name = "")  
96 - KIND_VIDEO -> RemoteVideoTrack(sid = sid, rtcTrack = rtcTrack as VideoTrack, name = "") 81 + val track: Track = when (val kind = mediaTrack.kind()) {
  82 + KIND_AUDIO -> RemoteAudioTrack(sid = sid, mediaTrack = mediaTrack as AudioTrack, name = "")
  83 + KIND_VIDEO -> RemoteVideoTrack(sid = sid, mediaTrack = mediaTrack as VideoTrack, name = "")
97 else -> throw TrackException.InvalidTrackTypeException("invalid track type: $kind") 84 else -> throw TrackException.InvalidTrackTypeException("invalid track type: $kind")
98 } 85 }
99 86
@@ -102,27 +89,12 @@ class RemoteParticipant( @@ -102,27 +89,12 @@ class RemoteParticipant(
102 val message = "Could not find published track with sid: $sid" 89 val message = "Could not find published track with sid: $sid"
103 val exception = TrackException.InvalidTrackStateException(message) 90 val exception = TrackException.InvalidTrackStateException(message)
104 Timber.e { "remote participant ${this.sid} --- $message" } 91 Timber.e { "remote participant ${this.sid} --- $message" }
105 - when (rtcTrack.kind()) {  
106 - KIND_AUDIO -> {  
107 - listener?.onFailToSubscribe(  
108 - audioTrack = track as RemoteAudioTrack,  
109 - exception = exception,  
110 - participant = this  
111 - )  
112 - }  
113 -  
114 - KIND_VIDEO -> {  
115 - listener?.onFailToSubscribe(  
116 - videoTrack = track as RemoteVideoTrack,  
117 - exception = exception,  
118 - participant = this  
119 - )  
120 - }  
121 - } 92 +
  93 + listener?.onFailToSubscribe(sid, exception, this)
122 } else { 94 } else {
123 coroutineScope.launch { 95 coroutineScope.launch {
124 delay(150) 96 delay(150)
125 - addSubscribedMediaTrack(rtcTrack, sid, triesLeft - 1) 97 + addSubscribedMediaTrack(mediaTrack, sid, triesLeft - 1)
126 } 98 }
127 } 99 }
128 return 100 return
@@ -130,103 +102,70 @@ class RemoteParticipant( @@ -130,103 +102,70 @@ class RemoteParticipant(
130 102
131 val remoteTrack = track as RemoteTrack 103 val remoteTrack = track as RemoteTrack
132 publication.track = track 104 publication.track = track
133 - track.name = publication.trackName  
134 - remoteTrack.sid = publication.trackSid 105 + track.name = publication.name
  106 + remoteTrack.sid = publication.sid
135 107
136 - when (publication) {  
137 - is RemoteAudioTrackPublication -> listener?.onSubscribe(publication, this)  
138 - is RemoteVideoTrackPublication -> listener?.onSubscribe(publication, this)  
139 - else -> throw TrackException.InvalidTrackTypeException()  
140 - } 108 + // TODO: how does mediatrack send ended event?
  109 +
  110 + listener?.onSubscribe(track, publication, this)
141 } 111 }
142 112
143 /** 113 /**
144 * @suppress 114 * @suppress
145 */ 115 */
146 - fun addSubscribedDataTrack(rtcTrack: DataChannel, sid: Track.Sid, name: String) {  
147 - val track = RemoteDataTrack(sid, name, rtcTrack)  
148 - var publication = getTrackPublication(sid) as? RemoteDataTrackPublication 116 + fun addSubscribedDataTrack(dataChannel: DataChannel, sid: String, name: String) {
  117 + val track = DataTrack(name, dataChannel)
  118 + var publication = getTrackPublication(sid)
149 119
150 if (publication != null) { 120 if (publication != null) {
151 publication.track = track 121 publication.track = track
152 } else { 122 } else {
153 val trackInfo = LivekitModels.TrackInfo.newBuilder() 123 val trackInfo = LivekitModels.TrackInfo.newBuilder()
154 - .setSid(sid.sid) 124 + .setSid(sid)
155 .setName(name) 125 .setName(name)
156 .setType(LivekitModels.TrackType.DATA) 126 .setType(LivekitModels.TrackType.DATA)
157 .build() 127 .build()
158 - publication = RemoteDataTrackPublication(info = trackInfo, track = track)  
159 - addTrack(publication) 128 + publication = TrackPublication(info = trackInfo, track = track)
  129 + addTrackPublication(publication)
160 if (hasInfo) { 130 if (hasInfo) {
161 - sendTrackPublishedEvent(publication) 131 + listener?.onPublish(publication, this)
162 } 132 }
163 } 133 }
164 134
165 -  
166 - rtcTrack.registerObserver(object : DataChannel.Observer { 135 + dataChannel.registerObserver(object : DataChannel.Observer {
167 override fun onBufferedAmountChange(previousAmount: Long) {} 136 override fun onBufferedAmountChange(previousAmount: Long) {}
168 137
169 override fun onStateChange() { 138 override fun onStateChange() {
170 - val newState = rtcTrack.state() 139 + val newState = dataChannel.state()
171 if (newState == DataChannel.State.CLOSED) { 140 if (newState == DataChannel.State.CLOSED) {
172 - listener?.onUnsubscribe(publication, this@RemoteParticipant) 141 + publication.track = null
  142 + listener?.onUnsubscribe(track, publication, this@RemoteParticipant)
173 } 143 }
174 } 144 }
175 145
176 override fun onMessage(buffer: DataChannel.Buffer) { 146 override fun onMessage(buffer: DataChannel.Buffer) {
177 - listener?.onReceive(buffer.data, publication, this@RemoteParticipant) 147 + listener?.onReceive(buffer.data, track, this@RemoteParticipant)
178 } 148 }
179 }) 149 })
180 - listener?.onSubscribe(dataTrack = publication, participant = this) 150 + listener?.onSubscribe(track, publication, participant = this)
181 } 151 }
182 152
183 - fun unpublishTrack(trackSid: Track.Sid, sendUnpublish: Boolean = false) { 153 + fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) {
184 val publication = tracks.remove(trackSid) ?: return 154 val publication = tracks.remove(trackSid) ?: return
185 -  
186 - when (publication) {  
187 - is RemoteAudioTrackPublication -> audioTracks.remove(trackSid)  
188 - is RemoteVideoTrackPublication -> videoTracks.remove(trackSid)  
189 - is RemoteDataTrackPublication -> {  
190 - dataTracks.remove(trackSid)  
191 - publication.dataTrack?.rtcTrack?.unregisterObserver()  
192 - } 155 + when (publication.kind) {
  156 + LivekitModels.TrackType.AUDIO -> audioTracks.remove(trackSid)
  157 + LivekitModels.TrackType.VIDEO -> videoTracks.remove(trackSid)
  158 + LivekitModels.TrackType.DATA -> dataTracks.remove(trackSid)
193 else -> throw TrackException.InvalidTrackTypeException() 159 else -> throw TrackException.InvalidTrackTypeException()
194 } 160 }
195 161
196 - if (publication.track != null) {  
197 - // TODO: need to stop track?  
198 - publication.track  
199 - sendTrackUnsubscribedEvent(publication) 162 + val track = publication.track
  163 + if (track != null) {
  164 + track.stop()
  165 + listener?.onUnsubscribe(track, publication, this)
200 } 166 }
201 if (sendUnpublish) { 167 if (sendUnpublish) {
202 - sendTrackUnpublishedEvent(publication)  
203 - }  
204 - }  
205 -  
206 - private fun sendTrackUnsubscribedEvent(publication: TrackPublication) {  
207 - when (publication) {  
208 - is RemoteAudioTrackPublication -> listener?.onUnsubscribe(publication, this)  
209 - is RemoteVideoTrackPublication -> listener?.onUnsubscribe(publication, this)  
210 - is RemoteDataTrackPublication -> listener?.onUnsubscribe(publication, this)  
211 - else -> throw TrackException.InvalidTrackTypeException()  
212 - }  
213 - }  
214 -  
215 - private fun sendTrackUnpublishedEvent(publication: TrackPublication) {  
216 - when (publication) {  
217 - is RemoteAudioTrackPublication -> listener?.onUnpublish(publication, this)  
218 - is RemoteVideoTrackPublication -> listener?.onUnpublish(publication, this)  
219 - is RemoteDataTrackPublication -> listener?.onUnpublish(publication, this)  
220 - else -> throw TrackException.InvalidTrackTypeException()  
221 - }  
222 - }  
223 -  
224 - private fun sendTrackPublishedEvent(publication: RemoteTrackPublication) {  
225 - when (publication) {  
226 - is RemoteAudioTrackPublication -> listener?.onPublish(publication, this)  
227 - is RemoteVideoTrackPublication -> listener?.onPublish(publication, this)  
228 - is RemoteDataTrackPublication -> listener?.onPublish(publication, this)  
229 - else -> throw TrackException.InvalidTrackTypeException() 168 + listener?.onUnpublish(publication, this)
230 } 169 }
231 } 170 }
232 171
@@ -244,68 +183,36 @@ class RemoteParticipant( @@ -244,68 +183,36 @@ class RemoteParticipant(
244 } 183 }
245 184
246 interface Listener: Participant.Listener { 185 interface Listener: Participant.Listener {
247 - fun onPublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}  
248 - fun onUnpublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}  
249 - fun onPublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}  
250 - fun onUnpublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}  
251 - fun onPublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}  
252 - fun onUnpublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}  
253 -  
254 - fun onEnable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}  
255 - fun onDisable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}  
256 - fun onEnable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}  
257 - fun onDisable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}  
258 -  
259 - fun onSubscribe(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}  
260 - fun onFailToSubscribe(  
261 - audioTrack: RemoteAudioTrack,  
262 - exception: Exception,  
263 - participant: RemoteParticipant  
264 - ) {  
265 - } 186 + fun onPublish(publication: TrackPublication, participant: RemoteParticipant) {}
  187 + fun onUnpublish(publication: TrackPublication, participant: RemoteParticipant) {}
266 188
267 - fun onUnsubscribe(  
268 - audioTrack: RemoteAudioTrackPublication,  
269 - participant: RemoteParticipant  
270 - ) {  
271 - } 189 + fun onEnable(publication: TrackPublication, participant: RemoteParticipant) {}
  190 + fun onDisable(publication: TrackPublication, participant: RemoteParticipant) {}
272 191
273 - fun onSubscribe(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {} 192 + fun onSubscribe(track: Track, publication: TrackPublication, participant: RemoteParticipant) {}
274 fun onFailToSubscribe( 193 fun onFailToSubscribe(
275 - videoTrack: RemoteVideoTrack, 194 + sid: String,
276 exception: Exception, 195 exception: Exception,
277 participant: RemoteParticipant 196 participant: RemoteParticipant
278 ) { 197 ) {
279 } 198 }
280 199
281 fun onUnsubscribe( 200 fun onUnsubscribe(
282 - videoTrack: RemoteVideoTrackPublication,  
283 - participant: RemoteParticipant  
284 - ) {  
285 - }  
286 -  
287 - fun onSubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}  
288 - fun onFailToSubscribe(  
289 - dataTrack: RemoteDataTrackPublication,  
290 - exception: Exception, 201 + track: Track,
  202 + publications: TrackPublication,
291 participant: RemoteParticipant 203 participant: RemoteParticipant
292 ) { 204 ) {
293 } 205 }
294 206
295 - fun onUnsubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}  
296 fun onReceive( 207 fun onReceive(
297 data: ByteBuffer, 208 data: ByteBuffer,
298 - dataTrack: RemoteDataTrackPublication, 209 + dataTrack: DataTrack,
299 participant: RemoteParticipant 210 participant: RemoteParticipant
300 ) { 211 ) {
301 } 212 }
302 213
303 - //fun networkQualityDidChange(networkQualityLevel: NetworkQualityLevel, participant: remoteParticipant)  
304 fun switchedOffVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {} 214 fun switchedOffVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {}
305 fun switchedOnVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {} 215 fun switchedOnVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {}
306 -// fun onChangePublishPriority(videoTrack: RemoteVideoTrackPublication, priority: PublishPriority, participant: RemoteParticipant)  
307 -// fun onChangePublishPriority(audioTrack: RemoteAudioTrackPublication, priority: PublishPriority, participant: RemoteParticipant)  
308 -// fun onChangePublishPriority(dataTrack: RemoteDataTrackPublication, priority: PublishPriority, participant: RemoteParticipant)  
309 } 216 }
310 217
311 } 218 }
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
  3 +import livekit.LivekitModels
3 import org.webrtc.AudioTrack 4 import org.webrtc.AudioTrack
4 -import org.webrtc.MediaStreamTrack  
5 5
6 -open class AudioTrack(name: String, val rtcTrack: AudioTrack) :  
7 - Track(name, stateFromRTCMediaTrackState(rtcTrack.state())),  
8 - MediaTrack { 6 +open class AudioTrack(name: String, override val rtcTrack: AudioTrack) :
  7 + MediaTrack(name, LivekitModels.TrackType.AUDIO, rtcTrack) {
9 8
10 - override val mediaTrack: MediaStreamTrack  
11 - get() = rtcTrack  
12 } 9 }
1 -package io.livekit.android.room.track  
2 -  
3 -interface AudioTrackPublication {  
4 - val audioTrack: AudioTrack?  
5 -}  
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
  3 +import livekit.LivekitModels
3 import org.webrtc.DataChannel 4 import org.webrtc.DataChannel
4 5
5 open class DataTrack( 6 open class DataTrack(
6 name: String, 7 name: String,
7 - var rtcTrack: DataChannel? = null  
8 -) : Track(  
9 - name,  
10 - run {  
11 - if (rtcTrack != null) {  
12 - stateFromRTCDataChannelState(rtcTrack.state())  
13 - } else {  
14 - State.NONE  
15 - }  
16 - }) {  
17 - 8 + var dataChannel: DataChannel? = null
  9 +) : Track(name, LivekitModels.TrackType.DATA) {
18 var ordered: Boolean = TODO() 10 var ordered: Boolean = TODO()
19 private set 11 private set
20 var maxRetransmitTimeMs: Int = TODO() 12 var maxRetransmitTimeMs: Int = TODO()
@@ -27,4 +19,8 @@ open class DataTrack( @@ -27,4 +19,8 @@ open class DataTrack(
27 maxRetransmitTimeMs = config.maxRetransmitTimeMs 19 maxRetransmitTimeMs = config.maxRetransmitTimeMs
28 maxRetransmits = config.maxRetransmits 20 maxRetransmits = config.maxRetransmits
29 } 21 }
30 -}  
  22 +
  23 + override fun stop() {
  24 + dataChannel?.unregisterObserver()
  25 + }
  26 +}
1 -package io.livekit.android.room.track  
2 -  
3 -interface DataTrackPublication {  
4 - val dataTrack: DataTrack?  
5 -}  
@@ -7,16 +7,14 @@ import java.util.* @@ -7,16 +7,14 @@ import java.util.*
7 class LocalAudioTrack( 7 class LocalAudioTrack(
8 name: String, 8 name: String,
9 audioOptions: AudioOptions? = null, 9 audioOptions: AudioOptions? = null,
10 - rtcTrack: org.webrtc.AudioTrack  
11 -) : AudioTrack(name, rtcTrack) { 10 + mediaTrack: org.webrtc.AudioTrack
  11 +) : AudioTrack(name, mediaTrack) {
12 var enabled: Boolean 12 var enabled: Boolean
13 get() = rtcTrack.enabled() 13 get() = rtcTrack.enabled()
14 set(value) { 14 set(value) {
15 rtcTrack.setEnabled(value) 15 rtcTrack.setEnabled(value)
16 } 16 }
17 17
18 - var sid: Sid? = null  
19 - internal set  
20 var audioOptions = audioOptions 18 var audioOptions = audioOptions
21 private set 19 private set
22 20
@@ -31,7 +29,7 @@ class LocalAudioTrack( @@ -31,7 +29,7 @@ class LocalAudioTrack(
31 val rtcAudioTrack = 29 val rtcAudioTrack =
32 factory.createAudioTrack(UUID.randomUUID().toString(), audioSource) 30 factory.createAudioTrack(UUID.randomUUID().toString(), audioSource)
33 31
34 - return LocalAudioTrack(name = name, rtcTrack = rtcAudioTrack) 32 + return LocalAudioTrack(name = name, mediaTrack = rtcAudioTrack)
35 } 33 }
36 } 34 }
37 -}  
  35 +}
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -class LocalAudioTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :  
6 - LocalTrackPublication(info, track), AudioTrackPublication {  
7 - override val audioTrack: AudioTrack?  
8 - get() = track as? AudioTrack  
9 -}  
@@ -5,21 +5,20 @@ import java.nio.ByteBuffer @@ -5,21 +5,20 @@ import java.nio.ByteBuffer
5 import java.util.* 5 import java.util.*
6 6
7 class LocalDataTrack( 7 class LocalDataTrack(
8 - val options: DataTrackOptions,  
9 - rtcTrack: DataChannel  
10 -) : DataTrack(options.name, rtcTrack) {  
11 - var sid: Sid? = null 8 + val options: DataTrackOptions
  9 +) : DataTrack(options.name) {
  10 + var sid: String? = null
12 internal set 11 internal set
13 - var cid: Cid = Cid(UUID.randomUUID().toString()) 12 + var cid: String = UUID.randomUUID().toString()
14 13
15 fun sendString(message: String) { 14 fun sendString(message: String) {
16 val byteBuffer = ByteBuffer.wrap(message.toByteArray()) 15 val byteBuffer = ByteBuffer.wrap(message.toByteArray())
17 val buffer = DataChannel.Buffer(byteBuffer, false) 16 val buffer = DataChannel.Buffer(byteBuffer, false)
18 - rtcTrack?.send(buffer) 17 + dataChannel?.send(buffer)
19 } 18 }
20 19
21 fun sendBytes(byteBuffer: ByteBuffer) { 20 fun sendBytes(byteBuffer: ByteBuffer) {
22 val buffer = DataChannel.Buffer(byteBuffer, true) 21 val buffer = DataChannel.Buffer(byteBuffer, true)
23 - rtcTrack?.send(buffer) 22 + dataChannel?.send(buffer)
24 } 23 }
25 -}  
  24 +}
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -class LocalDataTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :  
6 - LocalTrackPublication(info, track), DataTrackPublication {  
7 - override val dataTrack: DataTrack?  
8 - get() = track as? DataTrack  
9 -}  
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -open class LocalTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :  
6 - TrackPublication(info, track) {  
7 - val localTrack  
8 - get() = track  
9 - var priority = Track.Priority.STANDARD  
10 - private set  
11 -}  
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 -data class LocalTrackPublicationOptions(val priority: Track.Priority) 3 +data class LocalTrackPublicationOptions(val placeholder: Unit)
@@ -16,8 +16,6 @@ class LocalVideoTrack( @@ -16,8 +16,6 @@ class LocalVideoTrack(
16 capturer.startCapture(400, 400, 30) 16 capturer.startCapture(400, 400, 30)
17 } 17 }
18 18
19 - var sid: Sid? = null  
20 -  
21 companion object { 19 companion object {
22 internal fun createTrack( 20 internal fun createTrack(
23 peerConnectionFactory: PeerConnectionFactory, 21 peerConnectionFactory: PeerConnectionFactory,
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -class LocalVideoTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :  
6 - LocalTrackPublication(info, track), VideoTrackPublication {  
7 - override val videoTrack: VideoTrack?  
8 - get() = track as? VideoTrack  
9 -}  
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
  3 +import livekit.LivekitModels
3 import org.webrtc.MediaStreamTrack 4 import org.webrtc.MediaStreamTrack
4 5
5 -interface MediaTrack {  
6 - val mediaTrack: MediaStreamTrack  
7 -}  
  6 +
  7 +open class MediaTrack(name: String, kind: LivekitModels.TrackType, open val rtcTrack: MediaStreamTrack) :
  8 + Track(name, kind) {
  9 +
  10 + override fun stop() {
  11 + rtcTrack.setEnabled(false)
  12 + rtcTrack.dispose()
  13 + }
  14 +}
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 class RemoteAudioTrack( 3 class RemoteAudioTrack(
4 - sid: Sid, 4 + sid: String,
5 playbackEnabled: Boolean = true, 5 playbackEnabled: Boolean = true,
6 name: String, 6 name: String,
7 - rtcTrack: org.webrtc.AudioTrack  
8 -) : AudioTrack(name, rtcTrack), RemoteTrack { 7 + mediaTrack: org.webrtc.AudioTrack
  8 +) : AudioTrack(name, mediaTrack), RemoteTrack {
9 9
10 -  
11 - override var sid: Sid = sid 10 + override var sid: String = sid
12 var playbackEnabled = playbackEnabled 11 var playbackEnabled = playbackEnabled
13 internal set 12 internal set
14 13
15 -  
16 } 14 }
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -class RemoteAudioTrackPublication(  
6 - info: LivekitModels.TrackInfo,  
7 - track: Track? = null  
8 -) : RemoteTrackPublication(info, track), AudioTrackPublication {  
9 - override val audioTrack: AudioTrack?  
10 - get() = track as? AudioTrack  
11 -}  
@@ -3,7 +3,7 @@ package io.livekit.android.room.track @@ -3,7 +3,7 @@ package io.livekit.android.room.track
3 import org.webrtc.DataChannel 3 import org.webrtc.DataChannel
4 4
5 class RemoteDataTrack( 5 class RemoteDataTrack(
6 - override var sid: Sid, 6 + override var sid: String,
7 name: String, 7 name: String,
8 rtcTrack: DataChannel 8 rtcTrack: DataChannel
9 ) : 9 ) :
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -class RemoteDataTrackPublication(  
6 - info: LivekitModels.TrackInfo,  
7 - track: Track? = null  
8 -) : RemoteTrackPublication(info, track), DataTrackPublication {  
9 - override val dataTrack: DataTrack?  
10 - get() = track as? DataTrack  
11 -}  
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 interface RemoteTrack { 3 interface RemoteTrack {
4 - var sid: Track.Sid 4 + var sid: String
5 } 5 }
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -open class RemoteTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :  
6 - TrackPublication(info, track) {  
7 -  
8 - val remoteTrack: Track?  
9 - get() = track  
10 -  
11 - val trackSubscribed: Boolean  
12 - get() = track != null  
13 -  
14 - val publishPriority = Track.Priority.STANDARD  
15 -}  
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 class RemoteVideoTrack( 3 class RemoteVideoTrack(
4 - override var sid: Sid, 4 + override var sid: String,
5 var switchedOff: Boolean = false, 5 var switchedOff: Boolean = false,
6 - var priority: Priority? = null,  
7 name: String, 6 name: String,
8 - rtcTrack: org.webrtc.VideoTrack  
9 -) : VideoTrack(name, rtcTrack), RemoteTrack  
  7 + mediaTrack: org.webrtc.VideoTrack
  8 +) : VideoTrack(name, mediaTrack), RemoteTrack
1 -package io.livekit.android.room.track  
2 -  
3 -import livekit.LivekitModels  
4 -  
5 -class RemoteVideoTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :  
6 - RemoteTrackPublication(info, track),  
7 - VideoTrackPublication {  
8 -  
9 - override val videoTrack: VideoTrack?  
10 - get() = track as? VideoTrack  
11 -}  
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
  3 +import livekit.LivekitModels
3 import org.webrtc.DataChannel 4 import org.webrtc.DataChannel
4 import org.webrtc.MediaStreamTrack 5 import org.webrtc.MediaStreamTrack
5 6
6 -open class Track(name: String, state: State) {  
7 - 7 +open class Track(name: String, kind: LivekitModels.TrackType) {
8 var name = name 8 var name = name
9 internal set 9 internal set
10 - var state = state 10 + var kind = kind
11 internal set 11 internal set
12 -  
13 - inline class Sid(val sid: String)  
14 - inline class Cid(val cid: String)  
15 -  
16 - enum class Priority {  
17 - STANDARD, HIGH, LOW;  
18 - } 12 + var state: State = State.NONE
19 13
20 enum class State { 14 enum class State {
21 ENDED, LIVE, NONE; 15 ENDED, LIVE, NONE;
22 } 16 }
23 17
  18 + open fun stop() {
  19 + // subclasses override to provide stop behavior
  20 + }
  21 +
24 companion object { 22 companion object {
25 fun stateFromRTCMediaTrackState(trackState: MediaStreamTrack.State): State { 23 fun stateFromRTCMediaTrackState(trackState: MediaStreamTrack.State): State {
26 return when (trackState) { 24 return when (trackState) {
@@ -42,6 +40,7 @@ open class Track(name: String, state: State) { @@ -42,6 +40,7 @@ open class Track(name: String, state: State) {
42 } 40 }
43 } 41 }
44 } 42 }
  43 +
45 } 44 }
46 45
47 sealed class TrackException(message: String? = null, cause: Throwable? = null) : 46 sealed class TrackException(message: String? = null, cause: Throwable? = null) :
@@ -5,18 +5,28 @@ import livekit.LivekitModels @@ -5,18 +5,28 @@ import livekit.LivekitModels
5 open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) { 5 open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) {
6 var track: Track? = track 6 var track: Track? = track
7 internal set 7 internal set
8 - var trackName: String 8 + var name: String
9 internal set 9 internal set
10 - var trackSid: Track.Sid 10 + var sid: String
  11 + private set
  12 + var kind: LivekitModels.TrackType
  13 + private set
  14 + var muted: Boolean
11 private set 15 private set
12 16
13 init { 17 init {
14 - trackSid = Track.Sid(info.sid)  
15 - trackName = info.name 18 + sid = info.sid
  19 + name = info.name
  20 + kind = info.type
  21 + muted = info.muted
16 } 22 }
17 23
18 fun updateFromInfo(info: LivekitModels.TrackInfo) { 24 fun updateFromInfo(info: LivekitModels.TrackInfo) {
19 - trackSid = Track.Sid(info.sid)  
20 - trackName = info.name 25 + sid = info.sid
  26 + name = info.name
  27 + kind = info.type
  28 +
  29 + // TODO: forward mute status to listener
  30 + muted = info.muted
21 } 31 }
22 -}  
  32 +}
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 -import org.webrtc.MediaStreamTrack 3 +import livekit.LivekitModels
4 import org.webrtc.VideoSink 4 import org.webrtc.VideoSink
5 import org.webrtc.VideoTrack 5 import org.webrtc.VideoTrack
6 6
7 -open class VideoTrack(name: String, val rtcTrack: VideoTrack) :  
8 - Track(name, stateFromRTCMediaTrackState(rtcTrack.state())),  
9 - MediaTrack {  
10 -  
11 - override val mediaTrack: MediaStreamTrack  
12 - get() = rtcTrack 7 +open class VideoTrack(name: String, override val rtcTrack: VideoTrack) :
  8 + MediaTrack(name, LivekitModels.TrackType.VIDEO, rtcTrack){
13 9
14 var enabled: Boolean 10 var enabled: Boolean
15 get() = rtcTrack.enabled() 11 get() = rtcTrack.enabled()
@@ -20,4 +16,4 @@ open class VideoTrack(name: String, val rtcTrack: VideoTrack) : @@ -20,4 +16,4 @@ open class VideoTrack(name: String, val rtcTrack: VideoTrack) :
20 fun addRenderer(renderer: VideoSink) = rtcTrack.addSink(renderer) 16 fun addRenderer(renderer: VideoSink) = rtcTrack.addSink(renderer)
21 17
22 fun removeRenderer(renderer: VideoSink) = rtcTrack.addSink(renderer) 18 fun removeRenderer(renderer: VideoSink) = rtcTrack.addSink(renderer)
23 -}  
  19 +}
1 -package io.livekit.android.room.track  
2 -  
3 -interface VideoTrackPublication {  
4 - val videoTrack: VideoTrack?  
5 -}  
@@ -4,19 +4,19 @@ import io.livekit.android.room.participant.Participant @@ -4,19 +4,19 @@ import io.livekit.android.room.participant.Participant
4 import io.livekit.android.room.track.Track 4 import io.livekit.android.room.track.Track
5 import org.webrtc.DataChannel 5 import org.webrtc.DataChannel
6 6
7 -fun DataChannel.unpackedTrackLabel(): Triple<Participant.Sid, Track.Sid, String> { 7 +fun DataChannel.unpackedTrackLabel(): Triple<String, String, String> {
8 val parts = label().split("|") 8 val parts = label().split("|")
9 - val participantSid: Participant.Sid  
10 - val trackSid: Track.Sid 9 + val participantSid: String
  10 + val trackSid: String
11 val name: String 11 val name: String
12 12
13 if (parts.count() == 3) { 13 if (parts.count() == 3) {
14 - participantSid = Participant.Sid(parts[0])  
15 - trackSid = Track.Sid(parts[1]) 14 + participantSid = parts[0]
  15 + trackSid = parts[1]
16 name = parts[2] 16 name = parts[2]
17 } else { 17 } else {
18 - participantSid = Participant.Sid("")  
19 - trackSid = Track.Sid("") 18 + participantSid = ""
  19 + trackSid = ""
20 name = "" 20 name = ""
21 } 21 }
22 22
@@ -51,7 +51,7 @@ class CallActivity : AppCompatActivity() { @@ -51,7 +51,7 @@ class CallActivity : AppCompatActivity() {
51 51
52 tabLayoutMediator = 52 tabLayoutMediator =
53 TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position -> 53 TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
54 - tab.text = participants[position].name 54 + tab.text = participants[position].identity
55 } 55 }
56 tabLayoutMediator?.attach() 56 tabLayoutMediator?.attach()
57 } 57 }
@@ -84,7 +84,7 @@ class CallViewModel( @@ -84,7 +84,7 @@ class CallViewModel(
84 mutableRemoteParticipants.postValue( 84 mutableRemoteParticipants.postValue(
85 room.remoteParticipants 85 room.remoteParticipants
86 .keys 86 .keys
87 - .sortedBy { it.sid } 87 + .sortedBy { it }
88 .mapNotNull { room.remoteParticipants[it] } 88 .mapNotNull { room.remoteParticipants[it] }
89 ) 89 )
90 } 90 }
1 package io.livekit.android.sample 1 package io.livekit.android.sample
2 2
  3 +import android.provider.MediaStore
3 import android.view.View 4 import android.view.View
4 import com.github.ajalt.timberkt.Timber 5 import com.github.ajalt.timberkt.Timber
5 import com.xwray.groupie.viewbinding.BindableItem 6 import com.xwray.groupie.viewbinding.BindableItem
6 import com.xwray.groupie.viewbinding.GroupieViewHolder 7 import com.xwray.groupie.viewbinding.GroupieViewHolder
7 import io.livekit.android.room.Room 8 import io.livekit.android.room.Room
8 import io.livekit.android.room.participant.RemoteParticipant 9 import io.livekit.android.room.participant.RemoteParticipant
9 -import io.livekit.android.room.track.RemoteVideoTrackPublication  
10 -import io.livekit.android.room.track.VideoTrack  
11 -import io.livekit.android.room.track.VideoTrackPublication 10 +import io.livekit.android.room.track.*
12 import io.livekit.android.sample.databinding.ParticipantItemBinding 11 import io.livekit.android.sample.databinding.ParticipantItemBinding
13 12
14 class ParticipantItem( 13 class ParticipantItem(
@@ -30,11 +29,11 @@ class ParticipantItem( @@ -30,11 +29,11 @@ class ParticipantItem(
30 29
31 remoteParticipant.listener = object : RemoteParticipant.Listener { 30 remoteParticipant.listener = object : RemoteParticipant.Listener {
32 override fun onSubscribe( 31 override fun onSubscribe(
33 - videoTrack: RemoteVideoTrackPublication, 32 + track: Track,
  33 + publication: TrackPublication,
34 participant: RemoteParticipant 34 participant: RemoteParticipant
35 ) { 35 ) {
36 - val track = videoTrack.videoTrack  
37 - if (track != null) { 36 + if (track is VideoTrack) {
38 setupVideoIfNeeded(track, viewBinding) 37 setupVideoIfNeeded(track, viewBinding)
39 } 38 }
40 } 39 }
@@ -48,10 +47,8 @@ class ParticipantItem( @@ -48,10 +47,8 @@ class ParticipantItem(
48 47
49 private fun getVideoTrack(): VideoTrack? { 48 private fun getVideoTrack(): VideoTrack? {
50 return remoteParticipant 49 return remoteParticipant
51 - .remoteVideoTracks  
52 - .firstOrNull()  
53 - .let { it as? VideoTrackPublication }  
54 - ?.videoTrack 50 + .videoTracks.values
  51 + .firstOrNull()?.track as? VideoTrack
55 } 52 }
56 53
57 private fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) { 54 private fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) {