正在显示
7 个修改的文件
包含
143 行增加
和
38 行删除
| @@ -117,6 +117,7 @@ | @@ -117,6 +117,7 @@ | ||
| 117 | </codeStyleSettings> | 117 | </codeStyleSettings> |
| 118 | <codeStyleSettings language="kotlin"> | 118 | <codeStyleSettings language="kotlin"> |
| 119 | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | 119 | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> |
| 120 | + <option name="RIGHT_MARGIN" value="120" /> | ||
| 120 | </codeStyleSettings> | 121 | </codeStyleSettings> |
| 121 | </code_scheme> | 122 | </code_scheme> |
| 122 | </component> | 123 | </component> |
| @@ -5,6 +5,7 @@ import android.media.AudioManager | @@ -5,6 +5,7 @@ import android.media.AudioManager | ||
| 5 | import android.media.projection.MediaProjectionManager | 5 | import android.media.projection.MediaProjectionManager |
| 6 | import android.os.Bundle | 6 | import android.os.Bundle |
| 7 | import android.os.Parcelable | 7 | import android.os.Parcelable |
| 8 | +import android.view.View | ||
| 8 | import androidx.activity.result.contract.ActivityResultContracts | 9 | import androidx.activity.result.contract.ActivityResultContracts |
| 9 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.recyclerview.widget.LinearLayoutManager | 11 | import androidx.recyclerview.widget.LinearLayoutManager |
| @@ -72,7 +73,7 @@ class CallActivity : AppCompatActivity() { | @@ -72,7 +73,7 @@ class CallActivity : AppCompatActivity() { | ||
| 72 | 73 | ||
| 73 | // speaker view setup | 74 | // speaker view setup |
| 74 | viewModel.room.take(1).observe(this) { room -> | 75 | viewModel.room.take(1).observe(this) { room -> |
| 75 | - room.initVideoRenderer(binding.speakerView) | 76 | + room.initVideoRenderer(binding.speakerVideoView) |
| 76 | viewModel.activeSpeaker | 77 | viewModel.activeSpeaker |
| 77 | .scan(Pair<Participant?, Participant?>(null, null)) { pair, participant -> | 78 | .scan(Pair<Participant?, Participant?>(null, null)) { pair, participant -> |
| 78 | // old participant is first | 79 | // old participant is first |
| @@ -83,13 +84,19 @@ class CallActivity : AppCompatActivity() { | @@ -83,13 +84,19 @@ class CallActivity : AppCompatActivity() { | ||
| 83 | oldSpeaker?.videoTracks | 84 | oldSpeaker?.videoTracks |
| 84 | ?.values | 85 | ?.values |
| 85 | ?.forEach { trackPublication -> | 86 | ?.forEach { trackPublication -> |
| 86 | - (trackPublication.track as? VideoTrack)?.removeRenderer(binding.speakerView) | 87 | + (trackPublication.track as? VideoTrack)?.removeRenderer(binding.speakerVideoView) |
| 87 | } | 88 | } |
| 88 | 89 | ||
| 90 | + binding.identityText.text = newSpeaker?.identity | ||
| 89 | val videoTrack = newSpeaker?.videoTracks?.values | 91 | val videoTrack = newSpeaker?.videoTracks?.values |
| 90 | ?.firstOrNull() | 92 | ?.firstOrNull() |
| 91 | ?.track as? VideoTrack | 93 | ?.track as? VideoTrack |
| 92 | - videoTrack?.addRenderer(binding.speakerView) | 94 | + if (videoTrack != null) { |
| 95 | + videoTrack.addRenderer(binding.speakerVideoView) | ||
| 96 | + binding.speakerVideoView.visibility = View.VISIBLE | ||
| 97 | + } else { | ||
| 98 | + binding.speakerVideoView.visibility = View.INVISIBLE | ||
| 99 | + } | ||
| 93 | } | 100 | } |
| 94 | } | 101 | } |
| 95 | 102 | ||
| @@ -156,7 +163,7 @@ class CallActivity : AppCompatActivity() { | @@ -156,7 +163,7 @@ class CallActivity : AppCompatActivity() { | ||
| 156 | super.onDestroy() | 163 | super.onDestroy() |
| 157 | 164 | ||
| 158 | // Release video views | 165 | // Release video views |
| 159 | - binding.speakerView.release() | 166 | + binding.speakerVideoView.release() |
| 160 | 167 | ||
| 161 | // Undo audio mode changes | 168 | // Undo audio mode changes |
| 162 | val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager | 169 | val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager |
| @@ -14,10 +14,9 @@ import io.livekit.android.sample.databinding.ParticipantItemBinding | @@ -14,10 +14,9 @@ import io.livekit.android.sample.databinding.ParticipantItemBinding | ||
| 14 | class ParticipantItem( | 14 | class ParticipantItem( |
| 15 | val room: Room, | 15 | val room: Room, |
| 16 | val participant: Participant | 16 | val participant: Participant |
| 17 | -) : | ||
| 18 | - BindableItem<ParticipantItemBinding>() { | 17 | +) : BindableItem<ParticipantItemBinding>() { |
| 19 | 18 | ||
| 20 | - private var videoBound = false | 19 | + private var boundVideoTrack: VideoTrack? = null |
| 21 | 20 | ||
| 22 | override fun initializeViewBinding(view: View): ParticipantItemBinding { | 21 | override fun initializeViewBinding(view: View): ParticipantItemBinding { |
| 23 | val binding = ParticipantItemBinding.bind(view) | 22 | val binding = ParticipantItemBinding.bind(view) |
| @@ -26,33 +25,31 @@ class ParticipantItem( | @@ -26,33 +25,31 @@ class ParticipantItem( | ||
| 26 | } | 25 | } |
| 27 | 26 | ||
| 28 | override fun bind(viewBinding: ParticipantItemBinding, position: Int) { | 27 | override fun bind(viewBinding: ParticipantItemBinding, position: Int) { |
| 29 | - viewBinding.run { | ||
| 30 | - | ||
| 31 | - participant.listener = object : ParticipantListener { | ||
| 32 | - override fun onTrackSubscribed( | ||
| 33 | - track: Track, | ||
| 34 | - publication: RemoteTrackPublication, | ||
| 35 | - participant: RemoteParticipant | ||
| 36 | - ) { | ||
| 37 | - if (track !is VideoTrack) return | ||
| 38 | - if (publication.source == Track.Source.CAMERA) { | ||
| 39 | - setupVideoIfNeeded(track, viewBinding) | ||
| 40 | - } | ||
| 41 | - } | ||
| 42 | - | ||
| 43 | - override fun onTrackUnpublished( | ||
| 44 | - publication: RemoteTrackPublication, | ||
| 45 | - participant: RemoteParticipant | ||
| 46 | - ) { | ||
| 47 | - super.onTrackUnpublished(publication, participant) | ||
| 48 | - Timber.e { "Track unpublished" } | 28 | + viewBinding.identityText.text = participant.identity |
| 29 | + participant.listener = object : ParticipantListener { | ||
| 30 | + override fun onTrackSubscribed( | ||
| 31 | + track: Track, | ||
| 32 | + publication: RemoteTrackPublication, | ||
| 33 | + participant: RemoteParticipant | ||
| 34 | + ) { | ||
| 35 | + if (track !is VideoTrack) return | ||
| 36 | + if (publication.source == Track.Source.CAMERA) { | ||
| 37 | + setupVideoIfNeeded(track, viewBinding) | ||
| 49 | } | 38 | } |
| 50 | } | 39 | } |
| 51 | - val existingTrack = getVideoTrack() | ||
| 52 | - if (existingTrack != null) { | ||
| 53 | - setupVideoIfNeeded(existingTrack, viewBinding) | 40 | + |
| 41 | + override fun onTrackUnpublished( | ||
| 42 | + publication: RemoteTrackPublication, | ||
| 43 | + participant: RemoteParticipant | ||
| 44 | + ) { | ||
| 45 | + super.onTrackUnpublished(publication, participant) | ||
| 46 | + Timber.e { "Track unpublished" } | ||
| 54 | } | 47 | } |
| 55 | } | 48 | } |
| 49 | + val existingTrack = getVideoTrack() | ||
| 50 | + if (existingTrack != null) { | ||
| 51 | + setupVideoIfNeeded(existingTrack, viewBinding) | ||
| 52 | + } | ||
| 56 | } | 53 | } |
| 57 | 54 | ||
| 58 | private fun getVideoTrack(): VideoTrack? { | 55 | private fun getVideoTrack(): VideoTrack? { |
| @@ -60,18 +57,19 @@ class ParticipantItem( | @@ -60,18 +57,19 @@ class ParticipantItem( | ||
| 60 | } | 57 | } |
| 61 | 58 | ||
| 62 | internal fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) { | 59 | internal fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) { |
| 63 | - if (videoBound) { | 60 | + if (boundVideoTrack != null) { |
| 64 | return | 61 | return |
| 65 | } | 62 | } |
| 66 | 63 | ||
| 67 | - videoBound = true | 64 | + boundVideoTrack = videoTrack |
| 68 | Timber.v { "adding renderer to $videoTrack" } | 65 | Timber.v { "adding renderer to $videoTrack" } |
| 69 | videoTrack.addRenderer(viewBinding.renderer) | 66 | videoTrack.addRenderer(viewBinding.renderer) |
| 70 | } | 67 | } |
| 71 | 68 | ||
| 72 | override fun unbind(viewHolder: GroupieViewHolder<ParticipantItemBinding>) { | 69 | override fun unbind(viewHolder: GroupieViewHolder<ParticipantItemBinding>) { |
| 73 | super.unbind(viewHolder) | 70 | super.unbind(viewHolder) |
| 74 | - videoBound = false | 71 | + boundVideoTrack?.removeRenderer(viewHolder.binding.renderer) |
| 72 | + boundVideoTrack = null | ||
| 75 | } | 73 | } |
| 76 | 74 | ||
| 77 | override fun getLayout(): Int = R.layout.participant_item | 75 | override fun getLayout(): Int = R.layout.participant_item |
| @@ -4,15 +4,64 @@ | @@ -4,15 +4,64 @@ | ||
| 4 | android:layout_height="match_parent" | 4 | android:layout_height="match_parent" |
| 5 | android:keepScreenOn="true"> | 5 | android:keepScreenOn="true"> |
| 6 | 6 | ||
| 7 | - <io.livekit.android.renderer.TextureViewRenderer | 7 | + <FrameLayout |
| 8 | android:id="@+id/speaker_view" | 8 | android:id="@+id/speaker_view" |
| 9 | android:layout_width="0dp" | 9 | android:layout_width="0dp" |
| 10 | android:layout_height="0dp" | 10 | android:layout_height="0dp" |
| 11 | + android:background="@color/no_video_background" | ||
| 11 | app:layout_constraintBottom_toTopOf="@id/audience_row" | 12 | app:layout_constraintBottom_toTopOf="@id/audience_row" |
| 12 | app:layout_constraintTop_toTopOf="parent" | 13 | app:layout_constraintTop_toTopOf="parent" |
| 13 | app:layout_constraintEnd_toEndOf="parent" | 14 | app:layout_constraintEnd_toEndOf="parent" |
| 15 | + app:layout_constraintStart_toStartOf="parent"> | ||
| 16 | + | ||
| 17 | + <ImageView | ||
| 18 | + android:layout_width="120dp" | ||
| 19 | + android:layout_height="120dp" | ||
| 20 | + android:layout_gravity="center" | ||
| 21 | + android:src="@drawable/outline_videocam_off_24" | ||
| 22 | + app:tint="@color/no_video_participant" /> | ||
| 23 | + | ||
| 24 | + <io.livekit.android.renderer.TextureViewRenderer | ||
| 25 | + android:id="@+id/speaker_video_view" | ||
| 26 | + android:layout_width="0dp" | ||
| 27 | + android:layout_height="0dp" | ||
| 28 | + app:layout_constraintBottom_toTopOf="@id/audience_row" | ||
| 29 | + app:layout_constraintEnd_toEndOf="parent" | ||
| 30 | + app:layout_constraintStart_toStartOf="parent" | ||
| 31 | + app:layout_constraintTop_toTopOf="parent" /> | ||
| 32 | + </FrameLayout> | ||
| 33 | + | ||
| 34 | + <FrameLayout | ||
| 35 | + android:id="@+id/identity_bar" | ||
| 36 | + android:layout_width="0dp" | ||
| 37 | + android:layout_height="30dp" | ||
| 38 | + android:background="#80000000" | ||
| 39 | + app:layout_constraintBottom_toBottomOf="@id/speaker_view" | ||
| 40 | + app:layout_constraintEnd_toEndOf="parent" | ||
| 14 | app:layout_constraintStart_toStartOf="parent" /> | 41 | app:layout_constraintStart_toStartOf="parent" /> |
| 15 | 42 | ||
| 43 | + <ImageView | ||
| 44 | + android:id="@+id/mute_indicator" | ||
| 45 | + android:layout_width="24dp" | ||
| 46 | + android:layout_height="24dp" | ||
| 47 | + android:layout_marginEnd="@dimen/identity_bar_padding" | ||
| 48 | + android:src="@drawable/outline_mic_off_24" | ||
| 49 | + app:layout_constraintBottom_toBottomOf="@id/identity_bar" | ||
| 50 | + app:layout_constraintEnd_toEndOf="@id/identity_bar" | ||
| 51 | + app:layout_constraintTop_toTopOf="@id/identity_bar" | ||
| 52 | + app:tint="#BB0000" /> | ||
| 53 | + | ||
| 54 | + <TextView | ||
| 55 | + android:id="@+id/identity_text" | ||
| 56 | + android:layout_width="0dp" | ||
| 57 | + android:layout_height="wrap_content" | ||
| 58 | + android:layout_marginStart="@dimen/identity_bar_padding" | ||
| 59 | + android:ellipsize="end" | ||
| 60 | + app:layout_constraintBottom_toBottomOf="@id/identity_bar" | ||
| 61 | + app:layout_constraintEnd_toStartOf="@id/mute_indicator" | ||
| 62 | + app:layout_constraintStart_toStartOf="@id/identity_bar" | ||
| 63 | + app:layout_constraintTop_toTopOf="@id/identity_bar" /> | ||
| 64 | + | ||
| 16 | <androidx.recyclerview.widget.RecyclerView | 65 | <androidx.recyclerview.widget.RecyclerView |
| 17 | android:id="@+id/audience_row" | 66 | android:id="@+id/audience_row" |
| 18 | android:layout_width="match_parent" | 67 | android:layout_width="match_parent" |
| @@ -2,13 +2,60 @@ | @@ -2,13 +2,60 @@ | ||
| 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | android:layout_width="wrap_content" | 4 | android:layout_width="wrap_content" |
| 5 | - android:layout_height="match_parent"> | 5 | + android:layout_height="match_parent" |
| 6 | + android:background="@color/no_video_background"> | ||
| 6 | 7 | ||
| 7 | - <io.livekit.android.renderer.TextureViewRenderer | ||
| 8 | - android:id="@+id/renderer" | 8 | + <FrameLayout |
| 9 | android:layout_width="0dp" | 9 | android:layout_width="0dp" |
| 10 | android:layout_height="match_parent" | 10 | android:layout_height="match_parent" |
| 11 | app:layout_constraintDimensionRatio="1:1" | 11 | app:layout_constraintDimensionRatio="1:1" |
| 12 | app:layout_constraintStart_toStartOf="parent" | 12 | app:layout_constraintStart_toStartOf="parent" |
| 13 | - app:layout_constraintTop_toTopOf="parent" /> | 13 | + android:background="@color/no_video_background" |
| 14 | + app:layout_constraintTop_toTopOf="parent"> | ||
| 15 | + | ||
| 16 | + <ImageView | ||
| 17 | + android:layout_width="60dp" | ||
| 18 | + android:layout_height="60dp" | ||
| 19 | + android:layout_gravity="center" | ||
| 20 | + android:src="@drawable/outline_videocam_off_24" | ||
| 21 | + app:tint="@color/no_video_participant" /> | ||
| 22 | + | ||
| 23 | + <io.livekit.android.renderer.TextureViewRenderer | ||
| 24 | + android:id="@+id/renderer" | ||
| 25 | + android:layout_width="match_parent" | ||
| 26 | + android:layout_height="match_parent" /> | ||
| 27 | + </FrameLayout> | ||
| 28 | + | ||
| 29 | + <FrameLayout | ||
| 30 | + android:id="@+id/identity_bar" | ||
| 31 | + android:layout_width="0dp" | ||
| 32 | + android:layout_height="30dp" | ||
| 33 | + android:background="#80000000" | ||
| 34 | + app:layout_constraintBottom_toBottomOf="parent" | ||
| 35 | + app:layout_constraintEnd_toEndOf="parent" | ||
| 36 | + app:layout_constraintStart_toStartOf="parent" /> | ||
| 37 | + | ||
| 38 | + <ImageView | ||
| 39 | + android:id="@+id/mute_indicator" | ||
| 40 | + android:layout_width="24dp" | ||
| 41 | + android:layout_height="24dp" | ||
| 42 | + android:layout_marginEnd="@dimen/identity_bar_padding" | ||
| 43 | + android:src="@drawable/outline_mic_off_24" | ||
| 44 | + app:layout_constraintBottom_toBottomOf="@id/identity_bar" | ||
| 45 | + app:layout_constraintEnd_toEndOf="@id/identity_bar" | ||
| 46 | + app:layout_constraintTop_toTopOf="@id/identity_bar" | ||
| 47 | + app:tint="#BB0000" | ||
| 48 | + android:visibility="gone" | ||
| 49 | + /> | ||
| 50 | + | ||
| 51 | + <TextView | ||
| 52 | + android:id="@+id/identity_text" | ||
| 53 | + android:layout_width="0dp" | ||
| 54 | + android:layout_height="wrap_content" | ||
| 55 | + android:layout_marginStart="@dimen/identity_bar_padding" | ||
| 56 | + android:ellipsize="end" | ||
| 57 | + app:layout_constraintBottom_toBottomOf="@id/identity_bar" | ||
| 58 | + app:layout_constraintEnd_toStartOf="@id/mute_indicator" | ||
| 59 | + app:layout_constraintStart_toStartOf="@id/identity_bar" | ||
| 60 | + app:layout_constraintTop_toTopOf="@id/identity_bar" /> | ||
| 14 | </androidx.constraintlayout.widget.ConstraintLayout> | 61 | </androidx.constraintlayout.widget.ConstraintLayout> |
| @@ -3,4 +3,6 @@ | @@ -3,4 +3,6 @@ | ||
| 3 | <color name="colorPrimary">#007DFF</color> | 3 | <color name="colorPrimary">#007DFF</color> |
| 4 | <color name="colorPrimaryDark">#0058b3</color> | 4 | <color name="colorPrimaryDark">#0058b3</color> |
| 5 | <color name="colorAccent">#66b1ff</color> | 5 | <color name="colorAccent">#66b1ff</color> |
| 6 | + <color name="no_video_participant">#5A8BFF</color> | ||
| 7 | + <color name="no_video_background">#00153C</color> | ||
| 6 | </resources> | 8 | </resources> |
| @@ -2,4 +2,5 @@ | @@ -2,4 +2,5 @@ | ||
| 2 | <resources> | 2 | <resources> |
| 3 | <dimen name="control_size">40dp</dimen> | 3 | <dimen name="control_size">40dp</dimen> |
| 4 | <dimen name="control_padding">4dp</dimen> | 4 | <dimen name="control_padding">4dp</dimen> |
| 5 | + <dimen name="identity_bar_padding">4dp</dimen> | ||
| 5 | </resources> | 6 | </resources> |
-
请 注册 或 登录 后发表评论