Quick Start for Conference in Android
This quick start shows how to build a group video calling app with the VideoSDK Android SDK, step by step.
Prerequisites
Before proceeding, ensure that your development environment meets the following requirements:
- Java Development Kit.
- Android Studio Narwhal 3 Feature Drop (2025.1.3) or later.
- Android SDK API Level 24 or higher.
- A mobile device that runs Android 7.0 or later with an ARM (
arm64-v8aorarmeabi-v7a) processor, or an emulator with anarm64-v8asystem image.
You need a VideoSDK account to generate a token. Generate the token on the VideoSDK dashboard.
Getting Started with the Code!
Follow the steps to create the environment necessary to add video calls into your app.
Create new Android Project
For a new project in Android Studio, create a Phone and Tablet Android project with an Empty Views Activity. This template adds AppCompat and Material Components, which this quick start uses.

After creating the project, Android Studio automatically starts gradle sync. Ensure that the sync succeeds before you continue.
Integrate Video SDK
dependencyResolutionManagement {
repositories {
// ...
google()
mavenCentral()
}
}
- Add the following dependency in your app's
app/build.gradle.kts.
dependencies {
implementation("live.videosdk:rtc-android-sdk:3.0.0")
// library to perform Network call to generate a room id
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// lifecycleScope, to call the SDK's suspend functions
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
// RecyclerView, to show the participants
implementation("androidx.recyclerview:recyclerview:1.3.2")
// other app dependencies
}
Android SDK runs on arm64-v8a and armeabi-v7a devices only. If you want to run the application in an emulator, choose an arm64-v8a system image when creating a device. x86 / x86_64 emulator images are not supported.
Add permissions into your project
- In
/app/Manifests/AndroidManifest.xml, add the following permissions after</application>. The SDK doesn't addINTERNETorMODIFY_AUDIO_SETTINGSto your app, so declare them here.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
Structure of the project
Your project structure should look like this.
app
├── java
│ ├── packagename
│ ├── JoinActivity
│ ├── MainApplication
│ ├── RoomActivity
│ ├── ParticipantAdapter
├── res
│ ├── layout
│ │ ├── activity_join.xml
│ │ ├── activity_room.xml
│ │ ├── item_remote_peer.xml
You have to set JoinActivity as Launcher activity.
App Architecture

Step 1: Initialize VideoSDK
- Create
MainApplicationclass which will extend theandroid.app.Application.
import android.app.Application
import live.videosdk.rtc.android.VideoSDK
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
VideoSDK.initialize(applicationContext)
}
}
- Add
MainApplicationtoAndroidManifest.xml
<application
android:name=".MainApplication" >
<!-- ... -->
<activity
android:name=".RoomActivity"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden" />
</application>
android:configChanges keeps RoomActivity, and its room, alive when the device rotates. A recreated RoomActivity would call join() again, which the SDK refuses while the first room is still joined.
Step 2: Creating Joining Screen
Create a new Activity named JoinActivity. The Joining screen will include:
- Create Button - This button will create a new room for you.
- TextField for Room ID - This text field will contain the room ID you want to join.
- Join Button - This button will join the room with the
roomIdyou provided.
In /app/res/layout/activity_join.xml file, replace the content with the following.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".JoinActivity">
<Button
android:id="@+id/btnCreateRoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Create Room" />
<TextView
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OR" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:hint="Enter Room ID">
<EditText
android:id="@+id/etRoomId"
android:layout_width="250dp"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnJoinRoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Join Room" />
</LinearLayout>
Here is the implementation for the Join Activity:
- Create field
sampleTokeninJoinActivitywhich will hold the generated token from the VideoSDK dashboard. This token is passed toVideoSDK.createRoom()and is also used to generate aroomId.
class JoinActivity : AppCompatActivity() {
//Replace with the token you generated from the VideoSDK Dashboard
private var sampleToken = ""
override fun onCreate(savedInstanceState: Bundle?) {
//...
}
}
- On Join Button
onClickevents, we will navigate toRoomActivitywith the token androomId.
class JoinActivity : AppCompatActivity() {
//Replace with the token you generated from the VideoSDK Dashboard
private var sampleToken = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_join)
val btnCreate = findViewById<Button>(R.id.btnCreateRoom)
val btnJoin = findViewById<Button>(R.id.btnJoinRoom)
val etRoomId = findViewById<EditText>(R.id.etRoomId)
btnCreate.setOnClickListener { v: View? ->
createRoomId(sampleToken)
}
btnJoin.setOnClickListener { v: View? ->
val intent = Intent(this@JoinActivity, RoomActivity::class.java)
intent.putExtra("token", sampleToken)
intent.putExtra("roomId", etRoomId.text.toString())
startActivity(intent)
}
}
private fun createRoomId(token: String) {
// we will explore this method in the next step
}
}
- For Create Button, under
createRoomIdmethod we will generate aroomIdby calling the API and navigate toRoomActivitywith the token and generatedroomId.
class JoinActivity : AppCompatActivity() {
//...onCreate
private fun createRoomId(token: String) {
// we will make an API call to VideoSDK Server to get a roomId
lifecycleScope.launch {
try {
val roomId = withContext(Dispatchers.IO) {
val request = Request.Builder()
.url("https://api.videosdk.live/v2/rooms")
.header("Authorization", token) //we will pass the token in the Headers
.post("".toRequestBody())
.build()
OkHttpClient().newCall(request).execute().use { response ->
val body = response.body?.string().orEmpty()
check(response.isSuccessful) { "HTTP ${response.code}: $body" }
// response will contain `roomId`
JSONObject(body).getString("roomId")
}
}
// starting the RoomActivity with received roomId and our sampleToken
val intent = Intent(this@JoinActivity, RoomActivity::class.java)
intent.putExtra("token", sampleToken)
intent.putExtra("roomId", roomId)
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this@JoinActivity, e.message, Toast.LENGTH_SHORT).show()
}
}
}
}
The VideoSDK dashboard and some REST API fields still say "meeting" (for example meetingId). A meeting there is the same thing as a room.
- Our App is completely based on audio and video communication, that's why we need to ask for runtime permissions
RECORD_AUDIOandCAMERA. So, we will implement permission logic onJoinActivity.
class JoinActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQ_ID = 22
private val REQUESTED_PERMISSIONS = arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
)
}
private fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode)
return false
}
return true
}
override fun onCreate(savedInstanceState: Bundle?) {
//... button listeners
checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID)
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)
}
}
Output

