David Liu

Finish rtcclient fleshing

... ... @@ -12,6 +12,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
... ...
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
apply plugin: 'com.google.protobuf'
android {
... ... @@ -53,6 +54,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0'
implementation 'org.webrtc:google-webrtc:1.0.32006'
implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation "com.google.protobuf:protobuf-java:${versions.protobuf}"
... ...
... ... @@ -19,3 +19,26 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Kotlin Serialization Proguard Rules
########################################
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
# Change here com.yourcompany.yourpackage
-keep,includedescriptorclasses class io.livekit.android.**$$serializer { *; } # <-- change package name to your app's
-keepclassmembers class io.livekit.android.** { # <-- change package name to your app's
*** Companion;
}
-keepclasseswithmembers class io.livekit.android.** { # <-- change package name to your app's
kotlinx.serialization.KSerializer serializer(...);
}
\ No newline at end of file
... ...
package io.livekit.android.room
import kotlinx.serialization.Serializable
@Serializable
data class IceCandidateJSON(val sdp: String, val sdpMLineIndex: Int, val sdpMid: String?)
\ No newline at end of file
... ...
... ... @@ -2,6 +2,9 @@ package io.livekit.android.room
import com.github.ajalt.timberkt.Timber
import com.google.protobuf.util.JsonFormat
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import livekit.Model
import livekit.Rtc
import okhttp3.Request
... ... @@ -9,6 +12,7 @@ import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
import org.webrtc.IceCandidate
import org.webrtc.SessionDescription
import javax.inject.Inject
... ... @@ -102,6 +106,85 @@ constructor(
return sdBuilder.build()
}
fun sendOffer(offer: SessionDescription) {
val sd = toProtoSessionDescription(offer)
val request = Rtc.SignalRequest.newBuilder()
.setOffer(sd)
.build()
sendRequest(request)
}
fun sendAnswer(answer: SessionDescription) {
val sd = toProtoSessionDescription(answer)
val request = Rtc.SignalRequest.newBuilder()
.setAnswer(sd)
.build()
sendRequest(request)
}
fun sendCandidate(candidate: IceCandidate, target: Rtc.SignalTarget){
val iceCandidateJSON = IceCandidateJSON(
sdp = candidate.sdp,
sdpMid = candidate.sdpMid,
sdpMLineIndex = candidate.sdpMLineIndex
)
val trickleRequest = Rtc.TrickleRequest.newBuilder()
.setCandidateInit(Json.encodeToString(iceCandidateJSON))
.setTarget(target)
.build()
val request = Rtc.SignalRequest.newBuilder()
.setTrickle(trickleRequest)
.build()
sendRequest(request)
}
fun sendMuteTrack(trackSid: String, muted: Boolean) {
val muteRequest = Rtc.MuteTrackRequest.newBuilder()
.setSid(trackSid)
.setMuted(muted)
.build()
val request = Rtc.SignalRequest.newBuilder()
.setMute(muteRequest)
.build()
sendRequest(request)
}
fun sendAddTrack(cid: String, name: String, type: Model.TrackType) {
val addTrackRequest = Rtc.AddTrackRequest.newBuilder()
.setCid(cid)
.setName(name)
.setType(type)
.build()
val request = Rtc.SignalRequest.newBuilder()
.setAddTrack(addTrackRequest)
.build()
sendRequest(request)
}
fun sendRequest(request: Rtc.SignalRequest) {
Timber.v { "sending request: $request" }
if (!isConnected || currentWs != null) {
throw IllegalStateException("not connected!")
}
val message = toJson.print(request)
val sent = currentWs?.send(message) ?: false
if (!sent) {
Timber.d { "error sending request: $request" }
throw IllegalStateException()
}
}
fun handleSignalResponse(response: Rtc.SignalResponse) {
if (!isConnected) {
Timber.e { "Received response while not connected. ${toJson.print(response)}" }
... ... @@ -116,9 +199,16 @@ constructor(
val sd = fromProtoSessionDescription(response.offer)
listener?.onOffer(sd)
}
// Rtc.SignalResponse.MessageCase.TRICKLE -> {
// TODO()
// }
Rtc.SignalResponse.MessageCase.TRICKLE -> {
val iceCandidateJson =
Json.decodeFromString<IceCandidateJSON>(response.trickle.candidateInit)
val iceCandidate = IceCandidate(
iceCandidateJson.sdpMid,
iceCandidateJson.sdpMLineIndex,
iceCandidateJson.sdp
)
listener?.onTrickle(iceCandidate, response.trickle.target)
}
Rtc.SignalResponse.MessageCase.UPDATE -> {
listener?.onParticipantUpdate(response.update.participantsList)
}
... ... @@ -131,7 +221,6 @@ constructor(
Timber.v { "unhandled response type: ${response.messageCase.name}" }
}
}
}
interface Listener {
... ... @@ -140,7 +229,7 @@ constructor(
fun onAnswer(sessionDescription: SessionDescription)
fun onOffer(sessionDescription: SessionDescription)
//fun onTrickle(candidate: RTCIceCandidate, target: Rtc.SignalTarget)
fun onTrickle(candidate: IceCandidate, target: Rtc.SignalTarget)
fun onLocalTrackPublished(trackPublished: Rtc.TrackPublishedResponse)
fun onParticipantUpdate(updates: List<Model.ParticipantInfo>)
fun onClose(reason: String, code: Int)
... ...