David Liu

LocalParticipant

<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
... ...
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="KotlinThrowableNotThrown" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>
\ No newline at end of file
... ...
... ... @@ -47,7 +47,7 @@ constructor(
private val publisherObserver = PublisherTransportObserver(this)
private val subscriberObserver = SubscriberTransportObserver(this)
private val publisher: PeerConnectionTransport
internal val publisher: PeerConnectionTransport
private val subscriber: PeerConnectionTransport
private var privateDataChannel: DataChannel
... ...
... ... @@ -4,6 +4,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.livekit.android.ConnectOptions
import io.livekit.android.room.participant.LocalParticipant
import io.livekit.android.room.participant.RemoteParticipant
class Room
... ... @@ -13,7 +14,26 @@ constructor(
private val engine: RTCEngine,
) {
enum class State {
CONNECTING,
CONNECTED,
DISCONNECTED,
RECONNECTING;
}
inline class Sid(val sid: String)
var listener: Listener? = null
var sid: Sid? = null
private set
var name: String? = null
private set
var state: State = State.DISCONNECTED
private set
var localParticipant: LocalParticipant? = TODO()
suspend fun connect(url: String, token: String, isSecure: Boolean) {
engine.join(url, token, isSecure)
}
... ... @@ -23,7 +43,6 @@ constructor(
fun create(connectOptions: ConnectOptions): Room
}
interface Listener {
fun onConnect(room: Room)
fun onDisconnect(room: Room, error: Exception)
... ...
package io.livekit.android.room.participant
import com.github.ajalt.timberkt.Timber
import io.livekit.android.room.RTCEngine
import io.livekit.android.room.track.*
import livekit.Model
import org.webrtc.DataChannel
import org.webrtc.RtpTransceiver
class LocalParticipant(sid: Sid, name: String? = null) :
Participant(sid, name) {
constructor(info: Model.ParticipantInfo, engine: RTCEngine) : this(
Sid(info.sid),
info.identity
) {
metadata = info.metadata
this.engine = engine
}
private val streamId = "stream"
val localAudioTrackPublications
get() = audioTracks.values.toList()
val localVideoTrackPublications
get() = videoTracks.values.toList()
val localDataTrackPublications
get() = dataTracks.values.toList()
var engine: RTCEngine? = null
val listener: Listener? = null
suspend fun publishAudioTrack(
track: LocalAudioTrack,
options: LocalTrackPublicationOptions? = null
) {
if (localAudioTrackPublications.any { it.track == track }) {
listener?.onFailToPublishAudioTrack(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 = Model.TrackType.AUDIO)
val transInit = RtpTransceiver.RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(streamId)
)
val transceiver =
engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
if (transceiver == null) {
listener?.onFailToPublishAudioTrack(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)
}
suspend fun publishVideoTrack(
track: LocalVideoTrack,
options: LocalTrackPublicationOptions? = null
) {
if (localVideoTrackPublications.any { it.track == track }) {
listener?.onFailToPublishVideoTrack(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 = Model.TrackType.VIDEO)
val transInit = RtpTransceiver.RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(streamId)
)
val transceiver =
engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
if (transceiver == null) {
listener?.onFailToPublishVideoTrack(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)
}
suspend fun publishDataTrack(
track: LocalDataTrack,
options: LocalTrackPublicationOptions? = null
) {
if (localDataTrackPublications.any { it.track == track }) {
listener?.onFailToPublishDataTrack(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 = Model.TrackType.DATA)
val publication = LocalDataTrackPublication(trackInfo, track)
val trackSid = Track.Sid(trackInfo.sid)
track.sid = trackSid
val config = DataChannel.Init().apply {
ordered = track.options.ordered
maxRetransmitTimeMs = track.options.maxRetransmitTimeMs
maxRetransmits = track.options.maxRetransmits
}
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." }
return
}
unpublishMediaTrack(track, sid, audioTracks)
}
fun unpublishVideoTrack(track: LocalVideoTrack) {
val sid = track.sid ?: run {
Timber.d { "this track was never published." }
return
}
unpublishMediaTrack(track, sid, audioTracks)
}
fun unpublishDataTrack(track: LocalDataTrack) {
val sid = track.sid ?: run {
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
}
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)
val senders = engine?.publisher?.peerConnection?.senders ?: return
for (sender in senders) {
val t = sender.track() ?: continue
if (t == track.mediaTrack) {
engine?.publisher?.peerConnection?.removeTrack(sender)
}
}
}
interface Listener {
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
}
}
\ No newline at end of file
... ...
... ... @@ -2,7 +2,7 @@ package io.livekit.android.room.participant
import io.livekit.android.room.track.*
open class Participant(var sid: String, name: String? = null) {
open class Participant(var sid: Sid, name: String? = null) {
inline class Sid(val sid: String)
var metadata: String? = null
... ...
... ... @@ -14,10 +14,10 @@ import org.webrtc.VideoTrack
import java.nio.ByteBuffer
class RemoteParticipant(
sid: String, name: String? = null
sid: Sid, name: String? = null
) : Participant(sid, name), RemoteDataTrack.Listener {
constructor(info: Model.ParticipantInfo) : this(info.sid, info.identity) {
constructor(info: Model.ParticipantInfo) : this(Sid(info.sid), info.identity) {
updateFromInfo(info)
}
... ... @@ -42,7 +42,7 @@ class RemoteParticipant(
fun updateFromInfo(info: Model.ParticipantInfo) {
val hadInfo = hasInfo
sid = info.sid
sid = Sid(info.sid)
name = info.identity
participantInfo = info
metadata = info.metadata
... ...
... ... @@ -4,14 +4,27 @@ import org.webrtc.DataChannel
open class DataTrack(
name: String,
val rtcTrack: DataChannel
) : Track(name, stateFromRTCDataChannelState(rtcTrack.state())) {
var rtcTrack: DataChannel? = null
) : Track(
name,
run {
if (rtcTrack != null) {
stateFromRTCDataChannelState(rtcTrack.state())
} else {
State.NONE
}
}) {
/**
* TODO: These values are only available at [DataChannel.Init]
*/
val ordered: Boolean = TODO()
val maxPacketLifeTime: Int = TODO()
val maxRetransmits: Int = TODO()
var ordered: Boolean = TODO()
private set
var maxRetransmitTimeMs: Int = TODO()
private set
var maxRetransmits: Int = TODO()
private set
fun updateConfig(config: DataChannel.Init) {
ordered = config.ordered
maxRetransmitTimeMs = config.maxRetransmitTimeMs
maxRetransmits = config.maxRetransmits
}
}
\ No newline at end of file
... ...
... ... @@ -2,7 +2,7 @@ package io.livekit.android.room.track
data class DataTrackOptions(
val ordered: Boolean = true,
val maxPacketLifetime: Int = -1,
val maxRetransmitTimeMs: Int = -1,
val maxRetransmits: Int = -1,
val name: String
)
... ...
package io.livekit.android.room.track
import livekit.Model
class LocalAudioTrackPublication(info: Model.TrackInfo, track: Track? = null) :
LocalTrackPublication(info, track), AudioTrackPublication {
override val audioTrack: AudioTrack?
get() = track as? AudioTrack
}
... ...
... ... @@ -2,6 +2,7 @@ package io.livekit.android.room.track
import org.webrtc.DataChannel
import java.nio.ByteBuffer
import java.util.*
class LocalDataTrack(
val options: DataTrackOptions,
... ... @@ -9,15 +10,16 @@ class LocalDataTrack(
) : DataTrack(options.name, rtcTrack) {
var sid: Sid? = null
internal set
var cid: Cid = Cid(UUID.randomUUID().toString())
fun sendString(message: String) {
val byteBuffer = ByteBuffer.wrap(message.toByteArray())
val buffer = DataChannel.Buffer(byteBuffer, false)
rtcTrack.send(buffer)
rtcTrack?.send(buffer)
}
fun sendBytes(byteBuffer: ByteBuffer) {
val buffer = DataChannel.Buffer(byteBuffer, true)
rtcTrack.send(buffer)
rtcTrack?.send(buffer)
}
}
\ No newline at end of file
... ...
package io.livekit.android.room.track
import livekit.Model
class LocalDataTrackPublication(info: Model.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.Model
open class LocalTrackPublication(info: Model.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)
... ...
package io.livekit.android.room.track
import livekit.Model
class LocalVideoTrackPublication(info: Model.TrackInfo, track: Track? = null) :
LocalTrackPublication(info, track), VideoTrackPublication {
override val videoTrack: VideoTrack?
get() = track as? VideoTrack
}
... ...