David Zhao

major refactor, simplified TrackPublication hierarchy

正在显示 35 个修改的文件 包含 236 行增加497 行删除
... ... @@ -153,9 +153,9 @@ constructor(
sendRequest(request)
}
fun sendMuteTrack(trackSid: Track.Sid, muted: Boolean) {
fun sendMuteTrack(trackSid: String, muted: Boolean) {
val muteRequest = LivekitRtc.MuteTrackRequest.newBuilder()
.setSid(trackSid.sid)
.setSid(trackSid)
.setMuted(muted)
.build()
... ... @@ -166,9 +166,9 @@ constructor(
sendRequest(request)
}
fun sendAddTrack(cid: Track.Cid, name: String, type: LivekitModels.TrackType) {
fun sendAddTrack(cid: String, name: String, type: LivekitModels.TrackType) {
val addTrackRequest = LivekitRtc.AddTrackRequest.newBuilder()
.setCid(cid.cid)
.setCid(cid)
.setName(name)
.setType(type)
.build()
... ...
... ... @@ -45,7 +45,7 @@ constructor(
}
}
val pendingCandidates = mutableListOf<IceCandidate>()
private val pendingTrackResolvers: MutableMap<Track.Cid, Continuation<LivekitModels.TrackInfo>> =
private val pendingTrackResolvers: MutableMap<String, Continuation<LivekitModels.TrackInfo>> =
mutableMapOf()
private val publisherObserver = PublisherTransportObserver(this)
... ... @@ -76,7 +76,7 @@ constructor(
client.join(url, token)
}
suspend fun addTrack(cid: Track.Cid, name: String, kind: LivekitModels.TrackType): LivekitModels.TrackInfo {
suspend fun addTrack(cid: String, name: String, kind: LivekitModels.TrackType): LivekitModels.TrackInfo {
if (pendingTrackResolvers[cid] != null) {
throw TrackException.DuplicateTrackException("Track with same ID $cid has already been published!")
}
... ... @@ -87,7 +87,7 @@ constructor(
}
}
fun updateMuteStatus(sid: Track.Sid, muted: Boolean) {
fun updateMuteStatus(sid: String, muted: Boolean) {
client.sendMuteTrack(sid, muted)
}
... ... @@ -137,7 +137,7 @@ constructor(
interface Listener {
fun onJoin(response: LivekitRtc.JoinResponse)
fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>)
fun onPublishLocalTrack(cid: Track.Cid, track: LivekitModels.TrackInfo)
fun onPublishLocalTrack(cid: String, track: LivekitModels.TrackInfo)
fun onAddDataChannel(channel: DataChannel)
fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>)
fun onUpdateSpeakers(speakers: List<LivekitRtc.SpeakerInfo>)
... ... @@ -253,7 +253,7 @@ constructor(
Timber.e { "local track published with null cid?" }
return
}
val cid = Track.Cid(signalCid)
val cid = signalCid
val track = response.track
if (track == null) {
... ...
... ... @@ -9,7 +9,6 @@ import io.livekit.android.ConnectOptions
import io.livekit.android.room.participant.LocalParticipant
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.participant.RemoteParticipant
import io.livekit.android.room.track.Track
import io.livekit.android.room.util.unpackedTrackLabel
import livekit.LivekitModels
import livekit.LivekitRtc
... ... @@ -24,7 +23,7 @@ constructor(
@Assisted private val connectOptions: ConnectOptions,
private val engine: RTCEngine,
private val eglBase: EglBase,
) : RTCEngine.Listener, RemoteParticipant.Listener, LocalParticipant.Listener {
) : RTCEngine.Listener, RemoteParticipant.Listener {
init {
engine.listener = this
}
... ... @@ -48,8 +47,8 @@ constructor(
private set
var localParticipant: LocalParticipant? = null
private set
private val mutableRemoteParticipants = mutableMapOf<Participant.Sid, RemoteParticipant>()
val remoteParticipants: Map<Participant.Sid, RemoteParticipant>
private val mutableRemoteParticipants = mutableMapOf<String, RemoteParticipant>()
val remoteParticipants: Map<String, RemoteParticipant>
get() = mutableRemoteParticipants
private val mutableActiveSpeakers = mutableListOf<Participant>()
... ... @@ -73,17 +72,17 @@ constructor(
listener?.onDisconnect(this, null)
}
private fun handleParticipantDisconnect(sid: Participant.Sid, participant: RemoteParticipant) {
private fun handleParticipantDisconnect(sid: String, participant: RemoteParticipant) {
val removedParticipant = mutableRemoteParticipants.remove(sid) ?: return
removedParticipant.tracks.values.forEach { publication ->
removedParticipant.unpublishTrack(publication.trackSid)
removedParticipant.unpublishTrack(publication.sid)
}
listener?.onParticipantDisconnected(this, removedParticipant)
}
private fun getOrCreateRemoteParticipant(
sid: Participant.Sid,
sid: String,
info: LivekitModels.ParticipantInfo? = null
): RemoteParticipant {
var participant = remoteParticipants[sid]
... ... @@ -103,10 +102,10 @@ constructor(
private fun handleSpeakerUpdate(speakerInfos: List<LivekitRtc.SpeakerInfo>) {
val speakers = mutableListOf<Participant>()
val seenSids = mutableSetOf<Participant.Sid>()
val seenSids = mutableSetOf<String>()
val localParticipant = localParticipant
speakerInfos.forEach { speakerInfo ->
val speakerSid = Participant.Sid(speakerInfo.sid)
val speakerSid = speakerInfo.sid!!
seenSids.add(speakerSid)
if (speakerSid == localParticipant?.sid) {
... ... @@ -185,7 +184,7 @@ constructor(
}
if (response.otherParticipantsList.isNotEmpty()) {
response.otherParticipantsList.forEach {
getOrCreateRemoteParticipant(Participant.Sid(it.sid), it)
getOrCreateRemoteParticipant(it.sid, it)
}
}
... ... @@ -203,8 +202,8 @@ constructor(
return
}
val participantSid = Participant.Sid(streams.first().id)
val trackSid = Track.Sid(track.id())
val participantSid = streams.first().id
val trackSid = track.id()
val participant = getOrCreateRemoteParticipant(participantSid)
participant.addSubscribedMediaTrack(track, trackSid)
}
... ... @@ -222,7 +221,7 @@ constructor(
/**
* @suppress
*/
override fun onPublishLocalTrack(cid: Track.Cid, track: LivekitModels.TrackInfo) {
override fun onPublishLocalTrack(cid: String, track: LivekitModels.TrackInfo) {
}
/**
... ... @@ -230,7 +229,7 @@ constructor(
*/
override fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>) {
for (info in updates) {
val participantSid = Participant.Sid(info.sid)
val participantSid = info.sid
if(localParticipant?.sid == participantSid) {
localParticipant?.updateFromInfo(info)
... ...
... ... @@ -8,28 +8,16 @@ import org.webrtc.DataChannel
import org.webrtc.RtpTransceiver
import java.util.*
class LocalParticipant(sid: Sid, name: String? = null) :
Participant(sid, name) {
/**
* @suppress
*/
constructor(info: LivekitModels.ParticipantInfo, engine: RTCEngine) : this(
Sid(info.sid),
info.identity
) {
metadata = info.metadata
this.engine = engine
class LocalParticipant(info: LivekitModels.ParticipantInfo, private val engine: RTCEngine) :
Participant(info.sid, info.identity) {
init {
updateFromInfo(info)
}
val localAudioTrackPublications
get() = audioTracks.values.toList()
val localVideoTrackPublications
get() = videoTracks.values.toList()
val localDataTrackPublications
get() = dataTracks.values.toList()
val localTrackPublications
get() = tracks.values.toList()
private var engine: RTCEngine? = null
var listener: Listener? = null
set(v) {
field = v
... ... @@ -38,98 +26,77 @@ class LocalParticipant(sid: Sid, name: String? = null) :
suspend fun publishAudioTrack(
track: LocalAudioTrack,
options: LocalTrackPublicationOptions? = null
publishListener: PublishListener? = null
) {
if (localAudioTrackPublications.any { it.track == track }) {
listener?.onFailToPublishAudioTrack(TrackException.PublishException("Track has already been published"))
if (localTrackPublications.any { it.track == track }) {
publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
return
}
val cid = track.rtcTrack.id()
val engine = this.engine ?: run {
listener?.onFailToPublishAudioTrack(IllegalStateException("engine is null!"))
return
}
val trackInfo =
engine.addTrack(cid = Track.Cid(cid), name = track.name, kind = LivekitModels.TrackType.AUDIO)
engine.addTrack(cid = cid, name = track.name, kind = track.kind)
val transInit = RtpTransceiver.RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(this.sid.sid)
listOf(this.sid)
)
// TODO: sendEncodings to customize
val transceiver =
engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
if (transceiver == null) {
listener?.onFailToPublishAudioTrack(TrackException.PublishException("null sender returned from peer connection"))
publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection"))
return
}
val publication = LocalAudioTrackPublication(trackInfo)
val trackSid = Track.Sid(trackInfo.sid)
track.sid = trackSid
audioTracks[trackSid] = publication
listener?.onPublishAudioTrack(track)
val publication = TrackPublication(trackInfo, track)
addTrackPublication(publication)
publishListener?.onPublishSuccess(publication)
}
suspend fun publishVideoTrack(
track: LocalVideoTrack,
options: LocalTrackPublicationOptions? = null
publishListener: PublishListener? = null
) {
if (localVideoTrackPublications.any { it.track == track }) {
listener?.onFailToPublishVideoTrack(TrackException.PublishException("Track has already been published"))
if (localTrackPublications.any { it.track == track }) {
publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
return
}
val cid = track.rtcTrack.id()
val engine = this.engine ?: run {
listener?.onFailToPublishVideoTrack(IllegalStateException("engine is null!"))
return
}
val trackInfo =
engine.addTrack(cid = Track.Cid(cid), name = track.name, kind = LivekitModels.TrackType.VIDEO)
engine.addTrack(cid = cid, name = track.name, kind = LivekitModels.TrackType.VIDEO)
val transInit = RtpTransceiver.RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(this.sid.sid)
listOf(this.sid)
)
// TODO: video encodings & simulcast
val transceiver =
engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
if (transceiver == null) {
listener?.onFailToPublishVideoTrack(TrackException.PublishException("null sender returned from peer connection"))
publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection"))
return
}
val publication = LocalVideoTrackPublication(trackInfo)
val trackSid = Track.Sid(trackInfo.sid)
track.sid = trackSid
videoTracks[trackSid] = publication
listener?.onPublishVideoTrack(track)
val publication = TrackPublication(trackInfo, track)
addTrackPublication(publication)
publishListener?.onPublishSuccess(publication)
}
suspend fun publishDataTrack(
track: LocalDataTrack,
options: LocalTrackPublicationOptions? = null
publishListener: PublishListener? = null
) {
if (localDataTrackPublications.any { it.track == track }) {
listener?.onFailToPublishDataTrack(TrackException.PublishException("Track has already been published"))
if (localTrackPublications.any { it.track == track }) {
publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
return
}
val cid = track.cid
val engine = this.engine ?: run {
listener?.onFailToPublishDataTrack(IllegalStateException("engine is null!"))
return
}
val trackInfo =
engine.addTrack(cid = cid, name = track.name, kind = LivekitModels.TrackType.DATA)
val publication = LocalDataTrackPublication(trackInfo, track)
val trackSid = Track.Sid(trackInfo.sid)
track.sid = trackSid
engine.addTrack(cid = cid, name = track.name, track.kind)
val publication = TrackPublication(trackInfo, track)
val config = DataChannel.Init().apply {
ordered = track.options.ordered
... ... @@ -138,76 +105,51 @@ class LocalParticipant(sid: Sid, name: String? = null) :
}
val dataChannel = engine.publisher.peerConnection.createDataChannel(track.name, config)
if (dataChannel != null) {
track.rtcTrack = dataChannel
track.updateConfig(config)
dataTracks[trackSid] = publication
listener?.onPublishDataTrack(track)
} else {
Timber.d { "error creating data channel with name: $name" }
unpublishDataTrack(track)
}
}
fun unpublishAudioTrack(track: LocalAudioTrack) {
val sid = track.sid ?: run {
Timber.d { "this track was never published." }
if (dataChannel == null) {
publishListener?.onPublishFailure(TrackException.PublishException("could not create data channel"))
return
}
unpublishMediaTrack(track, sid, audioTracks)
}
track.dataChannel = dataChannel
track.updateConfig(config)
addTrackPublication(publication)
fun unpublishVideoTrack(track: LocalVideoTrack) {
val sid = track.sid ?: run {
Timber.d { "this track was never published." }
return
}
unpublishMediaTrack(track, sid, audioTracks)
publishListener?.onPublishSuccess(publication)
}
fun unpublishDataTrack(track: LocalDataTrack) {
val sid = track.sid ?: run {
fun unpublishTrack(track: Track) {
val publication = localTrackPublications.firstOrNull { it.track == track }
if (publication === null) {
Timber.d { "this track was never published." }
return
}
val publication = dataTracks.remove(sid) as? LocalDataTrackPublication
if (publication == null) {
Timber.d { "track was not published with sid: $sid" }
return
track.stop()
if (track is MediaTrack) {
unpublishMediaTrack(track, sid)
}
val sid = publication.sid
tracks.remove(sid)
when (publication.kind) {
LivekitModels.TrackType.AUDIO -> audioTracks.remove(sid)
LivekitModels.TrackType.VIDEO -> videoTracks.remove(sid)
LivekitModels.TrackType.DATA -> dataTracks.remove(sid)
}
publication.dataTrack?.rtcTrack?.dispose()
}
private fun <T> unpublishMediaTrack(
track: T,
sid: Track.Sid,
publications: MutableMap<Track.Sid, TrackPublication>
) where T : Track, T : MediaTrack {
val removed = publications.remove(sid)
if (removed != null) {
Timber.d { "track was not published with sid: $sid" }
return
}
track.mediaTrack.setEnabled(false)
sid: String
) where T : MediaTrack {
val senders = engine?.publisher?.peerConnection?.senders ?: return
for (sender in senders) {
val t = sender.track() ?: continue
if (t == track.mediaTrack) {
if (t == track.rtcTrack) {
engine?.publisher?.peerConnection?.removeTrack(sender)
}
}
}
interface Listener : Participant.Listener {
// TODO: can we move these to exceptions? instead of callbacks
fun onPublishAudioTrack(track: LocalAudioTrack) {}
fun onFailToPublishAudioTrack(exception: Exception) {}
fun onPublishVideoTrack(track: LocalVideoTrack) {}
fun onFailToPublishVideoTrack(exception: Exception) {}
fun onPublishDataTrack(track: LocalDataTrack) {}
fun onFailToPublishDataTrack(exception: Exception) {}
//fun onNetworkQualityLevelChange
interface PublishListener {
fun onPublishSuccess(publication: TrackPublication) {}
fun onPublishFailure(exception: Exception) {}
}
}
... ...
... ... @@ -3,12 +3,10 @@ package io.livekit.android.room.participant
import io.livekit.android.room.track.*
import livekit.LivekitModels
open class Participant(var sid: Sid, name: String? = null) {
inline class Sid(val sid: String)
open class Participant(var sid: String, identity: String? = null) {
var participantInfo: LivekitModels.ParticipantInfo? = null
private set
var name: String? = name
var identity: String? = identity
internal set
var audioLevel: Float = 0f
internal set
... ... @@ -17,23 +15,23 @@ open class Participant(var sid: Sid, name: String? = null) {
val hasInfo
get() = participantInfo != null
var tracks = mutableMapOf<Track.Sid, TrackPublication>()
var audioTracks = mutableMapOf<Track.Sid, TrackPublication>()
var tracks = mutableMapOf<String, TrackPublication>()
var audioTracks = mutableMapOf<String, TrackPublication>()
private set
var videoTracks = mutableMapOf<Track.Sid, TrackPublication>()
var videoTracks = mutableMapOf<String, TrackPublication>()
private set
var dataTracks = mutableMapOf<Track.Sid, TrackPublication>()
var dataTracks = mutableMapOf<String, TrackPublication>()
private set
/**
* @suppress
*/
fun addTrack(publication: TrackPublication) {
tracks[publication.trackSid] = publication
when (publication) {
is RemoteAudioTrackPublication -> audioTracks[publication.trackSid] = publication
is RemoteVideoTrackPublication -> videoTracks[publication.trackSid] = publication
is RemoteDataTrackPublication -> dataTracks[publication.trackSid] = publication
fun addTrackPublication(publication: TrackPublication) {
tracks[publication.sid] = publication
when (publication.kind) {
LivekitModels.TrackType.AUDIO -> audioTracks[publication.sid] = publication
LivekitModels.TrackType.VIDEO -> videoTracks[publication.sid] = publication
LivekitModels.TrackType.DATA -> dataTracks[publication.sid] = publication
}
}
... ... @@ -41,8 +39,8 @@ open class Participant(var sid: Sid, name: String? = null) {
* @suppress
*/
open fun updateFromInfo(info: LivekitModels.ParticipantInfo) {
sid = Sid(info.sid)
name = info.identity
sid = info.sid
identity = info.identity
participantInfo = info
val prevMetadata = metadata
... ...
... ... @@ -14,22 +14,15 @@ import org.webrtc.VideoTrack
import java.nio.ByteBuffer
class RemoteParticipant(
sid: Sid, name: String? = null
sid: String, name: String? = null
) : Participant(sid, name), RemoteDataTrack.Listener {
/**
* @suppress
*/
constructor(info: LivekitModels.ParticipantInfo) : this(Sid(info.sid), info.identity) {
constructor(info: LivekitModels.ParticipantInfo) : this(info.sid, info.identity) {
updateFromInfo(info)
}
val remoteAudioTracks
get() = audioTracks.values.toList()
val remoteVideoTracks
get() = videoTracks.values.toList()
val remoteDataTracks
get() = dataTracks.values.toList()
var listener: Listener? = null
set(v) {
field = v
... ... @@ -38,8 +31,8 @@ class RemoteParticipant(
private val coroutineScope = CloseableCoroutineScope(SupervisorJob())
fun getTrackPublication(sid: Track.Sid): RemoteTrackPublication? =
tracks[sid] as? RemoteTrackPublication
fun getTrackPublication(sid: String): TrackPublication? =
tracks[sid]
/**
* @suppress
... ... @@ -48,24 +41,18 @@ class RemoteParticipant(
val hadInfo = hasInfo
super.updateFromInfo(info)
val validTrackPublication = mutableMapOf<Track.Sid, RemoteTrackPublication>()
val newTrackPublications = mutableMapOf<Track.Sid, RemoteTrackPublication>()
val validTrackPublication = mutableMapOf<String, TrackPublication>()
val newTrackPublications = mutableMapOf<String, TrackPublication>()
for (trackInfo in info.tracksList) {
val trackSid = Track.Sid(trackInfo.sid)
val trackSid = trackInfo.sid
var publication = getTrackPublication(trackSid)
if (publication == null) {
publication = when (trackInfo.type) {
LivekitModels.TrackType.AUDIO -> RemoteAudioTrackPublication(trackInfo)
LivekitModels.TrackType.VIDEO -> RemoteVideoTrackPublication(trackInfo)
LivekitModels.TrackType.DATA -> RemoteDataTrackPublication(trackInfo)
LivekitModels.TrackType.UNRECOGNIZED -> throw TrackException.InvalidTrackTypeException()
null -> throw NullPointerException("trackInfo.type is null")
}
publication = TrackPublication(trackInfo)
newTrackPublications[trackSid] = publication
addTrack(publication)
addTrackPublication(publication)
} else {
publication.updateFromInfo(trackInfo)
}
... ... @@ -75,25 +62,25 @@ class RemoteParticipant(
if (hadInfo) {
for (publication in newTrackPublications.values) {
sendTrackPublishedEvent(publication)
listener?.onPublish(publication, this)
}
}
val invalidKeys = tracks.keys - validTrackPublication.keys
for (invalidKey in invalidKeys) {
val publication = tracks[invalidKey] ?: continue
unpublishTrack(publication.trackSid, true)
unpublishTrack(publication.sid, true)
}
}
/**
* @suppress
*/
fun addSubscribedMediaTrack(rtcTrack: MediaStreamTrack, sid: Track.Sid, triesLeft: Int = 20) {
fun addSubscribedMediaTrack(mediaTrack: MediaStreamTrack, sid: String, triesLeft: Int = 20) {
val publication = getTrackPublication(sid)
val track: Track = when (val kind = rtcTrack.kind()) {
KIND_AUDIO -> RemoteAudioTrack(sid = sid, rtcTrack = rtcTrack as AudioTrack, name = "")
KIND_VIDEO -> RemoteVideoTrack(sid = sid, rtcTrack = rtcTrack as VideoTrack, name = "")
val track: Track = when (val kind = mediaTrack.kind()) {
KIND_AUDIO -> RemoteAudioTrack(sid = sid, mediaTrack = mediaTrack as AudioTrack, name = "")
KIND_VIDEO -> RemoteVideoTrack(sid = sid, mediaTrack = mediaTrack as VideoTrack, name = "")
else -> throw TrackException.InvalidTrackTypeException("invalid track type: $kind")
}
... ... @@ -102,27 +89,12 @@ class RemoteParticipant(
val message = "Could not find published track with sid: $sid"
val exception = TrackException.InvalidTrackStateException(message)
Timber.e { "remote participant ${this.sid} --- $message" }
when (rtcTrack.kind()) {
KIND_AUDIO -> {
listener?.onFailToSubscribe(
audioTrack = track as RemoteAudioTrack,
exception = exception,
participant = this
)
}
KIND_VIDEO -> {
listener?.onFailToSubscribe(
videoTrack = track as RemoteVideoTrack,
exception = exception,
participant = this
)
}
}
listener?.onFailToSubscribe(sid, exception, this)
} else {
coroutineScope.launch {
delay(150)
addSubscribedMediaTrack(rtcTrack, sid, triesLeft - 1)
addSubscribedMediaTrack(mediaTrack, sid, triesLeft - 1)
}
}
return
... ... @@ -130,103 +102,70 @@ class RemoteParticipant(
val remoteTrack = track as RemoteTrack
publication.track = track
track.name = publication.trackName
remoteTrack.sid = publication.trackSid
track.name = publication.name
remoteTrack.sid = publication.sid
when (publication) {
is RemoteAudioTrackPublication -> listener?.onSubscribe(publication, this)
is RemoteVideoTrackPublication -> listener?.onSubscribe(publication, this)
else -> throw TrackException.InvalidTrackTypeException()
}
// TODO: how does mediatrack send ended event?
listener?.onSubscribe(track, publication, this)
}
/**
* @suppress
*/
fun addSubscribedDataTrack(rtcTrack: DataChannel, sid: Track.Sid, name: String) {
val track = RemoteDataTrack(sid, name, rtcTrack)
var publication = getTrackPublication(sid) as? RemoteDataTrackPublication
fun addSubscribedDataTrack(dataChannel: DataChannel, sid: String, name: String) {
val track = DataTrack(name, dataChannel)
var publication = getTrackPublication(sid)
if (publication != null) {
publication.track = track
} else {
val trackInfo = LivekitModels.TrackInfo.newBuilder()
.setSid(sid.sid)
.setSid(sid)
.setName(name)
.setType(LivekitModels.TrackType.DATA)
.build()
publication = RemoteDataTrackPublication(info = trackInfo, track = track)
addTrack(publication)
publication = TrackPublication(info = trackInfo, track = track)
addTrackPublication(publication)
if (hasInfo) {
sendTrackPublishedEvent(publication)
listener?.onPublish(publication, this)
}
}
rtcTrack.registerObserver(object : DataChannel.Observer {
dataChannel.registerObserver(object : DataChannel.Observer {
override fun onBufferedAmountChange(previousAmount: Long) {}
override fun onStateChange() {
val newState = rtcTrack.state()
val newState = dataChannel.state()
if (newState == DataChannel.State.CLOSED) {
listener?.onUnsubscribe(publication, this@RemoteParticipant)
publication.track = null
listener?.onUnsubscribe(track, publication, this@RemoteParticipant)
}
}
override fun onMessage(buffer: DataChannel.Buffer) {
listener?.onReceive(buffer.data, publication, this@RemoteParticipant)
listener?.onReceive(buffer.data, track, this@RemoteParticipant)
}
})
listener?.onSubscribe(dataTrack = publication, participant = this)
listener?.onSubscribe(track, publication, participant = this)
}
fun unpublishTrack(trackSid: Track.Sid, sendUnpublish: Boolean = false) {
fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) {
val publication = tracks.remove(trackSid) ?: return
when (publication) {
is RemoteAudioTrackPublication -> audioTracks.remove(trackSid)
is RemoteVideoTrackPublication -> videoTracks.remove(trackSid)
is RemoteDataTrackPublication -> {
dataTracks.remove(trackSid)
publication.dataTrack?.rtcTrack?.unregisterObserver()
}
when (publication.kind) {
LivekitModels.TrackType.AUDIO -> audioTracks.remove(trackSid)
LivekitModels.TrackType.VIDEO -> videoTracks.remove(trackSid)
LivekitModels.TrackType.DATA -> dataTracks.remove(trackSid)
else -> throw TrackException.InvalidTrackTypeException()
}
if (publication.track != null) {
// TODO: need to stop track?
publication.track
sendTrackUnsubscribedEvent(publication)
val track = publication.track
if (track != null) {
track.stop()
listener?.onUnsubscribe(track, publication, this)
}
if (sendUnpublish) {
sendTrackUnpublishedEvent(publication)
}
}
private fun sendTrackUnsubscribedEvent(publication: TrackPublication) {
when (publication) {
is RemoteAudioTrackPublication -> listener?.onUnsubscribe(publication, this)
is RemoteVideoTrackPublication -> listener?.onUnsubscribe(publication, this)
is RemoteDataTrackPublication -> listener?.onUnsubscribe(publication, this)
else -> throw TrackException.InvalidTrackTypeException()
}
}
private fun sendTrackUnpublishedEvent(publication: TrackPublication) {
when (publication) {
is RemoteAudioTrackPublication -> listener?.onUnpublish(publication, this)
is RemoteVideoTrackPublication -> listener?.onUnpublish(publication, this)
is RemoteDataTrackPublication -> listener?.onUnpublish(publication, this)
else -> throw TrackException.InvalidTrackTypeException()
}
}
private fun sendTrackPublishedEvent(publication: RemoteTrackPublication) {
when (publication) {
is RemoteAudioTrackPublication -> listener?.onPublish(publication, this)
is RemoteVideoTrackPublication -> listener?.onPublish(publication, this)
is RemoteDataTrackPublication -> listener?.onPublish(publication, this)
else -> throw TrackException.InvalidTrackTypeException()
listener?.onUnpublish(publication, this)
}
}
... ... @@ -244,68 +183,36 @@ class RemoteParticipant(
}
interface Listener: Participant.Listener {
fun onPublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onPublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onPublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}
fun onEnable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onDisable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onEnable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onDisable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onSubscribe(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onFailToSubscribe(
audioTrack: RemoteAudioTrack,
exception: Exception,
participant: RemoteParticipant
) {
}
fun onPublish(publication: TrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(publication: TrackPublication, participant: RemoteParticipant) {}
fun onUnsubscribe(
audioTrack: RemoteAudioTrackPublication,
participant: RemoteParticipant
) {
}
fun onEnable(publication: TrackPublication, participant: RemoteParticipant) {}
fun onDisable(publication: TrackPublication, participant: RemoteParticipant) {}
fun onSubscribe(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onSubscribe(track: Track, publication: TrackPublication, participant: RemoteParticipant) {}
fun onFailToSubscribe(
videoTrack: RemoteVideoTrack,
sid: String,
exception: Exception,
participant: RemoteParticipant
) {
}
fun onUnsubscribe(
videoTrack: RemoteVideoTrackPublication,
participant: RemoteParticipant
) {
}
fun onSubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}
fun onFailToSubscribe(
dataTrack: RemoteDataTrackPublication,
exception: Exception,
track: Track,
publications: TrackPublication,
participant: RemoteParticipant
) {
}
fun onUnsubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}
fun onReceive(
data: ByteBuffer,
dataTrack: RemoteDataTrackPublication,
dataTrack: DataTrack,
participant: RemoteParticipant
) {
}
//fun networkQualityDidChange(networkQualityLevel: NetworkQualityLevel, participant: remoteParticipant)
fun switchedOffVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {}
fun switchedOnVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {}
// fun onChangePublishPriority(videoTrack: RemoteVideoTrackPublication, priority: PublishPriority, participant: RemoteParticipant)
// fun onChangePublishPriority(audioTrack: RemoteAudioTrackPublication, priority: PublishPriority, participant: RemoteParticipant)
// fun onChangePublishPriority(dataTrack: RemoteDataTrackPublication, priority: PublishPriority, participant: RemoteParticipant)
}
}
\ No newline at end of file
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
import org.webrtc.AudioTrack
import org.webrtc.MediaStreamTrack
open class AudioTrack(name: String, val rtcTrack: AudioTrack) :
Track(name, stateFromRTCMediaTrackState(rtcTrack.state())),
MediaTrack {
open class AudioTrack(name: String, override val rtcTrack: AudioTrack) :
MediaTrack(name, LivekitModels.TrackType.AUDIO, rtcTrack) {
override val mediaTrack: MediaStreamTrack
get() = rtcTrack
}
\ No newline at end of file
... ...
package io.livekit.android.room.track
interface AudioTrackPublication {
val audioTrack: AudioTrack?
}
\ No newline at end of file
package io.livekit.android.room.track
import livekit.LivekitModels
import org.webrtc.DataChannel
open class DataTrack(
name: String,
var rtcTrack: DataChannel? = null
) : Track(
name,
run {
if (rtcTrack != null) {
stateFromRTCDataChannelState(rtcTrack.state())
} else {
State.NONE
}
}) {
var dataChannel: DataChannel? = null
) : Track(name, LivekitModels.TrackType.DATA) {
var ordered: Boolean = TODO()
private set
var maxRetransmitTimeMs: Int = TODO()
... ... @@ -27,4 +19,8 @@ open class DataTrack(
maxRetransmitTimeMs = config.maxRetransmitTimeMs
maxRetransmits = config.maxRetransmits
}
}
\ No newline at end of file
override fun stop() {
dataChannel?.unregisterObserver()
}
}
... ...
package io.livekit.android.room.track
interface DataTrackPublication {
val dataTrack: DataTrack?
}
\ No newline at end of file
... ... @@ -7,16 +7,14 @@ import java.util.*
class LocalAudioTrack(
name: String,
audioOptions: AudioOptions? = null,
rtcTrack: org.webrtc.AudioTrack
) : AudioTrack(name, rtcTrack) {
mediaTrack: org.webrtc.AudioTrack
) : AudioTrack(name, mediaTrack) {
var enabled: Boolean
get() = rtcTrack.enabled()
set(value) {
rtcTrack.setEnabled(value)
}
var sid: Sid? = null
internal set
var audioOptions = audioOptions
private set
... ... @@ -31,7 +29,7 @@ class LocalAudioTrack(
val rtcAudioTrack =
factory.createAudioTrack(UUID.randomUUID().toString(), audioSource)
return LocalAudioTrack(name = name, rtcTrack = rtcAudioTrack)
return LocalAudioTrack(name = name, mediaTrack = rtcAudioTrack)
}
}
}
\ No newline at end of file
}
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
class LocalAudioTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :
LocalTrackPublication(info, track), AudioTrackPublication {
override val audioTrack: AudioTrack?
get() = track as? AudioTrack
}
... ... @@ -5,21 +5,20 @@ import java.nio.ByteBuffer
import java.util.*
class LocalDataTrack(
val options: DataTrackOptions,
rtcTrack: DataChannel
) : DataTrack(options.name, rtcTrack) {
var sid: Sid? = null
val options: DataTrackOptions
) : DataTrack(options.name) {
var sid: String? = null
internal set
var cid: Cid = Cid(UUID.randomUUID().toString())
var cid: String = UUID.randomUUID().toString()
fun sendString(message: String) {
val byteBuffer = ByteBuffer.wrap(message.toByteArray())
val buffer = DataChannel.Buffer(byteBuffer, false)
rtcTrack?.send(buffer)
dataChannel?.send(buffer)
}
fun sendBytes(byteBuffer: ByteBuffer) {
val buffer = DataChannel.Buffer(byteBuffer, true)
rtcTrack?.send(buffer)
dataChannel?.send(buffer)
}
}
\ No newline at end of file
}
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
class LocalDataTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :
LocalTrackPublication(info, track), DataTrackPublication {
override val dataTrack: DataTrack?
get() = track as? DataTrack
}
\ No newline at end of file
package io.livekit.android.room.track
import livekit.LivekitModels
open class LocalTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :
TrackPublication(info, track) {
val localTrack
get() = track
var priority = Track.Priority.STANDARD
private set
}
\ No newline at end of file
package io.livekit.android.room.track
data class LocalTrackPublicationOptions(val priority: Track.Priority)
data class LocalTrackPublicationOptions(val placeholder: Unit)
... ...
... ... @@ -16,8 +16,6 @@ class LocalVideoTrack(
capturer.startCapture(400, 400, 30)
}
var sid: Sid? = null
companion object {
internal fun createTrack(
peerConnectionFactory: PeerConnectionFactory,
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
class LocalVideoTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :
LocalTrackPublication(info, track), VideoTrackPublication {
override val videoTrack: VideoTrack?
get() = track as? VideoTrack
}
package io.livekit.android.room.track
import livekit.LivekitModels
import org.webrtc.MediaStreamTrack
interface MediaTrack {
val mediaTrack: MediaStreamTrack
}
\ No newline at end of file
open class MediaTrack(name: String, kind: LivekitModels.TrackType, open val rtcTrack: MediaStreamTrack) :
Track(name, kind) {
override fun stop() {
rtcTrack.setEnabled(false)
rtcTrack.dispose()
}
}
... ...
package io.livekit.android.room.track
class RemoteAudioTrack(
sid: Sid,
sid: String,
playbackEnabled: Boolean = true,
name: String,
rtcTrack: org.webrtc.AudioTrack
) : AudioTrack(name, rtcTrack), RemoteTrack {
mediaTrack: org.webrtc.AudioTrack
) : AudioTrack(name, mediaTrack), RemoteTrack {
override var sid: Sid = sid
override var sid: String = sid
var playbackEnabled = playbackEnabled
internal set
}
\ No newline at end of file
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
class RemoteAudioTrackPublication(
info: LivekitModels.TrackInfo,
track: Track? = null
) : RemoteTrackPublication(info, track), AudioTrackPublication {
override val audioTrack: AudioTrack?
get() = track as? AudioTrack
}
\ No newline at end of file
... ... @@ -3,7 +3,7 @@ package io.livekit.android.room.track
import org.webrtc.DataChannel
class RemoteDataTrack(
override var sid: Sid,
override var sid: String,
name: String,
rtcTrack: DataChannel
) :
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
class RemoteDataTrackPublication(
info: LivekitModels.TrackInfo,
track: Track? = null
) : RemoteTrackPublication(info, track), DataTrackPublication {
override val dataTrack: DataTrack?
get() = track as? DataTrack
}
\ No newline at end of file
package io.livekit.android.room.track
interface RemoteTrack {
var sid: Track.Sid
var sid: String
}
\ No newline at end of file
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
open class RemoteTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :
TrackPublication(info, track) {
val remoteTrack: Track?
get() = track
val trackSubscribed: Boolean
get() = track != null
val publishPriority = Track.Priority.STANDARD
}
\ No newline at end of file
package io.livekit.android.room.track
class RemoteVideoTrack(
override var sid: Sid,
override var sid: String,
var switchedOff: Boolean = false,
var priority: Priority? = null,
name: String,
rtcTrack: org.webrtc.VideoTrack
) : VideoTrack(name, rtcTrack), RemoteTrack
\ No newline at end of file
mediaTrack: org.webrtc.VideoTrack
) : VideoTrack(name, mediaTrack), RemoteTrack
... ...
package io.livekit.android.room.track
import livekit.LivekitModels
class RemoteVideoTrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) :
RemoteTrackPublication(info, track),
VideoTrackPublication {
override val videoTrack: VideoTrack?
get() = track as? VideoTrack
}
\ No newline at end of file
package io.livekit.android.room.track
import livekit.LivekitModels
import org.webrtc.DataChannel
import org.webrtc.MediaStreamTrack
open class Track(name: String, state: State) {
open class Track(name: String, kind: LivekitModels.TrackType) {
var name = name
internal set
var state = state
var kind = kind
internal set
inline class Sid(val sid: String)
inline class Cid(val cid: String)
enum class Priority {
STANDARD, HIGH, LOW;
}
var state: State = State.NONE
enum class State {
ENDED, LIVE, NONE;
}
open fun stop() {
// subclasses override to provide stop behavior
}
companion object {
fun stateFromRTCMediaTrackState(trackState: MediaStreamTrack.State): State {
return when (trackState) {
... ... @@ -42,6 +40,7 @@ open class Track(name: String, state: State) {
}
}
}
}
sealed class TrackException(message: String? = null, cause: Throwable? = null) :
... ...
... ... @@ -5,18 +5,28 @@ import livekit.LivekitModels
open class TrackPublication(info: LivekitModels.TrackInfo, track: Track? = null) {
var track: Track? = track
internal set
var trackName: String
var name: String
internal set
var trackSid: Track.Sid
var sid: String
private set
var kind: LivekitModels.TrackType
private set
var muted: Boolean
private set
init {
trackSid = Track.Sid(info.sid)
trackName = info.name
sid = info.sid
name = info.name
kind = info.type
muted = info.muted
}
fun updateFromInfo(info: LivekitModels.TrackInfo) {
trackSid = Track.Sid(info.sid)
trackName = info.name
sid = info.sid
name = info.name
kind = info.type
// TODO: forward mute status to listener
muted = info.muted
}
}
\ No newline at end of file
}
... ...
package io.livekit.android.room.track
import org.webrtc.MediaStreamTrack
import livekit.LivekitModels
import org.webrtc.VideoSink
import org.webrtc.VideoTrack
open class VideoTrack(name: String, val rtcTrack: VideoTrack) :
Track(name, stateFromRTCMediaTrackState(rtcTrack.state())),
MediaTrack {
override val mediaTrack: MediaStreamTrack
get() = rtcTrack
open class VideoTrack(name: String, override val rtcTrack: VideoTrack) :
MediaTrack(name, LivekitModels.TrackType.VIDEO, rtcTrack){
var enabled: Boolean
get() = rtcTrack.enabled()
... ... @@ -20,4 +16,4 @@ open class VideoTrack(name: String, val rtcTrack: VideoTrack) :
fun addRenderer(renderer: VideoSink) = rtcTrack.addSink(renderer)
fun removeRenderer(renderer: VideoSink) = rtcTrack.addSink(renderer)
}
\ No newline at end of file
}
... ...
package io.livekit.android.room.track
interface VideoTrackPublication {
val videoTrack: VideoTrack?
}
\ No newline at end of file
... ... @@ -4,19 +4,19 @@ import io.livekit.android.room.participant.Participant
import io.livekit.android.room.track.Track
import org.webrtc.DataChannel
fun DataChannel.unpackedTrackLabel(): Triple<Participant.Sid, Track.Sid, String> {
fun DataChannel.unpackedTrackLabel(): Triple<String, String, String> {
val parts = label().split("|")
val participantSid: Participant.Sid
val trackSid: Track.Sid
val participantSid: String
val trackSid: String
val name: String
if (parts.count() == 3) {
participantSid = Participant.Sid(parts[0])
trackSid = Track.Sid(parts[1])
participantSid = parts[0]
trackSid = parts[1]
name = parts[2]
} else {
participantSid = Participant.Sid("")
trackSid = Track.Sid("")
participantSid = ""
trackSid = ""
name = ""
}
... ...
... ... @@ -51,7 +51,7 @@ class CallActivity : AppCompatActivity() {
tabLayoutMediator =
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.text = participants[position].name
tab.text = participants[position].identity
}
tabLayoutMediator?.attach()
}
... ...
... ... @@ -84,7 +84,7 @@ class CallViewModel(
mutableRemoteParticipants.postValue(
room.remoteParticipants
.keys
.sortedBy { it.sid }
.sortedBy { it }
.mapNotNull { room.remoteParticipants[it] }
)
}
... ...
package io.livekit.android.sample
import android.provider.MediaStore
import android.view.View
import com.github.ajalt.timberkt.Timber
import com.xwray.groupie.viewbinding.BindableItem
import com.xwray.groupie.viewbinding.GroupieViewHolder
import io.livekit.android.room.Room
import io.livekit.android.room.participant.RemoteParticipant
import io.livekit.android.room.track.RemoteVideoTrackPublication
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.room.track.VideoTrackPublication
import io.livekit.android.room.track.*
import io.livekit.android.sample.databinding.ParticipantItemBinding
class ParticipantItem(
... ... @@ -30,11 +29,11 @@ class ParticipantItem(
remoteParticipant.listener = object : RemoteParticipant.Listener {
override fun onSubscribe(
videoTrack: RemoteVideoTrackPublication,
track: Track,
publication: TrackPublication,
participant: RemoteParticipant
) {
val track = videoTrack.videoTrack
if (track != null) {
if (track is VideoTrack) {
setupVideoIfNeeded(track, viewBinding)
}
}
... ... @@ -48,10 +47,8 @@ class ParticipantItem(
private fun getVideoTrack(): VideoTrack? {
return remoteParticipant
.remoteVideoTracks
.firstOrNull()
.let { it as? VideoTrackPublication }
?.videoTrack
.videoTracks.values
.firstOrNull()?.track as? VideoTrack
}
private fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) {
... ...