正在显示
8 个修改的文件
包含
116 行增加
和
93 行删除
| @@ -231,7 +231,7 @@ constructor( | @@ -231,7 +231,7 @@ constructor( | ||
| 231 | } | 231 | } |
| 232 | } | 232 | } |
| 233 | 233 | ||
| 234 | - fun handleSignalResponse(response: LivekitRtc.SignalResponse) { | 234 | + private fun handleSignalResponse(response: LivekitRtc.SignalResponse) { |
| 235 | if (!isConnected) { | 235 | if (!isConnected) { |
| 236 | // Only handle joins if not connected. | 236 | // Only handle joins if not connected. |
| 237 | if (response.hasJoin()) { | 237 | if (response.hasJoin()) { |
| @@ -307,7 +307,7 @@ constructor( | @@ -307,7 +307,7 @@ constructor( | ||
| 307 | const val SD_TYPE_ANSWER = "answer" | 307 | const val SD_TYPE_ANSWER = "answer" |
| 308 | const val SD_TYPE_OFFER = "offer" | 308 | const val SD_TYPE_OFFER = "offer" |
| 309 | const val SD_TYPE_PRANSWER = "pranswer" | 309 | const val SD_TYPE_PRANSWER = "pranswer" |
| 310 | - const val PROTOCOL_VERSION = 1; | 310 | + const val PROTOCOL_VERSION = 2; |
| 311 | 311 | ||
| 312 | private fun iceServer(url: String) = | 312 | private fun iceServer(url: String) = |
| 313 | PeerConnection.IceServer.builder(url).createIceServer() | 313 | PeerConnection.IceServer.builder(url).createIceServer() |
| @@ -29,7 +29,7 @@ constructor( | @@ -29,7 +29,7 @@ constructor( | ||
| 29 | val client: RTCClient, | 29 | val client: RTCClient, |
| 30 | private val pctFactory: PeerConnectionTransport.Factory, | 30 | private val pctFactory: PeerConnectionTransport.Factory, |
| 31 | @Named(InjectionNames.DISPATCHER_IO) ioDispatcher: CoroutineDispatcher, | 31 | @Named(InjectionNames.DISPATCHER_IO) ioDispatcher: CoroutineDispatcher, |
| 32 | -) : RTCClient.Listener { | 32 | +) : RTCClient.Listener, DataChannel.Observer { |
| 33 | 33 | ||
| 34 | var listener: Listener? = null | 34 | var listener: Listener? = null |
| 35 | var rtcConnected: Boolean = false | 35 | var rtcConnected: Boolean = false |
| @@ -41,7 +41,8 @@ constructor( | @@ -41,7 +41,8 @@ constructor( | ||
| 41 | private val subscriberObserver = SubscriberTransportObserver(this) | 41 | private val subscriberObserver = SubscriberTransportObserver(this) |
| 42 | internal lateinit var publisher: PeerConnectionTransport | 42 | internal lateinit var publisher: PeerConnectionTransport |
| 43 | private lateinit var subscriber: PeerConnectionTransport | 43 | private lateinit var subscriber: PeerConnectionTransport |
| 44 | - private lateinit var privateDataChannel: DataChannel | 44 | + internal var reliableDataChannel: DataChannel? = null |
| 45 | + internal var lossyDataChannel: DataChannel? = null | ||
| 45 | 46 | ||
| 46 | private val coroutineScope = CloseableCoroutineScope(SupervisorJob() + ioDispatcher) | 47 | private val coroutineScope = CloseableCoroutineScope(SupervisorJob() + ioDispatcher) |
| 47 | init { | 48 | init { |
| @@ -115,10 +116,13 @@ constructor( | @@ -115,10 +116,13 @@ constructor( | ||
| 115 | fun onUpdateSpeakers(speakers: List<LivekitRtc.SpeakerInfo>) | 116 | fun onUpdateSpeakers(speakers: List<LivekitRtc.SpeakerInfo>) |
| 116 | fun onDisconnect(reason: String) | 117 | fun onDisconnect(reason: String) |
| 117 | fun onFailToConnect(error: Exception) | 118 | fun onFailToConnect(error: Exception) |
| 119 | + fun onUserPacket(packet: LivekitRtc.UserPacket, kind: LivekitRtc.DataPacket.Kind) | ||
| 118 | } | 120 | } |
| 119 | 121 | ||
| 120 | companion object { | 122 | companion object { |
| 121 | - private const val PRIVATE_DATA_CHANNEL_LABEL = "_private" | 123 | + private const val RELIABLE_DATA_CHANNEL_LABEL = "_reliable" |
| 124 | + private const val LOSSY_DATA_CHANNEL_LABEL = "_lossy" | ||
| 125 | + internal const val MAX_DATA_PACKET_SIZE = 15000 | ||
| 122 | 126 | ||
| 123 | private val OFFER_CONSTRAINTS = MediaConstraints().apply { | 127 | private val OFFER_CONSTRAINTS = MediaConstraints().apply { |
| 124 | with(mandatory) { | 128 | with(mandatory) { |
| @@ -136,6 +140,8 @@ constructor( | @@ -136,6 +140,8 @@ constructor( | ||
| 136 | } | 140 | } |
| 137 | } | 141 | } |
| 138 | 142 | ||
| 143 | + //---------------------------------- RTCClient.Listener --------------------------------------// | ||
| 144 | + | ||
| 139 | override fun onJoin(info: LivekitRtc.JoinResponse) { | 145 | override fun onJoin(info: LivekitRtc.JoinResponse) { |
| 140 | val iceServers = mutableListOf<PeerConnection.IceServer>() | 146 | val iceServers = mutableListOf<PeerConnection.IceServer>() |
| 141 | for(serverInfo in info.iceServersList){ | 147 | for(serverInfo in info.iceServersList){ |
| @@ -170,10 +176,21 @@ constructor( | @@ -170,10 +176,21 @@ constructor( | ||
| 170 | publisher = pctFactory.create(rtcConfig, publisherObserver) | 176 | publisher = pctFactory.create(rtcConfig, publisherObserver) |
| 171 | subscriber = pctFactory.create(rtcConfig, subscriberObserver) | 177 | subscriber = pctFactory.create(rtcConfig, subscriberObserver) |
| 172 | 178 | ||
| 173 | - privateDataChannel = publisher.peerConnection.createDataChannel( | ||
| 174 | - PRIVATE_DATA_CHANNEL_LABEL, | ||
| 175 | - DataChannel.Init() | 179 | + val reliableInit = DataChannel.Init() |
| 180 | + reliableInit.ordered = true | ||
| 181 | + reliableDataChannel = publisher.peerConnection.createDataChannel( | ||
| 182 | + RELIABLE_DATA_CHANNEL_LABEL, | ||
| 183 | + reliableInit | ||
| 184 | + ) | ||
| 185 | + reliableDataChannel!!.registerObserver(this) | ||
| 186 | + val lossyInit = DataChannel.Init() | ||
| 187 | + lossyInit.ordered = true | ||
| 188 | + lossyInit.maxRetransmits = 1 | ||
| 189 | + lossyDataChannel = publisher.peerConnection.createDataChannel( | ||
| 190 | + LOSSY_DATA_CHANNEL_LABEL, | ||
| 191 | + lossyInit | ||
| 176 | ) | 192 | ) |
| 193 | + | ||
| 177 | coroutineScope.launch { | 194 | coroutineScope.launch { |
| 178 | val sdpOffer = | 195 | val sdpOffer = |
| 179 | when (val outcome = publisher.peerConnection.createOffer(OFFER_CONSTRAINTS)) { | 196 | when (val outcome = publisher.peerConnection.createOffer(OFFER_CONSTRAINTS)) { |
| @@ -302,4 +319,31 @@ constructor( | @@ -302,4 +319,31 @@ constructor( | ||
| 302 | override fun onError(error: Exception) { | 319 | override fun onError(error: Exception) { |
| 303 | listener?.onFailToConnect(error) | 320 | listener?.onFailToConnect(error) |
| 304 | } | 321 | } |
| 322 | + | ||
| 323 | + //--------------------------------- DataChannel.Observer ------------------------------------// | ||
| 324 | + | ||
| 325 | + override fun onBufferedAmountChange(previousAmount: Long) { | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + override fun onStateChange() { | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + override fun onMessage(buffer: DataChannel.Buffer?) { | ||
| 332 | + if (buffer == null) { | ||
| 333 | + return | ||
| 334 | + } | ||
| 335 | + val dp = LivekitRtc.DataPacket.parseFrom(buffer.data) | ||
| 336 | + when (dp.valueCase) { | ||
| 337 | + LivekitRtc.DataPacket.ValueCase.SPEAKER -> { | ||
| 338 | + listener?.onUpdateSpeakers(dp.speaker.speakersList) | ||
| 339 | + } | ||
| 340 | + LivekitRtc.DataPacket.ValueCase.USER -> { | ||
| 341 | + listener?.onUserPacket(dp.user, dp.kind) | ||
| 342 | + } | ||
| 343 | + LivekitRtc.DataPacket.ValueCase.VALUE_NOT_SET, | ||
| 344 | + null -> { | ||
| 345 | + Timber.v { "invalid value for data packet" } | ||
| 346 | + } | ||
| 347 | + } | ||
| 348 | + } | ||
| 305 | } | 349 | } |
| @@ -257,6 +257,17 @@ constructor( | @@ -257,6 +257,17 @@ constructor( | ||
| 257 | /** | 257 | /** |
| 258 | * @suppress | 258 | * @suppress |
| 259 | */ | 259 | */ |
| 260 | + override fun onUserPacket(packet: LivekitRtc.UserPacket, kind: LivekitRtc.DataPacket.Kind) { | ||
| 261 | + val participant = remoteParticipants[packet.participantSid] ?: return | ||
| 262 | + val data = packet.payload.toByteArray() | ||
| 263 | + | ||
| 264 | + listener?.onDataReceived(data, participant, this) | ||
| 265 | + participant.listener?.onDataReceived(data, participant) | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + /** | ||
| 269 | + * @suppress | ||
| 270 | + */ | ||
| 260 | override fun onDisconnect(reason: String) { | 271 | override fun onDisconnect(reason: String) { |
| 261 | Timber.v { "engine did disconnect: $reason" } | 272 | Timber.v { "engine did disconnect: $reason" } |
| 262 | handleDisconnect() | 273 | handleDisconnect() |
| @@ -333,17 +344,6 @@ constructor( | @@ -333,17 +344,6 @@ constructor( | ||
| 333 | 344 | ||
| 334 | /** | 345 | /** |
| 335 | * @suppress | 346 | * @suppress |
| 336 | - */ | ||
| 337 | - override fun onDataReceived( | ||
| 338 | - data: ByteBuffer, | ||
| 339 | - dataTrack: DataTrack, | ||
| 340 | - participant: RemoteParticipant | ||
| 341 | - ) { | ||
| 342 | - listener?.onDataReceived(data, dataTrack, participant, this) | ||
| 343 | - } | ||
| 344 | - | ||
| 345 | - /** | ||
| 346 | - * @suppress | ||
| 347 | * // TODO(@dl): can this be moved out of Room/SDK? | 347 | * // TODO(@dl): can this be moved out of Room/SDK? |
| 348 | */ | 348 | */ |
| 349 | fun initVideoRenderer(viewRenderer: SurfaceViewRenderer) { | 349 | fun initVideoRenderer(viewRenderer: SurfaceViewRenderer) { |
| @@ -440,9 +440,9 @@ interface RoomListener { | @@ -440,9 +440,9 @@ interface RoomListener { | ||
| 440 | fun onTrackUnsubscribed(track: Track, publications: TrackPublication, participant: RemoteParticipant, room: Room) {} | 440 | fun onTrackUnsubscribed(track: Track, publications: TrackPublication, participant: RemoteParticipant, room: Room) {} |
| 441 | 441 | ||
| 442 | /** | 442 | /** |
| 443 | - * Message received over a [DataTrack] | 443 | + * Received data published by another participant |
| 444 | */ | 444 | */ |
| 445 | - fun onDataReceived(data: ByteBuffer, dataTrack: DataTrack, participant: RemoteParticipant, room: Room) {} | 445 | + fun onDataReceived(data: ByteArray, participant: RemoteParticipant, room: Room) {} |
| 446 | } | 446 | } |
| 447 | 447 | ||
| 448 | sealed class RoomException(message: String? = null, cause: Throwable? = null) : | 448 | sealed class RoomException(message: String? = null, cause: Throwable? = null) : |
| @@ -2,13 +2,16 @@ package io.livekit.android.room.participant | @@ -2,13 +2,16 @@ package io.livekit.android.room.participant | ||
| 2 | 2 | ||
| 3 | import android.content.Context | 3 | import android.content.Context |
| 4 | import com.github.ajalt.timberkt.Timber | 4 | import com.github.ajalt.timberkt.Timber |
| 5 | +import com.google.protobuf.ByteString | ||
| 5 | import dagger.assisted.Assisted | 6 | import dagger.assisted.Assisted |
| 6 | import dagger.assisted.AssistedFactory | 7 | import dagger.assisted.AssistedFactory |
| 7 | import dagger.assisted.AssistedInject | 8 | import dagger.assisted.AssistedInject |
| 8 | import io.livekit.android.room.RTCEngine | 9 | import io.livekit.android.room.RTCEngine |
| 9 | import io.livekit.android.room.track.* | 10 | import io.livekit.android.room.track.* |
| 10 | import livekit.LivekitModels | 11 | import livekit.LivekitModels |
| 12 | +import livekit.LivekitRtc | ||
| 11 | import org.webrtc.* | 13 | import org.webrtc.* |
| 14 | +import java.nio.ByteBuffer | ||
| 12 | 15 | ||
| 13 | class LocalParticipant | 16 | class LocalParticipant |
| 14 | @AssistedInject | 17 | @AssistedInject |
| @@ -107,39 +110,6 @@ internal constructor( | @@ -107,39 +110,6 @@ internal constructor( | ||
| 107 | publishListener?.onPublishSuccess(publication) | 110 | publishListener?.onPublishSuccess(publication) |
| 108 | } | 111 | } |
| 109 | 112 | ||
| 110 | - suspend fun publishDataTrack( | ||
| 111 | - track: LocalDataTrack, | ||
| 112 | - publishListener: PublishListener? = null | ||
| 113 | - ) { | ||
| 114 | - if (localTrackPublications.any { it.track == track }) { | ||
| 115 | - publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published")) | ||
| 116 | - return | ||
| 117 | - } | ||
| 118 | - | ||
| 119 | - // data track cid isn't ready until peer connection creates it, so we'll use name | ||
| 120 | - val cid = track.name | ||
| 121 | - val trackInfo = | ||
| 122 | - engine.addTrack(cid = cid, name = track.name, track.kind) | ||
| 123 | - val publication = LocalTrackPublication(trackInfo, track, this) | ||
| 124 | - | ||
| 125 | - val config = DataChannel.Init().apply { | ||
| 126 | - ordered = track.options.ordered | ||
| 127 | - maxRetransmitTimeMs = track.options.maxRetransmitTimeMs | ||
| 128 | - maxRetransmits = track.options.maxRetransmits | ||
| 129 | - } | ||
| 130 | - | ||
| 131 | - val dataChannel = engine.publisher.peerConnection.createDataChannel(track.name, config) | ||
| 132 | - if (dataChannel == null) { | ||
| 133 | - publishListener?.onPublishFailure(TrackException.PublishException("could not create data channel")) | ||
| 134 | - return | ||
| 135 | - } | ||
| 136 | - track.dataChannel = dataChannel | ||
| 137 | - track.updateConfig(config) | ||
| 138 | - addTrackPublication(publication) | ||
| 139 | - | ||
| 140 | - publishListener?.onPublishSuccess(publication) | ||
| 141 | - } | ||
| 142 | - | ||
| 143 | fun unpublishTrack(track: Track) { | 113 | fun unpublishTrack(track: Track) { |
| 144 | val publication = localTrackPublications.firstOrNull { it.track == track } | 114 | val publication = localTrackPublications.firstOrNull { it.track == track } |
| 145 | if (publication === null) { | 115 | if (publication === null) { |
| @@ -156,9 +126,47 @@ internal constructor( | @@ -156,9 +126,47 @@ internal constructor( | ||
| 156 | LivekitModels.TrackType.AUDIO -> audioTracks.remove(sid) | 126 | LivekitModels.TrackType.AUDIO -> audioTracks.remove(sid) |
| 157 | LivekitModels.TrackType.VIDEO -> videoTracks.remove(sid) | 127 | LivekitModels.TrackType.VIDEO -> videoTracks.remove(sid) |
| 158 | LivekitModels.TrackType.DATA -> dataTracks.remove(sid) | 128 | LivekitModels.TrackType.DATA -> dataTracks.remove(sid) |
| 129 | + else -> {} | ||
| 159 | } | 130 | } |
| 160 | } | 131 | } |
| 161 | 132 | ||
| 133 | + /** | ||
| 134 | + * Publish a new data payload to the room. Data will be forwarded to each participant in the room. | ||
| 135 | + * Each payload must not exceed 15k in size | ||
| 136 | + * | ||
| 137 | + * @param data payload to send | ||
| 138 | + * @param reliability for delivery guarantee, use RELIABLE. for fastest delivery without guarantee, use LOSSY | ||
| 139 | + */ | ||
| 140 | + fun publishData(data: ByteArray, reliability: DataPublishReliability) { | ||
| 141 | + if (data.size > RTCEngine.MAX_DATA_PACKET_SIZE) { | ||
| 142 | + throw IllegalArgumentException("cannot publish data larger than " + RTCEngine.MAX_DATA_PACKET_SIZE) | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + val kind = when (reliability) { | ||
| 146 | + DataPublishReliability.RELIABLE -> LivekitRtc.DataPacket.Kind.RELIABLE | ||
| 147 | + DataPublishReliability.LOSSY -> LivekitRtc.DataPacket.Kind.LOSSY | ||
| 148 | + } | ||
| 149 | + val channel = when (reliability) { | ||
| 150 | + DataPublishReliability.RELIABLE -> engine.reliableDataChannel | ||
| 151 | + DataPublishReliability.LOSSY -> engine.lossyDataChannel | ||
| 152 | + } ?: throw TrackException.PublishException("data channel not established") | ||
| 153 | + | ||
| 154 | + val userPacket = LivekitRtc.UserPacket.newBuilder(). | ||
| 155 | + setPayload(ByteString.copyFrom(data)). | ||
| 156 | + setParticipantSid(sid). | ||
| 157 | + build() | ||
| 158 | + val dataPacket = LivekitRtc.DataPacket.newBuilder(). | ||
| 159 | + setUser(userPacket). | ||
| 160 | + setKind(kind). | ||
| 161 | + build() | ||
| 162 | + val buf = DataChannel.Buffer( | ||
| 163 | + ByteBuffer.wrap(dataPacket.toByteArray()), | ||
| 164 | + true, | ||
| 165 | + ) | ||
| 166 | + | ||
| 167 | + channel.send(buf) | ||
| 168 | + } | ||
| 169 | + | ||
| 162 | override fun updateFromInfo(info: LivekitModels.ParticipantInfo) { | 170 | override fun updateFromInfo(info: LivekitModels.ParticipantInfo) { |
| 163 | super.updateFromInfo(info) | 171 | super.updateFromInfo(info) |
| 164 | 172 | ||
| @@ -175,11 +183,11 @@ internal constructor( | @@ -175,11 +183,11 @@ internal constructor( | ||
| 175 | track: T, | 183 | track: T, |
| 176 | sid: String | 184 | sid: String |
| 177 | ) where T : MediaTrack { | 185 | ) where T : MediaTrack { |
| 178 | - val senders = engine.publisher?.peerConnection?.senders ?: return | 186 | + val senders = engine.publisher.peerConnection.senders ?: return |
| 179 | for (sender in senders) { | 187 | for (sender in senders) { |
| 180 | val t = sender.track() ?: continue | 188 | val t = sender.track() ?: continue |
| 181 | if (t == track.rtcTrack) { | 189 | if (t == track.rtcTrack) { |
| 182 | - engine.publisher?.peerConnection?.removeTrack(sender) | 190 | + engine.publisher.peerConnection.removeTrack(sender) |
| 183 | } | 191 | } |
| 184 | } | 192 | } |
| 185 | } | 193 | } |
| @@ -161,12 +161,7 @@ interface ParticipantListener { | @@ -161,12 +161,7 @@ interface ParticipantListener { | ||
| 161 | } | 161 | } |
| 162 | 162 | ||
| 163 | /** | 163 | /** |
| 164 | - * Data was received on a data track | 164 | + * Received data published by another participant |
| 165 | */ | 165 | */ |
| 166 | - fun onDataReceived( | ||
| 167 | - data: ByteBuffer, | ||
| 168 | - dataTrack: DataTrack, | ||
| 169 | - participant: RemoteParticipant | ||
| 170 | - ) { | ||
| 171 | - } | 166 | + fun onDataReceived(data: ByteArray, participant: RemoteParticipant) {} |
| 172 | } | 167 | } |
| @@ -144,8 +144,7 @@ class RemoteParticipant( | @@ -144,8 +144,7 @@ class RemoteParticipant( | ||
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | override fun onMessage(buffer: DataChannel.Buffer) { | 146 | override fun onMessage(buffer: DataChannel.Buffer) { |
| 147 | - internalListener?.onDataReceived(buffer.data, track, this@RemoteParticipant) | ||
| 148 | - listener?.onDataReceived(buffer.data, track, this@RemoteParticipant) | 147 | + |
| 149 | } | 148 | } |
| 150 | }) | 149 | }) |
| 151 | internalListener?.onTrackSubscribed(track, publication, participant = this) | 150 | internalListener?.onTrackSubscribed(track, publication, participant = this) |
| @@ -12,40 +12,12 @@ open class Track( | @@ -12,40 +12,12 @@ open class Track( | ||
| 12 | internal set | 12 | internal set |
| 13 | var kind = kind | 13 | var kind = kind |
| 14 | internal set | 14 | internal set |
| 15 | - var state: State = State.NONE | ||
| 16 | var sid: String? = null | 15 | var sid: String? = null |
| 17 | internal set | 16 | internal set |
| 18 | 17 | ||
| 19 | - enum class State { | ||
| 20 | - ENDED, LIVE, NONE; | ||
| 21 | - } | ||
| 22 | - | ||
| 23 | open fun stop() { | 18 | open fun stop() { |
| 24 | // subclasses override to provide stop behavior | 19 | // subclasses override to provide stop behavior |
| 25 | } | 20 | } |
| 26 | - | ||
| 27 | - companion object { | ||
| 28 | - fun stateFromRTCMediaTrackState(trackState: MediaStreamTrack.State): State { | ||
| 29 | - return when (trackState) { | ||
| 30 | - MediaStreamTrack.State.ENDED -> State.ENDED | ||
| 31 | - MediaStreamTrack.State.LIVE -> State.LIVE | ||
| 32 | - } | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - fun stateFromRTCDataChannelState(dataChannelState: DataChannel.State): State { | ||
| 36 | - return when (dataChannelState) { | ||
| 37 | - DataChannel.State.CONNECTING, | ||
| 38 | - DataChannel.State.OPEN -> { | ||
| 39 | - State.LIVE | ||
| 40 | - } | ||
| 41 | - DataChannel.State.CLOSING, | ||
| 42 | - DataChannel.State.CLOSED -> { | ||
| 43 | - State.ENDED | ||
| 44 | - } | ||
| 45 | - } | ||
| 46 | - } | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | } | 21 | } |
| 50 | 22 | ||
| 51 | sealed class TrackException(message: String? = null, cause: Throwable? = null) : | 23 | sealed class TrackException(message: String? = null, cause: Throwable? = null) : |
-
请 注册 或 登录 后发表评论