Step 3: Creating Room Screen
Create a new Activity named RoomActivity.
In /app/res/layout/activity_room.xml file, replace the content with the following.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".RoomActivity">
<TextView
android:id="@+id/tvRoomId"
style="@style/TextAppearance.AppCompat.Display1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Room ID" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvParticipants"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/btnMic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:text="Mic"/>
<Button
android:id="@+id/btnLeave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginHorizontal="8dp"
android:text="Leave"/>
<Button
android:id="@+id/btnWebcam"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:text="Webcam" />
</LinearLayout>
</LinearLayout>
The next step is to create the room, after getting the token and roomId from JoinActivity:
- Create the room with
VideoSDK.createRoom(), passingroomId,token,participantName,micEnabled,camEnabledand more. It throwsVideoSDKExceptionfor an emptytokenorroomId. - Add a
RoomEventListenerto listen for events such as Room Joined/Left and Participant Joined/Left. - Join the room with
room.join()method. It is asuspendfunction, so call it from a coroutine and catchVideoSDKException.
class RoomActivity : AppCompatActivity() {
// declare the variables we will be using to handle the room
private var room: Room? = null
private var micEnabled = true
private var camEnabled = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
val token = intent.getStringExtra("token").orEmpty()
val roomId = intent.getStringExtra("roomId").orEmpty()
val participantName = "John Doe"
try {
// 1. Create the VideoSDK room
room = VideoSDK.createRoom(
roomId = roomId,
token = token,
participantName = participantName,
micEnabled = micEnabled,
camEnabled = camEnabled,
)
} catch (e: VideoSDKException) {
// thrown for an empty token or room id
Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
finish()
return
}
// 2. Add event listener to listen for events
room!!.addEventListener(roomEventListener)
// 3. Join the room. join() is a suspend function, so call it from a coroutine.
lifecycleScope.launch {
try {
room?.join()
} catch (e: VideoSDKException) {
Toast.makeText(this@RoomActivity, "Could not join: ${e.message}", Toast.LENGTH_LONG).show()
finish()
}
}
(findViewById<View>(R.id.tvRoomId) as TextView).text = roomId
}
// creating the RoomEventListener
private val roomEventListener: RoomEventListener = object : RoomEventListener() {
override fun onRoomJoined(switchRoomId: String?) {
Log.d("#room", "onRoomJoined()")
}
override fun onRoomLeft(reason: LeaveReason) {
Log.d("#room", "onRoomLeft()")
room = null
if (!isDestroyed) finish()
}
override fun onParticipantJoined(participant: Participant) {
Toast.makeText(
this@RoomActivity, participant.displayName + " joined",
Toast.LENGTH_SHORT
).show()
}
override fun onParticipantLeft(participant: Participant, reason: LeaveReason) {
Toast.makeText(
this@RoomActivity, participant.displayName + " left",
Toast.LENGTH_SHORT
).show()
}
}
}
Step 4: Handle Local Participant Media
After successfully joining the room, it's time to enable/disable the local participant's (your) camera and mic. For that we will use the Room class methods enableCam / disableCam for the camera and muteMic / unmuteMic for the mic. These are suspend functions, so each button launches a coroutine. room.isMicEnabled and room.isCamEnabled tell you the current state.
class RoomActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
//...Room Setup is Here
// actions
setActionListeners()
}
private fun setActionListeners() {
// toggle mic
findViewById<View>(R.id.btnMic).setOnClickListener {
val room = room ?: return@setOnClickListener
lifecycleScope.launch {
try {
if (room.isMicEnabled) {
// this will mute the local participant's mic
room.muteMic()
Toast.makeText(this@RoomActivity, "Mic Muted", Toast.LENGTH_SHORT).show()
} else {
// this will unmute the local participant's mic
room.unmuteMic()
Toast.makeText(this@RoomActivity, "Mic Enabled", Toast.LENGTH_SHORT).show()
}
} catch (e: VideoSDKException) {
Toast.makeText(this@RoomActivity, e.message, Toast.LENGTH_SHORT).show()
}
}
}
// toggle camera
findViewById<View>(R.id.btnWebcam).setOnClickListener {
val room = room ?: return@setOnClickListener
lifecycleScope.launch {
try {
if (room.isCamEnabled) {
// this will disable the local participant's camera
room.disableCam()
Toast.makeText(this@RoomActivity, "Camera Disabled", Toast.LENGTH_SHORT).show()
} else {
// this will enable the local participant's camera
room.enableCam()
Toast.makeText(this@RoomActivity, "Camera Enabled", Toast.LENGTH_SHORT).show()
}
} catch (e: VideoSDKException) {
Toast.makeText(this@RoomActivity, e.message, Toast.LENGTH_SHORT).show()
}
}
}
// leave the room
findViewById<View>(R.id.btnLeave).setOnClickListener {
leaveRoom()
}
// the Back button leaves the room too, instead of leaving it running
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
leaveRoom()
}
})
}
private fun leaveRoom() {
lifecycleScope.launch {
try {
// this will make the local participant leave the room;
// onRoomLeft then closes this screen
room?.leave()
} catch (e: VideoSDKException) {
// the join never completed, so there is nothing to leave
finish()
}
}
}
}
Output

