Introduction
In this technological landscape, where seamless video experiences are in high demand, integrating real-time chat features into Android applications has become paramount. Incorporating chat functionality within an Android(Kotlin) video call app is a strategic move.
This article aims to guide you through the process of leveraging the capabilities of the VideoSDK, integrating chat, and using pubSub in your Android (Kotlin) Video call app. Our goal is to empower you to create a dynamic and immersive user experience, ensuring robust communication capabilities within your application.
Goals
By the End of this Article:
- Create a VideoSDK account and generate your VideoSDK auth token.
- Integrate the VideoSDK library and dependencies into your project.
- Implement core functionalities for video calls using VideoSDK.
- Enable Chat feature.
Getting Started with VideoSDK
To take advantage of the chat functionality, we will need to use the capabilities that the VideoSDK offers. Before we dive into the implementation steps, let's make sure you complete the necessary prerequisites.
Create a VideoSDK Account
Go to your VideoSDK dashboard and sign up if you don't have an account. This account gives you access to the required Video SDK token, which acts as an authentication key that allows your application to interact with VideoSDK functionality.
Generate your Auth Token
Visit your VideoSDK dashboard and navigate to the "API Key" section to generate your auth token. This token plays a crucial role in authorizing your application to use VideoSDK features.
For a more visual understanding of the account creation and token generation process, consider referring to the provided tutorial.
Prerequisites and Setup
Make sure your development environment meets the following requirements:
- Java Development Kit is supported.
- Android Studio version 3.0 or later.
- Android SDK API level 21 or higher.
- A mobile device with Android 5.0 or later version.
Integrate VideoSDK
Following the account creation and token generation steps, we'll guide you through the process of adding the VideoSDK library and other dependencies to your project. We'll also ensure your app has the required permissions to access features like audio recording, camera usage, and internet connectivity, all crucial for a seamless video experience.
Step (a): Add the repositories to the project's settings.gradle
file.
settings.gradle
file.dependencyResolutionManagement {
repositories {
// ...
google()
mavenCentral()
maven { url '<https://jitpack.io>' }
maven { url "<https://maven.aliyun.com/repository/jcenter>" }
}
}
Step (b): Include the following dependency within your application's build.gradle
file:
build.gradle
file:dependencies {
implementation 'live.videosdk:rtc-android-sdk:0.1.26'
// library to perform Network call to generate a meeting id
implementation 'com.amitshekhar.android:android-networking:1.0.2'
// Other dependencies specific to your app
}
If your project has setandroid.useAndroidX=true
, then setandroid.enableJetifier=true
in thegradle.properties
file to migrate your project to AndroidX and avoid duplicate class conflict.
Step (c): Add permissions to your project
In /app/Manifests/AndroidManifest.xml
, add the following permissions after </application>
.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
These permissions are essential for enabling core functionalities like audio recording, internet connectivity for real-time communication, and camera access for video streams within your video application.
Essential Steps for Building the Video Calling Functionality
We'll now delve into the functionalities that make your video application after setting up your project with VideoSDK. This section outlines the essential steps for implementing core functionalities within your app.
This section will guide you through four key aspects:
Step 1: Generate a meetingId
meetingId
Now, we can create the meetingId
from the VideoSDK's rooms API. You can refer to this documentation to generate meetingId.
Step 2: Initializing the Meeting
After getting meetingId
, the next step involves initializing the meeting for that we need to,
- Initialize VideoSDK.
- Configure VideoSDK with the token.
- Initialize the meeting with required params such as
meetingId
,participantName
,micEnabled
,webcamEnabled
and more. - Add
MeetingEventListener
for listening events such as Meeting Join/Left and Participant Join/Left. - Join the room with
meeting.join()
a method.
Please copy the .xml file of the MeetingActivity
from here.
class MeetingActivity : AppCompatActivity() {
// declare the variables we will be using to handle the meeting
private var meeting: Meeting? = null
private var micEnabled = true
private var webcamEnabled = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meeting)
val token = "" // Replace with the token you generated from the VideoSDK Dashboard
val meetingId = "" // Replace with the meetingId you have generated
val participantName = "John Doe"
// 1. Initialize VideoSDK
VideoSDK.initialize(applicationContext)
// 2. Configuration VideoSDK with Token
VideoSDK.config(token)
// 3. Initialize VideoSDK Meeting
meeting = VideoSDK.initMeeting(
this@MeetingActivity, meetingId, participantName,
micEnabled, webcamEnabled,null, null, false, null, null)
// 4. Add event listener for listening upcoming events
meeting!!.addEventListener(meetingEventListener)
// 5. Join VideoSDK Meeting
meeting!!.join()
(findViewById<View>(R.id.tvMeetingId) as TextView).text = meetingId
}
// creating the MeetingEventListener
private val meetingEventListener: MeetingEventListener = object : MeetingEventListener() {
override fun onMeetingJoined() {
Log.d("#meeting", "onMeetingJoined()")
}
override fun onMeetingLeft() {
Log.d("#meeting", "onMeetingLeft()")
meeting = null
if (!isDestroyed) finish()
}
override fun onParticipantJoined(participant: Participant) {
Toast.makeText(
this@MeetingActivity, participant.displayName + " joined",
Toast.LENGTH_SHORT
).show()
}
override fun onParticipantLeft(participant: Participant) {
Toast.makeText(
this@MeetingActivity, participant.displayName + " left",
Toast.LENGTH_SHORT
).show()
}
}
}
Step 3: Handle Local Participant Media
After successfully entering the meeting, it's time to manage the webcam and microphone for the local participant (you).
To enable or disable the webcam, we'll use the Meeting
class methods enableWebcam()
and disableWebcam()
, respectively. Similarly, to mute or unmute the microphone, we'll utilize the methods muteMic()
and unmuteMic()
class MeetingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meeting)
//...Meeting Setup is Here
// actions
setActionListeners()
}
private fun setActionListeners() {
// toggle mic
findViewById<View>(R.id.btnMic).setOnClickListener { view: View? ->
if (micEnabled) {
// this will mute the local participant's mic
meeting!!.muteMic()
Toast.makeText(this@MeetingActivity, "Mic Muted", Toast.LENGTH_SHORT).show()
} else {
// this will unmute the local participant's mic
meeting!!.unmuteMic()
Toast.makeText(this@MeetingActivity, "Mic Enabled", Toast.LENGTH_SHORT).show()
}
micEnabled=!micEnabled
}
// toggle webcam
findViewById<View>(R.id.btnWebcam).setOnClickListener { view: View? ->
if (webcamEnabled) {
// this will disable the local participant webcam
meeting!!.disableWebcam()
Toast.makeText(this@MeetingActivity, "Webcam Disabled", Toast.LENGTH_SHORT).show()
} else {
// this will enable the local participant webcam
meeting!!.enableWebcam()
Toast.makeText(this@MeetingActivity, "Webcam Enabled", Toast.LENGTH_SHORT).show()
}
webcamEnabled=!webcamEnabled
}
// leave meeting
findViewById<View>(R.id.btnLeave).setOnClickListener { view: View? ->
// this will make the local participant leave the meeting
meeting!!.leave()
}
}
}
Step 4: Handling the Participants' View
To display a list of participants in your video UI, we'll utilize a RecyclerView
.
(a) This involves creating a new layout for the participant view named item_remote_peer.xml
in the res/layout
folder. You can copy item_remote_peer.xml
file from here.
(b) Create a RecyclerView adapter ParticipantAdapter
which will be responsible for displaying the participant list. Within this adapter, define a PeerViewHolder
class that extends RecyclerView.ViewHolder
.
class ParticipantAdapter(meeting: Meeting) : RecyclerView.Adapter<ParticipantAdapter.PeerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PeerViewHolder {
return PeerViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_remote_peer, parent, false)
)
}
override fun onBindViewHolder(holder: PeerViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return 0
}
class PeerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
// 'VideoView' to show Video Stream
var participantView: VideoView
var tvName: TextView
init {
tvName = view.findViewById(R.id.tvName)
participantView = view.findViewById(R.id.participantView)
}
}
}
(c) Now, we will render a list of Participant
for the meeting. We will initialize this list in the constructor of the ParticipantAdapter
class ParticipantAdapter(meeting: Meeting) :
RecyclerView.Adapter<ParticipantAdapter.PeerViewHolder>() {
// creating a empty list which will store all participants
private val participants: MutableList<Participant> = ArrayList()
init {
// adding the local participant(You) to the list
participants.add(meeting.localParticipant)
// adding Meeting Event listener to get the participant join/leave event in the meeting.
meeting.addEventListener(object : MeetingEventListener() {
override fun onParticipantJoined(participant: Participant) {
// add participant to the list
participants.add(participant)
notifyItemInserted(participants.size - 1)
}
override fun onParticipantLeft(participant: Participant) {
var pos = -1
for (i in participants.indices) {
if (participants[i].id == participant.id) {
pos = i
break
}
}
// remove participant from the list
participants.remove(participant)
if (pos >= 0) {
notifyItemRemoved(pos)
}
}
})
}
// replace getItemCount() method with following.
// this method returns the size of total number of participants
override fun getItemCount(): Int {
return participants.size
}
//...
}
(d) We have listed our participants. Let's set up the view holder to display a participant video.
class ParticipantAdapter(meeting: Meeting) :
RecyclerView.Adapter<ParticipantAdapter.PeerViewHolder>() {
// replace onBindViewHolder() method with following.
override fun onBindViewHolder(holder: PeerViewHolder, position: Int) {
val participant = participants[position]
holder.tvName.text = participant.displayName
// adding the initial video stream for the participant into the 'VideoView'
for ((_, stream) in participant.streams) {
if (stream.kind.equals("video", ignoreCase = true)) {
holder.participantView.visibility = View.VISIBLE
val videoTrack = stream.track as VideoTrack
holder.participantView.addTrack(videoTrack)
break
}
}
// add Listener to the participant which will update start or stop the video stream of that participant
participant.addEventListener(object : ParticipantEventListener() {
override fun onStreamEnabled(stream: Stream) {
if (stream.kind.equals("video", ignoreCase = true)) {
holder.participantView.visibility = View.VISIBLE
val videoTrack = stream.track as VideoTrack
holder.participantView.addTrack(videoTrack)
}
}
override fun onStreamDisabled(stream: Stream) {
if (stream.kind.equals("video", ignoreCase = true)) {
holder.participantView.removeTrack()
holder.participantView.visibility = View.GONE
}
}
})
}
}
(e) Now, add this adapter to the MeetingActivity
override fun onCreate(savedInstanceState: Bundle?) {
// Meeting Setup...
//...
val rvParticipants = findViewById<RecyclerView>(R.id.rvParticipants)
rvParticipants.layoutManager = GridLayoutManager(this, 2)
rvParticipants.adapter = ParticipantAdapter(meeting!!)
}
Integrate Chat feature
This section delineates the process of implementing group and private chat functionalities within your Android video App. The VideoSDK provides pubSub
the class that uses the Publish-Subscribe mechanism and can be used to develop various functions.
For example, participants can use it to send chat messages to each other, share files or other media, or trigger actions such as muting or unmuting audio or video. Now we will see how we can use pubSub to implement chat functionality. If you are not familiar with the pub-sub mechanism and pubSub
class, you can follow this guide.
Let's delve deeper into the implementation of chat functionality, exploring both group and private chat features, along with the seamless integration of chat message downloads for enhanced user engagement and convenience.
Implement Group Chat
- The first step in creating a group chat is choosing the topic that all the participants will publish and subscribe to send and receive the messages. We will be using
CHAT
as the topic for this one. - On the send button, we will publish the message that the sender typed in the
EditText
field.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import live.videosdk.rtc.android.Meeting
import live.videosdk.rtc.android.listeners.PubSubMessageListener
import live.videosdk.rtc.android.model.PubSubPublishOptions
class ChatActivity : AppCompatActivity() {
// Meeting
var meeting: Meeting? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
/**
* Here, we have created 'MainApplication' class, which extends android.app.Application class.
* It has Meeting property and getter and setter methods of Meeting property.
* In your android manifest, you must declare the class implementing android.app.Application
* (add the android:name=".MainApplication" attribute to the existing application tag):
* In MainActivity.kt, we have set Meeting property.
*
* For Example: (MainActivity.kt)
* var meeting = VideoSDK.initMeeting(context, meetingId, ParticipantName, micEnabled, webcamEnabled,paricipantId,mode,multiStream,customTrack,metaData)
* (this.application as MainApplication).meeting = meeting
*/
// Get Meeting
meeting = (this.application as MainApplication).meeting
findViewById(R.id.btnSend).setOnClickListener(view -> sendMessage());
}
private fun sendMessage() {
// get message from EditText
val message: String = etmessage.getText().toString()
if (!TextUtils.isEmpty(message)) {
val publishOptions = PubSubPublishOptions()
publishOptions.setPersist(true)
// Sending the Message using the publish method
meeting!!.pubSub.publish("CHAT", message, publishOptions)
// Clearing the message input
etmessage.setText("")
} else {
Toast.makeText(
this@ChatActivity, "Please Enter Message",
Toast.LENGTH_SHORT
).show()
}
}
}
- The next step would be to display the messages others send. For this, we have to
subscribe
to that topic i.eCHAT
and display all the messages.
class ChatActivity : AppCompatActivity() {
// PubSubMessageListener
var pubSubMessageListener =
PubSubMessageListener { message ->
// New message
Toast.makeText(
this@ChatActivity, message.senderName + " says : " + message.message,
Toast.LENGTH_SHORT
).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
//...
// Subscribe for 'CHAT' topic
val pubSubMessageList = meeting!!.pubSub.subscribe("CHAT", pubSubMessageListener)
for (message in pubSubMessageList) {
// Persisted messages
Toast.makeText(
this@ChatActivity, message.senderName + " says : " + message.message,
Toast.LENGTH_SHORT
).show()
}
}
}
- The final step in the group chat would be
unsubscribe
to that topic, which you had previously subscribed to but no longer needed. Here we areunsubscribe
toCHAT
topic on activity destroy.
class ChatActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
//...
}
override fun onDestroy() {
// Unsubscribe for 'CHAT' topic
meeting!!.pubSub.unsubscribe("CHAT", pubSubMessageListener)
super.onDestroy()
}
}
Implement Private Chat
- If you want to convert into a private chat between two participants, then all you have to do is pass
sendOnly
parameter inPubSubPublishOptions
.
class ChatActivity : AppCompatActivity() {
//..
private fun sendMessage() {
// get message from EditText
val message: String = etmessage.getText().toString()
if (!TextUtils.isEmpty(message)) {
val publishOptions = PubSubPublishOptions()
publishOptions.setPersist(true)
// Pass the participantId of the participant to whom you want to send the message.
var sendOnly: Array<String> = arrayOf("xyz")
publishOptions.setSendOnly(sendOnly);
// Sending the Message using the publish method
meeting!!.pubSub.publish("CHAT", message, publishOptions)
// Clearing the message input
etmessage.setText("")
} else {
Toast.makeText(
this@ChatActivity, "Please Enter Message",
Toast.LENGTH_SHORT
).show()
}
}
}
Downloading Chat Messages
All the messages from the PubSub that were published persist : true
can be downloaded as a .csv
file. This file will be available in the VideoSDK dashboard as well as through the Sessions API.
Conclusion
Integrating Chat using pubSub in your Android (Kotlin) video call app elevates your app's communication capabilities to new heights. Whether it's facilitating group chats, enabling private conversations, or downloading chat messages for analysis, you can enhance the communication capabilities of your app, providing users with a richer, more immersive video chat experience.
Sign up with VideoSDK today and Get 10000 minutes free to take your video app to the next level!