David Liu

Finish rtcclient fleshing

@@ -12,6 +12,7 @@ buildscript { @@ -12,6 +12,7 @@ buildscript {
12 dependencies { 12 dependencies {
13 classpath 'com.android.tools.build:gradle:4.1.2' 13 classpath 'com.android.tools.build:gradle:4.1.2'
14 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
  15 + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
15 classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15' 16 classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15'
16 // NOTE: Do not place your application dependencies here; they belong 17 // NOTE: Do not place your application dependencies here; they belong
17 // in the individual module build.gradle files 18 // in the individual module build.gradle files
1 apply plugin: 'com.android.library' 1 apply plugin: 'com.android.library'
2 apply plugin: 'kotlin-android' 2 apply plugin: 'kotlin-android'
  3 +apply plugin: 'kotlinx-serialization'
3 apply plugin: 'com.google.protobuf' 4 apply plugin: 'com.google.protobuf'
4 5
5 android { 6 android {
@@ -53,6 +54,7 @@ dependencies { @@ -53,6 +54,7 @@ dependencies {
53 implementation fileTree(dir: 'libs', include: ['*.jar']) 54 implementation fileTree(dir: 'libs', include: ['*.jar'])
54 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 55 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
55 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' 56 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
  57 + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0'
56 implementation 'org.webrtc:google-webrtc:1.0.32006' 58 implementation 'org.webrtc:google-webrtc:1.0.32006'
57 implementation "com.squareup.okhttp3:okhttp:4.9.0" 59 implementation "com.squareup.okhttp3:okhttp:4.9.0"
58 implementation "com.google.protobuf:protobuf-java:${versions.protobuf}" 60 implementation "com.google.protobuf:protobuf-java:${versions.protobuf}"
@@ -19,3 +19,26 @@ @@ -19,3 +19,26 @@
19 # If you keep the line number information, uncomment this to 19 # If you keep the line number information, uncomment this to
20 # hide the original source file name. 20 # hide the original source file name.
21 #-renamesourcefileattribute SourceFile 21 #-renamesourcefileattribute SourceFile
  22 +
  23 +# Kotlin Serialization Proguard Rules
  24 +########################################
  25 +
  26 +-keepattributes *Annotation*, InnerClasses
  27 +-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
  28 +
  29 +# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
  30 +-keepclassmembers class kotlinx.serialization.json.** {
  31 + *** Companion;
  32 +}
  33 +-keepclasseswithmembers class kotlinx.serialization.json.** {
  34 + kotlinx.serialization.KSerializer serializer(...);
  35 +}
  36 +
  37 +# Change here com.yourcompany.yourpackage
  38 +-keep,includedescriptorclasses class io.livekit.android.**$$serializer { *; } # <-- change package name to your app's
  39 +-keepclassmembers class io.livekit.android.** { # <-- change package name to your app's
  40 + *** Companion;
  41 +}
  42 +-keepclasseswithmembers class io.livekit.android.** { # <-- change package name to your app's
  43 + kotlinx.serialization.KSerializer serializer(...);
  44 +}
  1 +package io.livekit.android.room
  2 +
  3 +import kotlinx.serialization.Serializable
  4 +
  5 +@Serializable
  6 +data class IceCandidateJSON(val sdp: String, val sdpMLineIndex: Int, val sdpMid: String?)
@@ -2,6 +2,9 @@ package io.livekit.android.room @@ -2,6 +2,9 @@ package io.livekit.android.room
2 2
3 import com.github.ajalt.timberkt.Timber 3 import com.github.ajalt.timberkt.Timber
4 import com.google.protobuf.util.JsonFormat 4 import com.google.protobuf.util.JsonFormat
  5 +import kotlinx.serialization.decodeFromString
  6 +import kotlinx.serialization.encodeToString
  7 +import kotlinx.serialization.json.Json
5 import livekit.Model 8 import livekit.Model
6 import livekit.Rtc 9 import livekit.Rtc
7 import okhttp3.Request 10 import okhttp3.Request
@@ -9,6 +12,7 @@ import okhttp3.Response @@ -9,6 +12,7 @@ import okhttp3.Response
9 import okhttp3.WebSocket 12 import okhttp3.WebSocket
10 import okhttp3.WebSocketListener 13 import okhttp3.WebSocketListener
11 import okio.ByteString 14 import okio.ByteString
  15 +import org.webrtc.IceCandidate
12 import org.webrtc.SessionDescription 16 import org.webrtc.SessionDescription
13 import javax.inject.Inject 17 import javax.inject.Inject
14 18
@@ -102,6 +106,85 @@ constructor( @@ -102,6 +106,85 @@ constructor(
102 return sdBuilder.build() 106 return sdBuilder.build()
103 } 107 }
104 108
  109 + fun sendOffer(offer: SessionDescription) {
  110 + val sd = toProtoSessionDescription(offer)
  111 + val request = Rtc.SignalRequest.newBuilder()
  112 + .setOffer(sd)
  113 + .build()
  114 +
  115 + sendRequest(request)
  116 + }
  117 +
  118 + fun sendAnswer(answer: SessionDescription) {
  119 + val sd = toProtoSessionDescription(answer)
  120 + val request = Rtc.SignalRequest.newBuilder()
  121 + .setAnswer(sd)
  122 + .build()
  123 +
  124 + sendRequest(request)
  125 + }
  126 +
  127 + fun sendCandidate(candidate: IceCandidate, target: Rtc.SignalTarget){
  128 + val iceCandidateJSON = IceCandidateJSON(
  129 + sdp = candidate.sdp,
  130 + sdpMid = candidate.sdpMid,
  131 + sdpMLineIndex = candidate.sdpMLineIndex
  132 + )
  133 +
  134 + val trickleRequest = Rtc.TrickleRequest.newBuilder()
  135 + .setCandidateInit(Json.encodeToString(iceCandidateJSON))
  136 + .setTarget(target)
  137 + .build()
  138 +
  139 + val request = Rtc.SignalRequest.newBuilder()
  140 + .setTrickle(trickleRequest)
  141 + .build()
  142 +
  143 + sendRequest(request)
  144 + }
  145 +
  146 + fun sendMuteTrack(trackSid: String, muted: Boolean) {
  147 + val muteRequest = Rtc.MuteTrackRequest.newBuilder()
  148 + .setSid(trackSid)
  149 + .setMuted(muted)
  150 + .build()
  151 +
  152 + val request = Rtc.SignalRequest.newBuilder()
  153 + .setMute(muteRequest)
  154 + .build()
  155 +
  156 + sendRequest(request)
  157 + }
  158 +
  159 + fun sendAddTrack(cid: String, name: String, type: Model.TrackType) {
  160 + val addTrackRequest = Rtc.AddTrackRequest.newBuilder()
  161 + .setCid(cid)
  162 + .setName(name)
  163 + .setType(type)
  164 + .build()
  165 +
  166 + val request = Rtc.SignalRequest.newBuilder()
  167 + .setAddTrack(addTrackRequest)
  168 + .build()
  169 +
  170 + sendRequest(request)
  171 + }
  172 +
  173 + fun sendRequest(request: Rtc.SignalRequest) {
  174 + Timber.v { "sending request: $request" }
  175 + if (!isConnected || currentWs != null) {
  176 + throw IllegalStateException("not connected!")
  177 + }
  178 + val message = toJson.print(request)
  179 + val sent = currentWs?.send(message) ?: false
  180 +
  181 + if (!sent) {
  182 + Timber.d { "error sending request: $request" }
  183 + throw IllegalStateException()
  184 + }
  185 +
  186 + }
  187 +
105 fun handleSignalResponse(response: Rtc.SignalResponse) { 188 fun handleSignalResponse(response: Rtc.SignalResponse) {
106 if (!isConnected) { 189 if (!isConnected) {
107 Timber.e { "Received response while not connected. ${toJson.print(response)}" } 190 Timber.e { "Received response while not connected. ${toJson.print(response)}" }
@@ -116,9 +199,16 @@ constructor( @@ -116,9 +199,16 @@ constructor(
116 val sd = fromProtoSessionDescription(response.offer) 199 val sd = fromProtoSessionDescription(response.offer)
117 listener?.onOffer(sd) 200 listener?.onOffer(sd)
118 } 201 }
119 -// Rtc.SignalResponse.MessageCase.TRICKLE -> {  
120 -// TODO()  
121 -// } 202 + Rtc.SignalResponse.MessageCase.TRICKLE -> {
  203 + val iceCandidateJson =
  204 + Json.decodeFromString<IceCandidateJSON>(response.trickle.candidateInit)
  205 + val iceCandidate = IceCandidate(
  206 + iceCandidateJson.sdpMid,
  207 + iceCandidateJson.sdpMLineIndex,
  208 + iceCandidateJson.sdp
  209 + )
  210 + listener?.onTrickle(iceCandidate, response.trickle.target)
  211 + }
122 Rtc.SignalResponse.MessageCase.UPDATE -> { 212 Rtc.SignalResponse.MessageCase.UPDATE -> {
123 listener?.onParticipantUpdate(response.update.participantsList) 213 listener?.onParticipantUpdate(response.update.participantsList)
124 } 214 }
@@ -131,7 +221,6 @@ constructor( @@ -131,7 +221,6 @@ constructor(
131 Timber.v { "unhandled response type: ${response.messageCase.name}" } 221 Timber.v { "unhandled response type: ${response.messageCase.name}" }
132 } 222 }
133 } 223 }
134 -  
135 } 224 }
136 225
137 interface Listener { 226 interface Listener {
@@ -140,7 +229,7 @@ constructor( @@ -140,7 +229,7 @@ constructor(
140 fun onAnswer(sessionDescription: SessionDescription) 229 fun onAnswer(sessionDescription: SessionDescription)
141 fun onOffer(sessionDescription: SessionDescription) 230 fun onOffer(sessionDescription: SessionDescription)
142 231
143 - //fun onTrickle(candidate: RTCIceCandidate, target: Rtc.SignalTarget) 232 + fun onTrickle(candidate: IceCandidate, target: Rtc.SignalTarget)
144 fun onLocalTrackPublished(trackPublished: Rtc.TrackPublishedResponse) 233 fun onLocalTrackPublished(trackPublished: Rtc.TrackPublishedResponse)
145 fun onParticipantUpdate(updates: List<Model.ParticipantInfo>) 234 fun onParticipantUpdate(updates: List<Model.ParticipantInfo>)
146 fun onClose(reason: String, code: Int) 235 fun onClose(reason: String, code: Int)