正在显示
15 个修改的文件
包含
323 行增加
和
21 行删除
| 1 | <component name="ProjectCodeStyleConfiguration"> | 1 | <component name="ProjectCodeStyleConfiguration"> |
| 2 | <code_scheme name="Project" version="173"> | 2 | <code_scheme name="Project" version="173"> |
| 3 | - <AndroidXmlCodeStyleSettings> | ||
| 4 | - <option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" /> | ||
| 5 | - </AndroidXmlCodeStyleSettings> | ||
| 6 | <JetCodeStyleSettings> | 3 | <JetCodeStyleSettings> |
| 4 | + <option name="PACKAGES_TO_USE_STAR_IMPORTS"> | ||
| 5 | + <value> | ||
| 6 | + <package name="java.util" alias="false" withSubpackages="false" /> | ||
| 7 | + <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> | ||
| 8 | + <package name="io.ktor" alias="false" withSubpackages="true" /> | ||
| 9 | + </value> | ||
| 10 | + </option> | ||
| 11 | + <option name="PACKAGES_IMPORT_LAYOUT"> | ||
| 12 | + <value> | ||
| 13 | + <package name="" alias="false" withSubpackages="true" /> | ||
| 14 | + <package name="java" alias="false" withSubpackages="true" /> | ||
| 15 | + <package name="javax" alias="false" withSubpackages="true" /> | ||
| 16 | + <package name="kotlin" alias="false" withSubpackages="true" /> | ||
| 17 | + <package name="" alias="true" withSubpackages="true" /> | ||
| 18 | + </value> | ||
| 19 | + </option> | ||
| 7 | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | 20 | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> |
| 8 | </JetCodeStyleSettings> | 21 | </JetCodeStyleSettings> |
| 9 | <codeStyleSettings language="XML"> | 22 | <codeStyleSettings language="XML"> |
.idea/inspectionProfiles/Project_Default.xml
0 → 100644
| @@ -47,7 +47,7 @@ constructor( | @@ -47,7 +47,7 @@ constructor( | ||
| 47 | 47 | ||
| 48 | private val publisherObserver = PublisherTransportObserver(this) | 48 | private val publisherObserver = PublisherTransportObserver(this) |
| 49 | private val subscriberObserver = SubscriberTransportObserver(this) | 49 | private val subscriberObserver = SubscriberTransportObserver(this) |
| 50 | - private val publisher: PeerConnectionTransport | 50 | + internal val publisher: PeerConnectionTransport |
| 51 | private val subscriber: PeerConnectionTransport | 51 | private val subscriber: PeerConnectionTransport |
| 52 | 52 | ||
| 53 | private var privateDataChannel: DataChannel | 53 | private var privateDataChannel: DataChannel |
| @@ -4,6 +4,7 @@ import dagger.assisted.Assisted | @@ -4,6 +4,7 @@ import dagger.assisted.Assisted | ||
| 4 | import dagger.assisted.AssistedFactory | 4 | import dagger.assisted.AssistedFactory |
| 5 | import dagger.assisted.AssistedInject | 5 | import dagger.assisted.AssistedInject |
| 6 | import io.livekit.android.ConnectOptions | 6 | import io.livekit.android.ConnectOptions |
| 7 | +import io.livekit.android.room.participant.LocalParticipant | ||
| 7 | import io.livekit.android.room.participant.RemoteParticipant | 8 | import io.livekit.android.room.participant.RemoteParticipant |
| 8 | 9 | ||
| 9 | class Room | 10 | class Room |
| @@ -13,7 +14,26 @@ constructor( | @@ -13,7 +14,26 @@ constructor( | ||
| 13 | private val engine: RTCEngine, | 14 | private val engine: RTCEngine, |
| 14 | ) { | 15 | ) { |
| 15 | 16 | ||
| 17 | + enum class State { | ||
| 18 | + CONNECTING, | ||
| 19 | + CONNECTED, | ||
| 20 | + DISCONNECTED, | ||
| 21 | + RECONNECTING; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + inline class Sid(val sid: String) | ||
| 25 | + | ||
| 16 | var listener: Listener? = null | 26 | var listener: Listener? = null |
| 27 | + | ||
| 28 | + var sid: Sid? = null | ||
| 29 | + private set | ||
| 30 | + var name: String? = null | ||
| 31 | + private set | ||
| 32 | + var state: State = State.DISCONNECTED | ||
| 33 | + private set | ||
| 34 | + var localParticipant: LocalParticipant? = TODO() | ||
| 35 | + | ||
| 36 | + | ||
| 17 | suspend fun connect(url: String, token: String, isSecure: Boolean) { | 37 | suspend fun connect(url: String, token: String, isSecure: Boolean) { |
| 18 | engine.join(url, token, isSecure) | 38 | engine.join(url, token, isSecure) |
| 19 | } | 39 | } |
| @@ -23,7 +43,6 @@ constructor( | @@ -23,7 +43,6 @@ constructor( | ||
| 23 | fun create(connectOptions: ConnectOptions): Room | 43 | fun create(connectOptions: ConnectOptions): Room |
| 24 | } | 44 | } |
| 25 | 45 | ||
| 26 | - | ||
| 27 | interface Listener { | 46 | interface Listener { |
| 28 | fun onConnect(room: Room) | 47 | fun onConnect(room: Room) |
| 29 | fun onDisconnect(room: Room, error: Exception) | 48 | fun onDisconnect(room: Room, error: Exception) |
livekit-android-sdk/src/main/java/io/livekit/android/room/participant/LocalParticipant.kt
0 → 100644
| 1 | +package io.livekit.android.room.participant | ||
| 2 | + | ||
| 3 | +import com.github.ajalt.timberkt.Timber | ||
| 4 | +import io.livekit.android.room.RTCEngine | ||
| 5 | +import io.livekit.android.room.track.* | ||
| 6 | +import livekit.Model | ||
| 7 | +import org.webrtc.DataChannel | ||
| 8 | +import org.webrtc.RtpTransceiver | ||
| 9 | + | ||
| 10 | +class LocalParticipant(sid: Sid, name: String? = null) : | ||
| 11 | + Participant(sid, name) { | ||
| 12 | + constructor(info: Model.ParticipantInfo, engine: RTCEngine) : this( | ||
| 13 | + Sid(info.sid), | ||
| 14 | + info.identity | ||
| 15 | + ) { | ||
| 16 | + metadata = info.metadata | ||
| 17 | + this.engine = engine | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + private val streamId = "stream" | ||
| 21 | + | ||
| 22 | + val localAudioTrackPublications | ||
| 23 | + get() = audioTracks.values.toList() | ||
| 24 | + val localVideoTrackPublications | ||
| 25 | + get() = videoTracks.values.toList() | ||
| 26 | + val localDataTrackPublications | ||
| 27 | + get() = dataTracks.values.toList() | ||
| 28 | + | ||
| 29 | + var engine: RTCEngine? = null | ||
| 30 | + val listener: Listener? = null | ||
| 31 | + | ||
| 32 | + suspend fun publishAudioTrack( | ||
| 33 | + track: LocalAudioTrack, | ||
| 34 | + options: LocalTrackPublicationOptions? = null | ||
| 35 | + ) { | ||
| 36 | + if (localAudioTrackPublications.any { it.track == track }) { | ||
| 37 | + listener?.onFailToPublishAudioTrack(TrackException.PublishException("Track has already been published")) | ||
| 38 | + return | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + val cid = track.rtcTrack.id() | ||
| 42 | + val engine = this.engine ?: run { | ||
| 43 | + listener?.onFailToPublishAudioTrack(IllegalStateException("engine is null!")) | ||
| 44 | + return | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + val trackInfo = | ||
| 48 | + engine.addTrack(cid = Track.Cid(cid), name = track.name, kind = Model.TrackType.AUDIO) | ||
| 49 | + val transInit = RtpTransceiver.RtpTransceiverInit( | ||
| 50 | + RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, | ||
| 51 | + listOf(streamId) | ||
| 52 | + ) | ||
| 53 | + val transceiver = | ||
| 54 | + engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) | ||
| 55 | + | ||
| 56 | + if (transceiver == null) { | ||
| 57 | + listener?.onFailToPublishAudioTrack(TrackException.PublishException("null sender returned from peer connection")) | ||
| 58 | + return | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + val publication = LocalAudioTrackPublication(trackInfo) | ||
| 62 | + val trackSid = Track.Sid(trackInfo.sid) | ||
| 63 | + track.sid = trackSid | ||
| 64 | + audioTracks[trackSid] = publication | ||
| 65 | + listener?.onPublishAudioTrack(track) | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + suspend fun publishVideoTrack( | ||
| 69 | + track: LocalVideoTrack, | ||
| 70 | + options: LocalTrackPublicationOptions? = null | ||
| 71 | + ) { | ||
| 72 | + | ||
| 73 | + if (localVideoTrackPublications.any { it.track == track }) { | ||
| 74 | + listener?.onFailToPublishVideoTrack(TrackException.PublishException("Track has already been published")) | ||
| 75 | + return | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + val cid = track.rtcTrack.id() | ||
| 79 | + val engine = this.engine ?: run { | ||
| 80 | + listener?.onFailToPublishVideoTrack(IllegalStateException("engine is null!")) | ||
| 81 | + return | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + val trackInfo = | ||
| 85 | + engine.addTrack(cid = Track.Cid(cid), name = track.name, kind = Model.TrackType.VIDEO) | ||
| 86 | + val transInit = RtpTransceiver.RtpTransceiverInit( | ||
| 87 | + RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, | ||
| 88 | + listOf(streamId) | ||
| 89 | + ) | ||
| 90 | + val transceiver = | ||
| 91 | + engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) | ||
| 92 | + | ||
| 93 | + if (transceiver == null) { | ||
| 94 | + listener?.onFailToPublishVideoTrack(TrackException.PublishException("null sender returned from peer connection")) | ||
| 95 | + return | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + val publication = LocalVideoTrackPublication(trackInfo) | ||
| 99 | + val trackSid = Track.Sid(trackInfo.sid) | ||
| 100 | + track.sid = trackSid | ||
| 101 | + videoTracks[trackSid] = publication | ||
| 102 | + listener?.onPublishVideoTrack(track) | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + suspend fun publishDataTrack( | ||
| 106 | + track: LocalDataTrack, | ||
| 107 | + options: LocalTrackPublicationOptions? = null | ||
| 108 | + ) { | ||
| 109 | + | ||
| 110 | + if (localDataTrackPublications.any { it.track == track }) { | ||
| 111 | + listener?.onFailToPublishDataTrack(TrackException.PublishException("Track has already been published")) | ||
| 112 | + return | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + val cid = track.cid | ||
| 116 | + val engine = this.engine ?: run { | ||
| 117 | + listener?.onFailToPublishDataTrack(IllegalStateException("engine is null!")) | ||
| 118 | + return | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + val trackInfo = | ||
| 122 | + engine.addTrack(cid = cid, name = track.name, kind = Model.TrackType.DATA) | ||
| 123 | + val publication = LocalDataTrackPublication(trackInfo, track) | ||
| 124 | + val trackSid = Track.Sid(trackInfo.sid) | ||
| 125 | + track.sid = trackSid | ||
| 126 | + | ||
| 127 | + val config = DataChannel.Init().apply { | ||
| 128 | + ordered = track.options.ordered | ||
| 129 | + maxRetransmitTimeMs = track.options.maxRetransmitTimeMs | ||
| 130 | + maxRetransmits = track.options.maxRetransmits | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + val dataChannel = engine.publisher.peerConnection.createDataChannel(track.name, config) | ||
| 134 | + if (dataChannel != null) { | ||
| 135 | + track.rtcTrack = dataChannel | ||
| 136 | + track.updateConfig(config) | ||
| 137 | + dataTracks[trackSid] = publication | ||
| 138 | + listener?.onPublishDataTrack(track) | ||
| 139 | + } else { | ||
| 140 | + Timber.d { "error creating data channel with name: $name" } | ||
| 141 | + unpublishDataTrack(track) | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + fun unpublishAudioTrack(track: LocalAudioTrack) { | ||
| 146 | + val sid = track.sid ?: run { | ||
| 147 | + Timber.d { "this track was never published." } | ||
| 148 | + return | ||
| 149 | + } | ||
| 150 | + unpublishMediaTrack(track, sid, audioTracks) | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + fun unpublishVideoTrack(track: LocalVideoTrack) { | ||
| 154 | + val sid = track.sid ?: run { | ||
| 155 | + Timber.d { "this track was never published." } | ||
| 156 | + return | ||
| 157 | + } | ||
| 158 | + unpublishMediaTrack(track, sid, audioTracks) | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + fun unpublishDataTrack(track: LocalDataTrack) { | ||
| 162 | + | ||
| 163 | + val sid = track.sid ?: run { | ||
| 164 | + Timber.d { "this track was never published." } | ||
| 165 | + return | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + val publication = dataTracks.remove(sid) as? LocalDataTrackPublication | ||
| 169 | + if (publication == null) { | ||
| 170 | + Timber.d { "track was not published with sid: $sid" } | ||
| 171 | + return | ||
| 172 | + } | ||
| 173 | + publication.dataTrack?.rtcTrack?.dispose() | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + private fun <T> unpublishMediaTrack( | ||
| 177 | + track: T, | ||
| 178 | + sid: Track.Sid, | ||
| 179 | + publications: MutableMap<Track.Sid, TrackPublication> | ||
| 180 | + ) where T : Track, T : MediaTrack { | ||
| 181 | + val removed = publications.remove(sid) | ||
| 182 | + if (removed != null) { | ||
| 183 | + Timber.d { "track was not published with sid: $sid" } | ||
| 184 | + return | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + track.mediaTrack.setEnabled(false) | ||
| 188 | + val senders = engine?.publisher?.peerConnection?.senders ?: return | ||
| 189 | + for (sender in senders) { | ||
| 190 | + val t = sender.track() ?: continue | ||
| 191 | + if (t == track.mediaTrack) { | ||
| 192 | + engine?.publisher?.peerConnection?.removeTrack(sender) | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + interface Listener { | ||
| 198 | + | ||
| 199 | + fun onPublishAudioTrack(track: LocalAudioTrack) | ||
| 200 | + fun onFailToPublishAudioTrack(exception: Exception) | ||
| 201 | + fun onPublishVideoTrack(track: LocalVideoTrack) | ||
| 202 | + fun onFailToPublishVideoTrack(exception: Exception) | ||
| 203 | + fun onPublishDataTrack(track: LocalDataTrack) | ||
| 204 | + fun onFailToPublishDataTrack(exception: Exception) | ||
| 205 | + //fun onNetworkQualityLevelChange | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | +} |
| @@ -2,7 +2,7 @@ package io.livekit.android.room.participant | @@ -2,7 +2,7 @@ package io.livekit.android.room.participant | ||
| 2 | 2 | ||
| 3 | import io.livekit.android.room.track.* | 3 | import io.livekit.android.room.track.* |
| 4 | 4 | ||
| 5 | -open class Participant(var sid: String, name: String? = null) { | 5 | +open class Participant(var sid: Sid, name: String? = null) { |
| 6 | inline class Sid(val sid: String) | 6 | inline class Sid(val sid: String) |
| 7 | 7 | ||
| 8 | var metadata: String? = null | 8 | var metadata: String? = null |
| @@ -14,10 +14,10 @@ import org.webrtc.VideoTrack | @@ -14,10 +14,10 @@ import org.webrtc.VideoTrack | ||
| 14 | import java.nio.ByteBuffer | 14 | import java.nio.ByteBuffer |
| 15 | 15 | ||
| 16 | class RemoteParticipant( | 16 | class RemoteParticipant( |
| 17 | - sid: String, name: String? = null | 17 | + sid: Sid, name: String? = null |
| 18 | ) : Participant(sid, name), RemoteDataTrack.Listener { | 18 | ) : Participant(sid, name), RemoteDataTrack.Listener { |
| 19 | 19 | ||
| 20 | - constructor(info: Model.ParticipantInfo) : this(info.sid, info.identity) { | 20 | + constructor(info: Model.ParticipantInfo) : this(Sid(info.sid), info.identity) { |
| 21 | updateFromInfo(info) | 21 | updateFromInfo(info) |
| 22 | } | 22 | } |
| 23 | 23 | ||
| @@ -42,7 +42,7 @@ class RemoteParticipant( | @@ -42,7 +42,7 @@ class RemoteParticipant( | ||
| 42 | 42 | ||
| 43 | fun updateFromInfo(info: Model.ParticipantInfo) { | 43 | fun updateFromInfo(info: Model.ParticipantInfo) { |
| 44 | val hadInfo = hasInfo | 44 | val hadInfo = hasInfo |
| 45 | - sid = info.sid | 45 | + sid = Sid(info.sid) |
| 46 | name = info.identity | 46 | name = info.identity |
| 47 | participantInfo = info | 47 | participantInfo = info |
| 48 | metadata = info.metadata | 48 | metadata = info.metadata |
| @@ -4,14 +4,27 @@ import org.webrtc.DataChannel | @@ -4,14 +4,27 @@ import org.webrtc.DataChannel | ||
| 4 | 4 | ||
| 5 | open class DataTrack( | 5 | open class DataTrack( |
| 6 | name: String, | 6 | name: String, |
| 7 | - val rtcTrack: DataChannel | ||
| 8 | -) : Track(name, stateFromRTCDataChannelState(rtcTrack.state())) { | 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 | + }) { | ||
| 9 | 17 | ||
| 10 | - /** | ||
| 11 | - * TODO: These values are only available at [DataChannel.Init] | ||
| 12 | - */ | ||
| 13 | - val ordered: Boolean = TODO() | ||
| 14 | - val maxPacketLifeTime: Int = TODO() | ||
| 15 | - val maxRetransmits: Int = TODO() | ||
| 16 | - | 18 | + var ordered: Boolean = TODO() |
| 19 | + private set | ||
| 20 | + var maxRetransmitTimeMs: Int = TODO() | ||
| 21 | + private set | ||
| 22 | + var maxRetransmits: Int = TODO() | ||
| 23 | + private set | ||
| 24 | + | ||
| 25 | + fun updateConfig(config: DataChannel.Init) { | ||
| 26 | + ordered = config.ordered | ||
| 27 | + maxRetransmitTimeMs = config.maxRetransmitTimeMs | ||
| 28 | + maxRetransmits = config.maxRetransmits | ||
| 29 | + } | ||
| 17 | } | 30 | } |
| @@ -2,7 +2,7 @@ package io.livekit.android.room.track | @@ -2,7 +2,7 @@ package io.livekit.android.room.track | ||
| 2 | 2 | ||
| 3 | data class DataTrackOptions( | 3 | data class DataTrackOptions( |
| 4 | val ordered: Boolean = true, | 4 | val ordered: Boolean = true, |
| 5 | - val maxPacketLifetime: Int = -1, | 5 | + val maxRetransmitTimeMs: Int = -1, |
| 6 | val maxRetransmits: Int = -1, | 6 | val maxRetransmits: Int = -1, |
| 7 | val name: String | 7 | val name: String |
| 8 | ) | 8 | ) |
| @@ -2,6 +2,7 @@ package io.livekit.android.room.track | @@ -2,6 +2,7 @@ package io.livekit.android.room.track | ||
| 2 | 2 | ||
| 3 | import org.webrtc.DataChannel | 3 | import org.webrtc.DataChannel |
| 4 | import java.nio.ByteBuffer | 4 | import java.nio.ByteBuffer |
| 5 | +import java.util.* | ||
| 5 | 6 | ||
| 6 | class LocalDataTrack( | 7 | class LocalDataTrack( |
| 7 | val options: DataTrackOptions, | 8 | val options: DataTrackOptions, |
| @@ -9,15 +10,16 @@ class LocalDataTrack( | @@ -9,15 +10,16 @@ class LocalDataTrack( | ||
| 9 | ) : DataTrack(options.name, rtcTrack) { | 10 | ) : DataTrack(options.name, rtcTrack) { |
| 10 | var sid: Sid? = null | 11 | var sid: Sid? = null |
| 11 | internal set | 12 | internal set |
| 13 | + var cid: Cid = Cid(UUID.randomUUID().toString()) | ||
| 12 | 14 | ||
| 13 | fun sendString(message: String) { | 15 | fun sendString(message: String) { |
| 14 | val byteBuffer = ByteBuffer.wrap(message.toByteArray()) | 16 | val byteBuffer = ByteBuffer.wrap(message.toByteArray()) |
| 15 | val buffer = DataChannel.Buffer(byteBuffer, false) | 17 | val buffer = DataChannel.Buffer(byteBuffer, false) |
| 16 | - rtcTrack.send(buffer) | 18 | + rtcTrack?.send(buffer) |
| 17 | } | 19 | } |
| 18 | 20 | ||
| 19 | fun sendBytes(byteBuffer: ByteBuffer) { | 21 | fun sendBytes(byteBuffer: ByteBuffer) { |
| 20 | val buffer = DataChannel.Buffer(byteBuffer, true) | 22 | val buffer = DataChannel.Buffer(byteBuffer, true) |
| 21 | - rtcTrack.send(buffer) | 23 | + rtcTrack?.send(buffer) |
| 22 | } | 24 | } |
| 23 | } | 25 | } |
-
请 注册 或 登录 后发表评论