Toggle navigation
Toggle navigation
此项目
正在载入...
Sign in
xuning
/
livekitAndroidXuningTest
转到一个项目
Toggle navigation
项目
群组
代码片段
帮助
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
David Liu
2021-03-17 23:21:41 +0900
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
7ecb535aa95831bb2ae266ed46d5f8a9629849bf
7ecb535a
1 parent
4b06b654
Sample app
隐藏空白字符变更
内嵌
并排对比
正在显示
19 个修改的文件
包含
279 行增加
和
43 行删除
build.gradle
livekit-android-sdk/build.gradle
livekit-android-sdk/src/main/java/io/livekit/android/ConnectOptions.kt
livekit-android-sdk/src/main/java/io/livekit/android/LiveKit.kt
livekit-android-sdk/src/main/java/io/livekit/android/dagger/JsonFormatModule.kt
livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/IceCandidateJSON.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/RTCClient.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalVideoTrack.kt
sample-app/build.gradle
sample-app/src/main/AndroidManifest.xml
sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt
sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt
sample-app/src/main/java/io/livekit/android/sample/SampleApplication.kt
sample-app/src/main/res/layout/call_activity.xml
sample-app/src/main/res/layout/activity_main.xml → sample-app/src/main/res/layout/main_activity.xml
sample-app/src/main/res/xml/network_security_config.xml
build.gradle
查看文件 @
7ecb535
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript
{
ext
.
kotlin_version
=
'1.4.3
0
'
ext
.
kotlin_version
=
'1.4.3
1
'
ext
.
java_version
=
JavaVersion
.
VERSION_1_8
repositories
{
google
()
...
...
livekit-android-sdk/build.gradle
查看文件 @
7ecb535
...
...
@@ -60,8 +60,8 @@ dependencies {
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"
api
'org.webrtc:google-webrtc:1.0.32006'
api
"com.squareup.okhttp3:okhttp:4.9.0"
implementation
"com.google.protobuf:protobuf-java:${versions.protobuf}"
implementation
"com.google.protobuf:protobuf-java-util:${versions.protobuf}"
...
...
livekit-android-sdk/src/main/java/io/livekit/android/ConnectOptions.kt
查看文件 @
7ecb535
...
...
@@ -2,5 +2,5 @@ package io.livekit.android
data class ConnectOptions(
val isSecure: Boolean
val isSecure: Boolean
= true
)
\ No newline at end of file
...
...
livekit-android-sdk/src/main/java/io/livekit/android/LiveKit.kt
查看文件 @
7ecb535
...
...
@@ -2,6 +2,7 @@ package io.livekit.android
import android.content.Context
import io.livekit.android.dagger.DaggerLiveKitComponent
import io.livekit.android.room.Room
class LiveKit {
companion object {
...
...
@@ -9,8 +10,9 @@ class LiveKit {
appContext: Context,
url: String,
token: String,
options: ConnectOptions
) {
options: ConnectOptions,
listener: Room.Listener?
): Room {
val component = DaggerLiveKitComponent
.factory()
...
...
@@ -18,7 +20,10 @@ class LiveKit {
val room = component.roomFactory()
.create(options)
room.connect(url, token, false)
room.listener = listener
room.connect(url, token, options.isSecure)
return room
}
}
}
...
...
livekit-android-sdk/src/main/java/io/livekit/android/dagger/JsonFormatModule.kt
查看文件 @
7ecb535
...
...
@@ -3,22 +3,31 @@ package io.livekit.android.dagger
import com.google.protobuf.util.JsonFormat
import dagger.Module
import dagger.Provides
import dagger.Reusable
import kotlinx.serialization.json.Json
import javax.inject.Named
@Module
class JsonFormatModule {
companion object {
@Provides
fun
j
sonFormatParser(): JsonFormat.Parser {
fun
protobufJ
sonFormatParser(): JsonFormat.Parser {
return JsonFormat.parser()
}
@Provides
fun
j
sonFormatPrinter(): JsonFormat.Printer {
fun
protobufJ
sonFormatPrinter(): JsonFormat.Printer {
return JsonFormat.printer()
}
@Provides
@Reusable
fun kotlinSerializationJson(): Json =
Json {
ignoreUnknownKeys = true
}
@Provides
@Named(InjectionNames.SIGNAL_JSON_ENABLED)
fun signalJsonEnabled(): Boolean = false
}
...
...
livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt
查看文件 @
7ecb535
...
...
@@ -4,6 +4,7 @@ import android.content.Context
import com.github.ajalt.timberkt.Timber
import dagger.Module
import dagger.Provides
import org.webrtc.EglBase
import org.webrtc.PeerConnectionFactory
import org.webrtc.audio.AudioDeviceModule
import org.webrtc.audio.JavaAudioDeviceModule
...
...
@@ -99,5 +100,12 @@ class RTCModule {
.setAudioDeviceModule(audioDeviceModule)
.createPeerConnectionFactory()
}
@Provides
@Singleton
fun eglBase(): EglBase {
return EglBase.create()
}
}
}
\ No newline at end of file
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/IceCandidateJSON.kt
查看文件 @
7ecb535
...
...
@@ -3,4 +3,4 @@ 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
data class IceCandidateJSON(val candidate: String, val sdpMLineIndex: Int, val sdpMid: String?)
\ No newline at end of file
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/RTCClient.kt
查看文件 @
7ecb535
...
...
@@ -25,13 +25,15 @@ class RTCClient
@Inject
constructor(
private val websocketFactory: WebSocket.Factory,
private val fromJson: JsonFormat.Parser,
private val toJson: JsonFormat.Printer,
private val fromJsonProtobuf: JsonFormat.Parser,
private val toJsonProtobuf: JsonFormat.Printer,
private val json: Json,
@Named(InjectionNames.SIGNAL_JSON_ENABLED)
private val useJson: Boolean,
) : WebSocketListener() {
private var isConnected = false
var isConnected = false
private set
private var currentWs: WebSocket? = null
var listener: Listener? = null
...
...
@@ -52,12 +54,14 @@ constructor(
}
override fun onOpen(webSocket: WebSocket, response: Response) {
Timber.v { response.message }
super.onOpen(webSocket, response)
}
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.v { text }
val signalResponseBuilder = Rtc.SignalResponse.newBuilder()
fromJson.merge(text, signalResponseBuilder)
fromJson
Protobuf
.merge(text, signalResponseBuilder)
val response = signalResponseBuilder.build()
handleSignalResponse(response)
...
...
@@ -73,14 +77,18 @@ constructor(
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
Timber.v { "websocket closed" }
super.onClosed(webSocket, code, reason)
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
Timber.v { "websocket closing" }
super.onClosing(webSocket, code, reason)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
Timber.v(t) { "websocket failure: ${response}" }
super.onFailure(webSocket, t, response)
}
...
...
@@ -128,13 +136,13 @@ constructor(
fun sendCandidate(candidate: IceCandidate, target: Rtc.SignalTarget){
val iceCandidateJSON = IceCandidateJSON(
sdp
= candidate.sdp,
candidate
= candidate.sdp,
sdpMid = candidate.sdpMid,
sdpMLineIndex = candidate.sdpMLineIndex
)
val trickleRequest = Rtc.TrickleRequest.newBuilder()
.setCandidateInit(
J
son.encodeToString(iceCandidateJSON))
.setCandidateInit(
j
son.encodeToString(iceCandidateJSON))
.setTarget(target)
.build()
...
...
@@ -174,12 +182,12 @@ constructor(
fun sendRequest(request: Rtc.SignalRequest) {
Timber.v { "sending request: $request" }
if (!isConnected || currentWs
!
= null) {
if (!isConnected || currentWs
=
= null) {
throw IllegalStateException("not connected!")
}
val sent: Boolean
if (useJson) {
val message = toJson.print(request)
val message = toJson
Protobuf
.print(request)
sent = currentWs?.send(message) ?: false
} else {
val message = request.toByteArray().toByteString()
...
...
@@ -199,7 +207,7 @@ constructor(
isConnected = true
listener?.onJoin(response.join)
} else {
Timber.e { "Received response while not connected. ${toJson.print(response)}" }
Timber.e { "Received response while not connected. ${toJson
Protobuf
.print(response)}" }
}
return
}
...
...
@@ -214,11 +222,11 @@ constructor(
}
Rtc.SignalResponse.MessageCase.TRICKLE -> {
val iceCandidateJson =
J
son.decodeFromString<IceCandidateJSON>(response.trickle.candidateInit)
j
son.decodeFromString<IceCandidateJSON>(response.trickle.candidateInit)
val iceCandidate = IceCandidate(
iceCandidateJson.sdpMid,
iceCandidateJson.sdpMLineIndex,
iceCandidateJson.
sdp
iceCandidateJson.
candidate
)
listener?.onTrickle(iceCandidate, response.trickle.target)
}
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt
查看文件 @
7ecb535
...
...
@@ -96,24 +96,26 @@ constructor(
}
fun negotiate() {
if (!client.isConnected) {
return
}
coroutineScope.launch {
val offerObserver = CoroutineSdpObserver()
publisher.peerConnection.createOffer(offerObserver, OFFER_CONSTRAINTS)
val offerOutcome = offerObserver.awaitCreate()
val sdpOffer = when (offerOutcome) {
is Either.Left -> offerOutcome.value
val sdpOffer = when (val outcome = offerObserver.awaitCreate()) {
is Either.Left -> outcome.value
is Either.Right -> {
Timber.d { "error creating offer: ${o
fferO
utcome.value}" }
Timber.d { "error creating offer: ${outcome.value}" }
return@launch
}
}
val setObserver = CoroutineSdpObserver()
publisher.peerConnection.setLocalDescription(setObserver, sdpOffer)
val setOutcome = setObserver.awaitSet()
when (setOutcome) {
when (val outcome = setObserver.awaitSet()) {
is Either.Left -> client.sendOffer(sdpOffer)
is Either.Right -> Timber.d { "error setting local description: ${
setO
utcome.value}" }
is Either.Right -> Timber.d { "error setting local description: ${
o
utcome.value}" }
}
}
}
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
查看文件 @
7ecb535
...
...
@@ -13,15 +13,14 @@ import io.livekit.android.room.track.Track
import io.livekit.android.room.util.unpackedTrackLabel
import livekit.Model
import livekit.Rtc
import org.webrtc.DataChannel
import org.webrtc.MediaStream
import org.webrtc.MediaStreamTrack
import org.webrtc.*
class Room
@AssistedInject
constructor(
@Assisted private val connectOptions: ConnectOptions,
private val engine: RTCEngine,
private val eglBase: EglBase,
) : RTCEngine.Listener {
init {
engine.listener = this
...
...
@@ -74,7 +73,7 @@ constructor(
removedParticipant.unpublishTrack(publication.trackSid)
}
listener?.on
p
articipantDisconnected(this, removedParticipant)
listener?.on
P
articipantDisconnected(this, removedParticipant)
}
private fun getOrCreateRemoteParticipant(
...
...
@@ -136,7 +135,7 @@ constructor(
fun onConnect(room: Room)
fun onDisconnect(room: Room, error: Exception?)
fun onParticipantConnected(room: Room, participant: RemoteParticipant)
fun on
p
articipantDisconnected(room: Room, participant: RemoteParticipant)
fun on
P
articipantDisconnected(room: Room, participant: RemoteParticipant)
fun onFailedToConnect(room: Room, error: Exception)
fun onReconnecting(room: Room, error: Exception)
fun onReconnect(room: Room)
...
...
@@ -225,4 +224,10 @@ constructor(
override fun onFailToConnect(error: Exception) {
listener?.onFailedToConnect(this, error)
}
fun setupVideo(viewRenderer: SurfaceViewRenderer) {
viewRenderer.init(eglBase.eglBaseContext, null)
viewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
viewRenderer.setEnableHardwareScaler(false /* enabled */);
}
}
\ No newline at end of file
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalVideoTrack.kt
查看文件 @
7ecb535
...
...
@@ -19,11 +19,11 @@ class LocalVideoTrack(
peerConnectionFactory: PeerConnectionFactory,
context: Context,
enabled: Boolean,
name: String
name: String,
rootEglBase: EglBase,
): LocalVideoTrack {
val source = peerConnectionFactory.createVideoSource(false)
val capturer = createVideoCapturer(context) ?: TODO()
val rootEglBase = EglBase.create()
capturer.initialize(
SurfaceTextureHelper.create("CaptureThread", rootEglBase.eglBaseContext),
context,
...
...
sample-app/build.gradle
查看文件 @
7ecb535
apply
plugin:
'com.android.application'
apply
plugin:
'kotlin-android'
apply
plugin:
'kotlin-parcelize'
android
{
compileSdkVersion
29
...
...
@@ -19,20 +20,24 @@ android {
proguardFiles
getDefaultProguardFile
(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
}
}
android
{
compileOptions
{
sourceCompatibility
java_version
targetCompatibility
java_version
}
compileOptions
{
sourceCompatibility
java_version
targetCompatibility
java_version
}
buildFeatures
{
viewBinding
=
true
}
}
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
'com.google.android.material:material:1.3.0'
implementation
'androidx.appcompat:appcompat:1.2.0'
implementation
'androidx.core:core-ktx:1.3.2'
implementation
'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0'
implementation
'com.github.ajalt:timberkt:1.5.1'
implementation
project
(
":livekit-android-sdk"
)
testImplementation
'junit:junit:4.12'
androidTestImplementation
'androidx.test.ext:junit:1.1.2'
...
...
sample-app/src/main/AndroidManifest.xml
查看文件 @
7ecb535
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"io.livekit.android"
>
package=
"io.livekit.android.sample"
>
<uses-permission
android:name=
"android.permission.ACCESS_NETWORK_STATE"
/>
<uses-permission
android:name=
"android.permission.INTERNET"
/>
<application
android:name=
".SampleApplication"
android:networkSecurityConfig=
"@xml/network_security_config"
android:allowBackup=
"true"
android:icon=
"@mipmap/ic_launcher"
android:label=
"@string/app_name"
android:roundIcon=
"@mipmap/ic_launcher_round"
android:supportsRtl=
"true"
android:theme=
"@style/AppTheme"
>
<activity
android:name=
".sample.MainActivity"
/>
android:theme=
"@style/AppTheme"
>
<activity
android:name=
".MainActivity"
>
<intent-filter>
<action
android:name=
"android.intent.action.MAIN"
/>
<category
android:name=
"android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity
android:name=
".CallActivity"
/>
</application>
</manifest>
...
...
sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt
0 → 100644
查看文件 @
7ecb535
package io.livekit.android.sample
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import io.livekit.android.ConnectOptions
import io.livekit.android.LiveKit
import io.livekit.android.room.Room
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.participant.RemoteParticipant
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.sample.databinding.CallActivityBinding
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
class CallActivity : AppCompatActivity() {
lateinit var binding: CallActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CallActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
val args = intent.getParcelableExtra<BundleArgs>(KEY_ARGS)
if (args == null) {
finish()
return
}
lifecycleScope.launch {
val room = LiveKit.connect(
applicationContext,
args.url,
args.token,
ConnectOptions(false),
object : Room.Listener {
var loadedParticipant = false
override fun onConnect(room: Room) {
}
override fun onDisconnect(room: Room, error: Exception?) {
}
override fun onParticipantConnected(
room: Room,
participant: RemoteParticipant
) {
if (!loadedParticipant) {
room.setupVideo(binding.fullscreenVideoView)
participant.remoteVideoTracks
.first()
.track
.let { it as? VideoTrack }
?.addRenderer(binding.fullscreenVideoView)
}
}
override fun onParticipantDisconnected(
room: Room,
participant: RemoteParticipant
) {
}
override fun onFailedToConnect(room: Room, error: Exception) {
}
override fun onReconnecting(room: Room, error: Exception) {
}
override fun onReconnect(room: Room) {
}
override fun onStartRecording(room: Room) {
}
override fun onStopRecording(room: Room) {
}
override fun onActiveSpeakersChanged(speakers: List<Participant>, room: Room) {
}
}
)
}
}
companion object {
const val KEY_ARGS = "args"
}
@Parcelize
data class BundleArgs(val url: String, val token: String) : Parcelable
}
\ No newline at end of file
...
...
sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt
查看文件 @
7ecb535
package io.livekit.android.sample
import android.content.Intent
import android.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AppCompatActivity
import io.livekit.android.sample.databinding.MainActivityBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = MainActivityBinding.inflate(layoutInflater)
binding.run {
url.editText?.text = URL
token.editText?.text = TOKEN
connectButton.setOnClickListener {
val intent = Intent(this@MainActivity, CallActivity::class.java).apply {
putExtra(
CallActivity.KEY_ARGS,
CallActivity.BundleArgs(
url.editText?.text.toString(),
token.editText?.text.toString()
)
)
}
startActivity(intent)
}
}
setContentView(binding.root)
}
companion object {
val URL = SpannableStringBuilder("192.168.11.2:7880")
val TOKEN =
SpannableStringBuilder("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTg1NzgxNzMsImlzcyI6IkFQSXdMZWFoN2c0ZnVMWURZQUplYUtzU0UiLCJqdGkiOiJwaG9uZSIsIm5iZiI6MTYxNTk4NjE3MywidmlkZW8iOnsicm9vbSI6Im15cm9vbSIsInJvb21Kb2luIjp0cnVlfX0.O3UedhM9lwdPxsZJQoTfVk0qXc-0ukjV6oZCBIaRTck")
}
}
\ No newline at end of file
...
...
sample-app/src/main/java/io/livekit/android/sample/SampleApplication.kt
0 → 100644
查看文件 @
7ecb535
package io.livekit.android.sample
import android.app.Application
import timber.log.Timber
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
}
\ No newline at end of file
...
...
sample-app/src/main/res/layout/call_activity.xml
0 → 100644
查看文件 @
7ecb535
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
<org.webrtc.SurfaceViewRenderer
android:id=
"@+id/fullscreen_video_view"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center"
/>
<org.webrtc.SurfaceViewRenderer
android:id=
"@+id/pip_video_view"
android:layout_height=
"144dp"
android:layout_width=
"wrap_content"
android:layout_gravity=
"bottom|end"
android:layout_margin=
"16dp"
/>
<FrameLayout
android:id=
"@+id/call_fragment_container"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
<FrameLayout
android:id=
"@+id/hud_fragment_container"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
</FrameLayout>
...
...
sample-app/src/main/res/layout/
activity_main
.xml → sample-app/src/main/res/layout/
main_activity
.xml
查看文件 @
7ecb535
sample-app/src/main/res/xml/network_security_config.xml
0 → 100644
查看文件 @
7ecb535
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config
cleartextTrafficPermitted=
"true"
>
<domain
includeSubdomains=
"true"
>
example.com
</domain>
</domain-config>
<base-config
cleartextTrafficPermitted=
"true"
>
<trust-anchors>
<certificates
src=
"system"
/>
</trust-anchors>
</base-config>
</network-security-config>
\ No newline at end of file
...
...
请
注册
或
登录
后发表评论