From c3b9d3c5f8bbc7c00d9a6bcd455c50f8011a52e5 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Wed, 24 Jul 2024 18:34:46 +0200 Subject: [PATCH] Finish Android tutorial --- .../java/io/openvidu/android/MainActivity.kt | 2 +- .../io/openvidu/android/ParticipantAdapter.kt | 23 +++++++ .../openvidu/android/ParticipantViewHolder.kt | 28 ++++++++ .../io/openvidu/android/RoomLayoutActivity.kt | 65 ++++++++++++++----- .../drawable/rounded_corner_background.xml | 6 ++ .../main/res/drawable/rounded_corner_text.xml | 8 +++ .../app/src/main/res/layout/activity_main.xml | 3 +- .../main/res/layout/activity_room_layout.xml | 52 ++++++++++++--- .../src/main/res/layout/participant_item.xml | 32 +++++++++ .../app/src/main/res/values/colors.xml | 3 + .../app/src/main/res/values/strings.xml | 1 + 11 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantAdapter.kt create mode 100644 application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantViewHolder.kt create mode 100644 application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_background.xml create mode 100644 application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_text.xml create mode 100644 application-client/openvidu-android/app/src/main/res/layout/participant_item.xml diff --git a/application-client/openvidu-android/app/src/main/java/io/openvidu/android/MainActivity.kt b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/MainActivity.kt index 6e2af8fd..c867d42b 100644 --- a/application-client/openvidu-android/app/src/main/java/io/openvidu/android/MainActivity.kt +++ b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/MainActivity.kt @@ -19,7 +19,7 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - binding.participantName.setText("Participant %d".format((1..100).random())) + binding.participantName.setText("Participant%d".format((1..100).random())) binding.joinButton.setOnClickListener { navigateToRoomLayoutActivity() diff --git a/application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantAdapter.kt b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantAdapter.kt new file mode 100644 index 00000000..36ef2c6d --- /dev/null +++ b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantAdapter.kt @@ -0,0 +1,23 @@ +package io.openvidu.android + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.livekit.android.room.Room +import io.openvidu.android.databinding.ParticipantItemBinding + +class ParticipantAdapter(private val participantTracks: List, private val room: Room) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParticipantViewHolder = + ParticipantViewHolder( + ParticipantItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ParticipantViewHolder, position: Int) { + val trackInfo = participantTracks[position] + holder.render(trackInfo, room) + } + + override fun getItemCount(): Int = participantTracks.size +} \ No newline at end of file diff --git a/application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantViewHolder.kt b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantViewHolder.kt new file mode 100644 index 00000000..e6aadcb7 --- /dev/null +++ b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/ParticipantViewHolder.kt @@ -0,0 +1,28 @@ +package io.openvidu.android + +import androidx.recyclerview.widget.RecyclerView +import io.livekit.android.room.Room +import io.openvidu.android.databinding.ParticipantItemBinding + +class ParticipantViewHolder(private val binding: ParticipantItemBinding) : + RecyclerView.ViewHolder(binding.root) { + + private var used = false + + fun render(trackInfo: TrackInfo, room: Room) { + val participantIdentity = if (trackInfo.isLocal) { + trackInfo.participantIdentity + " (You)" + } else { + trackInfo.participantIdentity + } + + binding.identity.text = participantIdentity + + if (!used) { + room.initVideoRenderer(binding.renderer) + used = true + } + + trackInfo.track.addRenderer(binding.renderer) + } +} diff --git a/application-client/openvidu-android/app/src/main/java/io/openvidu/android/RoomLayoutActivity.kt b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/RoomLayoutActivity.kt index da5ea107..e3fe2fc8 100644 --- a/application-client/openvidu-android/app/src/main/java/io/openvidu/android/RoomLayoutActivity.kt +++ b/application-client/openvidu-android/app/src/main/java/io/openvidu/android/RoomLayoutActivity.kt @@ -9,6 +9,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.cio.CIO @@ -23,17 +24,26 @@ import io.livekit.android.events.RoomEvent import io.livekit.android.events.collect import io.livekit.android.room.Room import io.livekit.android.room.track.VideoTrack +import io.livekit.android.util.flow import io.openvidu.android.databinding.ActivityRoomLayoutBinding import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +data class TrackInfo( + val track: VideoTrack, + val participantIdentity: String, + val isLocal: Boolean = false +) + class RoomLayoutActivity : AppCompatActivity() { private lateinit var binding: ActivityRoomLayoutBinding + private lateinit var participantAdapter: ParticipantAdapter private lateinit var APPLICATION_SERVER_URL: String private lateinit var LIVEKIT_URL: String private lateinit var room: Room + private val participantTracks: MutableList = mutableListOf() private val client = HttpClient(CIO) { expectSuccess = true @@ -47,22 +57,33 @@ class RoomLayoutActivity : AppCompatActivity() { binding = ActivityRoomLayoutBinding.inflate(layoutInflater) setContentView(binding.root) + binding.loader.visibility = View.VISIBLE + binding.leaveButton.setOnClickListener { + leaveRoom() + } + APPLICATION_SERVER_URL = intent.getStringExtra("serverUrl") ?: "" LIVEKIT_URL = intent.getStringExtra("livekitUrl") ?: "" // Create Room object. room = LiveKit.create(applicationContext) - // Setup the video renderer - room.initVideoRenderer(binding.renderer) - + initRecyclerView() requestNeededPermissions { connectToRoom() } } + private fun initRecyclerView() { + participantAdapter = ParticipantAdapter(participantTracks, room) + binding.participants.layoutManager = LinearLayoutManager(this) + binding.participants.adapter = participantAdapter + } + private fun connectToRoom() { val participantName = intent.getStringExtra("participantName") ?: "Participant 1" val roomName = intent.getStringExtra("roomName") ?: "Test Room" + binding.roomName.text = roomName + lifecycleScope.launch { // Setup event handling. launch { @@ -86,6 +107,24 @@ class RoomLayoutActivity : AppCompatActivity() { val localParticipant = room.localParticipant localParticipant.setMicrophoneEnabled(true) localParticipant.setCameraEnabled(true) + + // Add local video track to the participantTracks list. + launch { + localParticipant::videoTrackPublications.flow + .collect { publications -> + val videoTrack = publications.firstOrNull()?.second as? VideoTrack + + if (videoTrack != null) { + participantTracks.add( + 0, + TrackInfo(videoTrack, participantName, true) + ) + participantAdapter.notifyItemInserted(0) + } + } + } + + binding.loader.visibility = View.GONE } catch (e: Exception) { println("There was an error connecting to the room: ${e.message}") Toast.makeText(this@RoomLayoutActivity, "Failed to join room", Toast.LENGTH_SHORT) @@ -99,26 +138,22 @@ class RoomLayoutActivity : AppCompatActivity() { val track = event.track if (track is VideoTrack) { - attachVideo(track) + participantTracks.add(TrackInfo(track, event.participant.identity!!.value)) + participantAdapter.notifyItemInserted(participantTracks.size - 1) } } - private fun attachVideo(videoTrack: VideoTrack) { - videoTrack.addRenderer(binding.renderer) - binding.progress.visibility = View.GONE - } - private fun onTrackUnsubscribed(event: RoomEvent.TrackUnsubscribed) { val track = event.track if (track is VideoTrack) { - detachVideo(track) - } - } + val index = participantTracks.indexOfFirst { it.track.sid == track.sid } - private fun detachVideo(videoTrack: VideoTrack) { - videoTrack.removeRenderer(binding.renderer) - binding.progress.visibility = View.VISIBLE + if (index != -1) { + participantTracks.removeAt(index) + participantAdapter.notifyItemRemoved(index) + } + } } private fun leaveRoom() { diff --git a/application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_background.xml b/application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_background.xml new file mode 100644 index 00000000..64f73c09 --- /dev/null +++ b/application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_text.xml b/application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_text.xml new file mode 100644 index 00000000..3ec1e3cf --- /dev/null +++ b/application-client/openvidu-android/app/src/main/res/drawable/rounded_corner_text.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/application-client/openvidu-android/app/src/main/res/layout/activity_main.xml b/application-client/openvidu-android/app/src/main/res/layout/activity_main.xml index 10880abb..e65dc590 100644 --- a/application-client/openvidu-android/app/src/main/res/layout/activity_main.xml +++ b/application-client/openvidu-android/app/src/main/res/layout/activity_main.xml @@ -49,7 +49,8 @@ android:inputType="text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/participantLabel" /> + app:layout_constraintTop_toBottomOf="@+id/participantLabel" + tools:text="Participant1" /> - - - - + android:layout_marginStart="16dp" + android:textSize="30sp" + android:textStyle="bold" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="Test Room" /> - \ No newline at end of file +