Step 5: Handling the Participants View
We will show the list of participants in a RecyclerView.
- Here the participant's video is displayed using
VideoView. - To know more about
VideoView, please visit here
- Create a new layout for the participant view named
item_remote_peer.xmlin theres/layoutfolder.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/cardview_dark_background"
tools:layout_height="200dp">
<live.videosdk.rtc.android.VideoView
android:id="@+id/participantView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#99000000"
android:orientation="horizontal">
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="4dp"
android:textColor="@color/white" />
</LinearLayout>
</FrameLayout>
- Create a recycler view adapter named
ParticipantAdapterwhich will show the participant list. CreatePeerViewHolderin the adapter which will extendRecyclerView.ViewHolder.
class ParticipantAdapter(room: Room) : 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)
}
}
}
- Now, we will render a list of
Participantfor the room. We will initialize this list in the constructor of theParticipantAdapter
class ParticipantAdapter(room: Room) :
RecyclerView.Adapter<ParticipantAdapter.PeerViewHolder>() {
// creating an empty list which will store all participants
private val participants: MutableList<Participant> = ArrayList()
init {
// adding the local participant(You) to the list
participants.add(room.localParticipant)
// adding Room Event listener to get the participant join/leave event in the room.
room.addEventListener(object : RoomEventListener() {
override fun onParticipantJoined(participant: Participant) {
// add participant to the list
participants.add(participant)
notifyItemInserted(participants.size - 1)
}
override fun onParticipantLeft(participant: Participant, reason: LeaveReason) {
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
}
//...
}
- We have listed our participants. Let's set up the view holder to display a participant video.
import live.videosdk.rtc.android.MediaType
import videosdk.org.webrtc.VideoTrack
class ParticipantAdapter(room: Room) :
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
// add Listener to the participant which will update start or stop the video stream of that participant.
// Adding it also calls onStreamEnabled for the streams the participant already has.
participant.addEventListener(object : ParticipantEventListener() {
override fun onStreamEnabled(stream: RTCStream) {
if (stream.kind == MediaType.VIDEO) {
holder.participantView.visibility = View.VISIBLE
val videoTrack = stream.track as VideoTrack
holder.participantView.addTrack(videoTrack)
}
}
override fun onStreamDisabled(stream: RTCStream) {
if (stream.kind == MediaType.VIDEO) {
holder.participantView.removeTrack()
holder.participantView.visibility = View.GONE
}
}
})
}
}
- Add this adapter to the
RoomActivity
override fun onCreate(savedInstanceState: Bundle?) {
//Room Setup...
//...
val rvParticipants = findViewById<RecyclerView>(R.id.rvParticipants)
rvParticipants.layoutManager = GridLayoutManager(this, 2)
rvParticipants.adapter = ParticipantAdapter(room!!)
}
Final Output
You have built a video calling app in Android with VideoSDK. To explore more features, go through Basic and Advanced features.
Got a Question? Ask us on discord

