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-18 12:48:53 +0900
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
fe0cad8b07f6467ed910781ad2ef5f344926e8e0
fe0cad8b
1 parent
7ecb535a
revamp sample app to support multiple remote participants
隐藏空白字符变更
内嵌
并排对比
正在显示
12 个修改的文件
包含
279 行增加
和
119 行删除
build.gradle
livekit-android-sdk/src/main/java/io/livekit/android/room/RTCClient.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt
sample-app/build.gradle
sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt
sample-app/src/main/java/io/livekit/android/sample/CallViewModel.kt
sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt
sample-app/src/main/java/io/livekit/android/sample/ParticipantItem.kt
sample-app/src/main/java/io/livekit/android/sample/ViewModelLazyExt.kt
sample-app/src/main/res/layout/call_activity.xml
sample-app/src/main/res/layout/participant_item.xml
build.gradle
查看文件 @
fe0cad8
...
...
@@ -39,9 +39,11 @@ ext {
minVersion
:
21
,
]
versions
=
[
androidx_core:
"1.2.0"
,
dagger
:
"2.27"
,
protobuf
:
"3.15.1"
,
androidx_core
:
"1.2.0"
,
androidx_lifecycle:
"2.3.0"
,
dagger
:
"2.27"
,
groupie
:
"2.9.0"
,
protobuf
:
"3.15.1"
,
]
generated
=
[
protoSrc:
"$projectDir/../protocol"
,
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/RTCClient.kt
查看文件 @
fe0cad8
...
...
@@ -236,7 +236,9 @@ constructor(
Rtc.SignalResponse.MessageCase.TRACK_PUBLISHED -> {
listener?.onLocalTrackPublished(response.trackPublished)
}
Rtc.SignalResponse.MessageCase.SPEAKER -> TODO()
Rtc.SignalResponse.MessageCase.SPEAKER -> {
listener?.onActiveSpeakersChanged(response.speaker.speakersList)
}
Rtc.SignalResponse.MessageCase.MESSAGE_NOT_SET -> TODO()
else -> {
Timber.v { "unhandled response type: ${response.messageCase.name}" }
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
查看文件 @
fe0cad8
...
...
@@ -225,7 +225,7 @@ constructor(
listener?.onFailedToConnect(this, error)
}
fun
setupVideo
(viewRenderer: SurfaceViewRenderer) {
fun
initVideoRenderer
(viewRenderer: SurfaceViewRenderer) {
viewRenderer.init(eglBase.eglBaseContext, null)
viewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
viewRenderer.setEnableHardwareScaler(false /* enabled */);
...
...
livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt
查看文件 @
fe0cad8
...
...
@@ -28,7 +28,7 @@ class RemoteParticipant(
val remoteDataTracks
get() = dataTracks.values.toList()
va
l
listener: Listener? = null
va
r
listener: Listener? = null
var participantInfo: Model.ParticipantInfo? = null
...
...
@@ -237,53 +237,65 @@ class RemoteParticipant(
}
interface Listener {
fun onPublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant)
fun onUnpublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant)
fun onPublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
fun onUnpublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
fun onPublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant)
fun onUnpublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant)
fun onEnable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant)
fun onDisable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant)
fun onEnable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
fun onDisable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
fun onSubscribe(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant)
fun onPublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onPublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onPublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}
fun onUnpublish(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant) {}
fun onEnable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onDisable(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onEnable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onDisable(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant) {}
fun onSubscribe(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant) {}
fun onFailToSubscribe(
audioTrack: RemoteAudioTrack,
exception: Exception,
participant: RemoteParticipant
)
) {
}
fun onUnsubscribe(audioTrack: RemoteAudioTrackPublication, participant: RemoteParticipant)
fun onUnsubscribe(
audioTrack: RemoteAudioTrackPublication,
participant: RemoteParticipant
) {
}
fun onSubscribe(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
fun onSubscribe(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
{}
fun onFailToSubscribe(
videoTrack: RemoteVideoTrack,
exception: Exception,
participant: RemoteParticipant
)
) {
}
fun onUnsubscribe(videoTrack: RemoteVideoTrackPublication, participant: RemoteParticipant)
fun onUnsubscribe(
videoTrack: RemoteVideoTrackPublication,
participant: RemoteParticipant
) {
}
fun onSubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant)
fun onSubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant)
{}
fun onFailToSubscribe(
dataTrack: RemoteDataTrackPublication,
exception: Exception,
participant: RemoteParticipant
)
) {
}
fun onUnsubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant)
fun onUnsubscribe(dataTrack: RemoteDataTrackPublication, participant: RemoteParticipant)
{}
fun onReceive(
data: ByteBuffer,
dataTrack: RemoteDataTrackPublication,
participant: RemoteParticipant
)
) {
}
//fun networkQualityDidChange(networkQualityLevel: NetworkQualityLevel, participant: remoteParticipant)
fun switchedOffVideo(track: RemoteVideoTrack, participant: RemoteParticipant)
fun switchedOnVideo(track: RemoteVideoTrack, participant: RemoteParticipant)
fun switchedOffVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {}
fun switchedOnVideo(track: RemoteVideoTrack, participant: RemoteParticipant) {}
// fun onChangePublishPriority(videoTrack: RemoteVideoTrackPublication, priority: PublishPriority, participant: RemoteParticipant)
// fun onChangePublishPriority(audioTrack: RemoteAudioTrackPublication, priority: PublishPriority, participant: RemoteParticipant)
// fun onChangePublishPriority(dataTrack: RemoteDataTrackPublication, priority: PublishPriority, participant: RemoteParticipant)
...
...
sample-app/build.gradle
查看文件 @
fe0cad8
...
...
@@ -24,6 +24,9 @@ android {
sourceCompatibility
java_version
targetCompatibility
java_version
}
kotlinOptions
{
jvmTarget
=
java_version
}
buildFeatures
{
viewBinding
=
true
}
...
...
@@ -36,7 +39,14 @@ dependencies {
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.activity:activity-ktx:1.2.1"
implementation
'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0'
implementation
"androidx.viewpager2:viewpager2:1.0.0"
implementation
"androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidx_lifecycle}"
implementation
"androidx.lifecycle:lifecycle-common-java8:${versions.androidx_lifecycle}"
implementation
"com.xwray:groupie:${versions.groupie}"
implementation
"com.xwray:groupie-viewbinding:${versions.groupie}"
implementation
'com.snakydesign.livedataextensions:lives:1.3.0'
implementation
'com.github.ajalt:timberkt:1.5.1'
implementation
project
(
":livekit-android-sdk"
)
testImplementation
'junit:junit:4.12'
...
...
sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt
查看文件 @
fe0cad8
...
...
@@ -3,20 +3,21 @@ 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 com.snakydesign.livedataextensions.combineLatest
import com.xwray.groupie.GroupieAdapter
import io.livekit.android.sample.databinding.CallActivityBinding
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
class CallActivity : AppCompatActivity() {
val viewModel: CallViewModel by viewModelByFactory {
val args = intent.getParcelableExtra<BundleArgs>(KEY_ARGS)
?: throw NullPointerException("args is null!")
CallViewModel(args.url, args.token, application)
}
lateinit var binding: CallActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
...
@@ -24,69 +25,20 @@ class CallActivity : AppCompatActivity() {
setContentView(binding.root)
val args = intent.getParcelableExtra<BundleArgs>(KEY_ARGS)
if (args == null) {
finish()
return
val adapter = GroupieAdapter()
binding.viewPager.apply {
this.adapter = adapter
}
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) {
}
}
)
}
combineLatest(
viewModel.room,
viewModel.remoteParticipants
) { room, participants -> room to participants }
.observe(this) {
val (room, participants) = it
val items = participants.map { participant -> ParticipantItem(room, participant) }
adapter.update(items)
}
}
...
...
sample-app/src/main/java/io/livekit/android/sample/CallViewModel.kt
0 → 100644
查看文件 @
fe0cad8
package io.livekit.android.sample
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
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 kotlinx.coroutines.launch
class CallViewModel(
val url: String,
val token: String,
application: Application
) : AndroidViewModel(application) {
private val mutableRoom = MutableLiveData<Room>()
val room: LiveData<Room> = mutableRoom
private val mutableRemoteParticipants = MutableLiveData<List<RemoteParticipant>>()
val remoteParticipants: LiveData<List<RemoteParticipant>> = mutableRemoteParticipants
init {
viewModelScope.launch {
mutableRoom.value = LiveKit.connect(
application,
url,
token,
ConnectOptions(false),
object : Room.Listener {
override fun onConnect(room: Room) {
}
override fun onDisconnect(room: Room, error: Exception?) {
}
override fun onParticipantConnected(
room: Room,
participant: RemoteParticipant
) {
updateParticipants(room)
}
override fun onParticipantDisconnected(
room: Room,
participant: RemoteParticipant
) {
updateParticipants(room)
}
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) {
}
}
)
}
}
fun updateParticipants(room: Room) {
mutableRemoteParticipants.value = room.remoteParticipants
.keys
.sortedBy { it.sid }
.mapNotNull { room.remoteParticipants[it] }
}
}
...
...
sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt
查看文件 @
fe0cad8
...
...
@@ -35,6 +35,6 @@ class MainActivity : AppCompatActivity() {
companion object {
val URL = SpannableStringBuilder("192.168.11.2:7880")
val TOKEN =
SpannableStringBuilder("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTg
1NzgxNzMsImlzcyI6IkFQSXdMZWFoN2c0ZnVMWURZQUplYUtzU0UiLCJqdGkiOiJwaG9uZSIsIm5iZiI6MTYxNTk4NjE3MywidmlkZW8iOnsicm9vbSI6Im15cm9vbSIsInJvb21Kb2luIjp0cnVlfX0.O3UedhM9lwdPxsZJQoTfVk0qXc-0ukjV6oZCBIaRTck
")
SpannableStringBuilder("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTg
2MjY0NDAsImlzcyI6IkFQSXdMZWFoN2c0ZnVMWURZQUplYUtzU0UiLCJqdGkiOiJwaG9uZSIsIm5iZiI6MTYxNjAzNDQ0MCwidmlkZW8iOnsicm9vbSI6Im15cm9vbSIsInJvb21Kb2luIjp0cnVlfX0.QWN0B_DO8eSP2sJivnr_QzBud_sdIgJeWDQGQz67DvY
")
}
}
\ No newline at end of file
...
...
sample-app/src/main/java/io/livekit/android/sample/ParticipantItem.kt
0 → 100644
查看文件 @
fe0cad8
package io.livekit.android.sample
import android.view.View
import com.xwray.groupie.viewbinding.BindableItem
import com.xwray.groupie.viewbinding.GroupieViewHolder
import io.livekit.android.room.Room
import io.livekit.android.room.participant.RemoteParticipant
import io.livekit.android.room.track.RemoteVideoTrackPublication
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.room.track.VideoTrackPublication
import io.livekit.android.sample.databinding.ParticipantItemBinding
class ParticipantItem(
val room: Room,
val remoteParticipant: RemoteParticipant
) :
BindableItem<ParticipantItemBinding>() {
private var videoBound = false
override fun initializeViewBinding(view: View): ParticipantItemBinding {
return ParticipantItemBinding.bind(view)
}
override fun bind(viewBinding: ParticipantItemBinding, position: Int) {
viewBinding.run {
room.initVideoRenderer(renderer)
val existingTrack = getVideoTrack()
if (existingTrack != null) {
setupVideoIfNeeded(existingTrack, viewBinding)
} else {
remoteParticipant.listener = object : RemoteParticipant.Listener {
override fun onSubscribe(
videoTrack: RemoteVideoTrackPublication,
participant: RemoteParticipant
) {
val track = videoTrack.videoTrack
if (track != null) {
setupVideoIfNeeded(track, viewBinding)
}
}
}
}
}
}
private fun getVideoTrack(): VideoTrack? {
return remoteParticipant
.remoteVideoTracks
.firstOrNull()
.let { it as? VideoTrackPublication }
?.videoTrack
}
private fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) {
if (videoBound) {
return
}
videoTrack.addRenderer(viewBinding.renderer)
}
override fun unbind(viewHolder: GroupieViewHolder<ParticipantItemBinding>) {
super.unbind(viewHolder)
videoBound = false
}
override fun getLayout(): Int = R.layout.participant_item
}
\ No newline at end of file
...
...
sample-app/src/main/java/io/livekit/android/sample/ViewModelLazyExt.kt
0 → 100644
查看文件 @
fe0cad8
package io.livekit.android.sample
import androidx.activity.viewModels
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
typealias CreateViewModel<VM> = () -> VM
inline fun <reified VM : ViewModel> FragmentActivity.viewModelByFactory(
noinline create: CreateViewModel<VM>
): Lazy<VM> {
return viewModels {
createViewModelFactoryFactory(create)
}
}
fun <VM> createViewModelFactoryFactory(
create: CreateViewModel<VM>
): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return create() as? T
?: throw IllegalArgumentException("Unknown viewmodel class!")
}
}
}
\ No newline at end of file
...
...
sample-app/src/main/res/layout/call_activity.xml
查看文件 @
fe0cad8
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
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"
/>
<androidx.viewpager2.widget.ViewPager2
android:id=
"@+id/view_pager"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
<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"
/>
android:layout_margin=
"16dp"
/>
</FrameLayout>
...
...
sample-app/src/main/res/layout/participant_item.xml
0 → 100644
查看文件 @
fe0cad8
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
<org.webrtc.SurfaceViewRenderer
android:id=
"@+id/renderer"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
</FrameLayout>
\ No newline at end of file
...
...
请
注册
或
登录
后发表评论