ParticipantItem.kt
4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package io.livekit.android.sample
import android.view.View
import com.github.ajalt.timberkt.Timber
import com.xwray.groupie.viewbinding.BindableItem
import com.xwray.groupie.viewbinding.GroupieViewHolder
import io.livekit.android.room.Room
import io.livekit.android.room.participant.ConnectionQuality
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.participant.ParticipantListener
import io.livekit.android.room.participant.RemoteParticipant
import io.livekit.android.room.track.RemoteTrackPublication
import io.livekit.android.room.track.Track
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.sample.databinding.ParticipantItemBinding
import io.livekit.android.util.flow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
class ParticipantItem(
private val room: Room,
private val participant: Participant
) : BindableItem<ParticipantItemBinding>() {
private var boundVideoTrack: VideoTrack? = null
private var coroutineScope: CoroutineScope? = null
override fun initializeViewBinding(view: View): ParticipantItemBinding {
val binding = ParticipantItemBinding.bind(view)
room.initVideoRenderer(binding.renderer)
return binding
}
private fun ensureCoroutineScope() {
if (coroutineScope == null) {
coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
}
}
override fun bind(viewBinding: ParticipantItemBinding, position: Int) {
ensureCoroutineScope()
coroutineScope?.launch {
participant::identity.flow.collect { identity ->
viewBinding.identityText.text = identity
}
}
coroutineScope?.launch {
participant::audioTracks.flow
.flatMapLatest { tracks ->
val audioTrack = tracks.firstOrNull()?.first
if (audioTrack != null) {
audioTrack::muted.flow
} else {
flowOf(true)
}
}
.collect { muted ->
viewBinding.muteIndicator.visibility = if (muted) View.VISIBLE else View.INVISIBLE
}
}
coroutineScope?.launch {
participant::connectionQuality.flow
.collect { quality ->
viewBinding.connectionQuality.visibility =
if (quality == ConnectionQuality.POOR) View.VISIBLE else View.INVISIBLE
}
}
participant.listener = object : ParticipantListener {
override fun onTrackSubscribed(
track: Track,
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
if (track !is VideoTrack) return
if (publication.source == Track.Source.CAMERA) {
setupVideoIfNeeded(track, viewBinding)
}
}
override fun onTrackUnpublished(
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
super.onTrackUnpublished(publication, participant)
Timber.e { "Track unpublished" }
}
}
val existingTrack = getVideoTrack()
if (existingTrack != null) {
setupVideoIfNeeded(existingTrack, viewBinding)
}
}
private fun getVideoTrack(): VideoTrack? {
return participant.getTrackPublication(Track.Source.CAMERA)?.track as? VideoTrack
}
internal fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) {
if (boundVideoTrack != null) {
return
}
boundVideoTrack = videoTrack
Timber.v { "adding renderer to $videoTrack" }
videoTrack.addRenderer(viewBinding.renderer)
}
override fun unbind(viewHolder: GroupieViewHolder<ParticipantItemBinding>) {
coroutineScope?.cancel()
coroutineScope = null
super.unbind(viewHolder)
boundVideoTrack?.removeRenderer(viewHolder.binding.renderer)
boundVideoTrack = null
}
override fun getLayout(): Int = R.layout.participant_item
}