Build 1on1 Video Chat App in Kotlin for Android

Build 1on1 Video Chat App in Kotlin for Android

After the pandemic, remote communication became super important. And it's not going away anytime soon. Nowadays, lots of mobile apps let you talk or see each other using voice or video. But making these features is tough and takes a long time. That's where VideoSDK comes in. It's like a toolbox for developers. It gives them easy-to-use tools to add video calls, voice chats, and interactive stuff into their apps without needing to build all that fancy tech from scratch.

Goals:
By the end of this blog, you'll know:

  1. What is a VideoSDK ?

  2. How to sign up for a VideoSDK account and get a token.

  3. How to make a simple 1-to-1 video calling app for Android in Kotlin with VideoSDK.

VideoSDK is like a toolbox for developers. It helps them make cool stuff inside apps, like live video, voice calls, recording, live streaming, and messaging. It works with different coding languages like JavaScript, Reactjs, React-Native, iOS, Android, and Flutter. You can use it to add real-time communication features to your app in just 10 minutes using its pre-made tools.

Before we start making our 1-to-1 video chat app, we need to do two things,

  1. Create an account on VideoSDK.

  2. Generate a token.

Creating an account means signing up for VideoSDK, so we can use their tools. Generating a token is like getting a special key that lets our app connect to Video SDK's features. Once we have these setup, we can start building our video chat app!

Create a VideoSDK account and generate a token

A VideoSDK Token (Dashboard > Api-Key) (Video Tutorial)

Prerequisites & Setup of the project

Before you start, make sure you have,

  1. Java Development Kit: It's a tool you need to build Android apps.

  2. Android Studio 3.0 or newer: This is the software where you'll build your app.

  3. Android SDK API Level 21 or higher: It's like a set of tools for building Android apps.

  4. A mobile device with Android 5.0 or newer: You'll need this to test your app once it's done.

Create a new project Let’s start by creating a new project.

  • In Android Studio, create a Phone and Tablet Android project with an Empty Activity.

  • Let’s start by creating a new project. In Android Studio, create a Phone and Tablet Android project with an Empty Activity.

Integrate VideoSDK

  • Add the repository to the project'ssettings.gradle file.
dependencyResolutionManagement{
  repositories {
    // ...
    google()
    mavenCentral()
    maven { url 'https://jitpack.io' }
    maven { url "https://maven.aliyun.com/repository/jcenter" }
  }
}
  • Add the following dependency to your app'sbuild.gradle.
dependencies {
  implementation 'live.videosdk:rtc-android-sdk:0.1.13'

  // library to perform Network call to generate a meeting id
  implementation 'com.amitshekhar.android:android-networking:1.0.2'

  // other app dependencies
  }

If your project has set android.useAndroidX=true, then set android.enableJetifier=true in the gradle.properties file to migrate your project to AndroidX and avoid duplicate class conflict.

  • 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" />

Follow these 9 steps to Create a 1-to-1 video chat app on Android.

STEP 1: Video Chat Android SDK Structure of Project

We will create two screens. The first screen is Joining screen, which allows the user to create/join the meeting, and the other is Meeting screen , which will show participants a Whatsapp-like view.

Our project structure would look like this.

   app
   ├── java
   │    ├── packagename
   │         ├── JoinActivity
   │         ├── MeetingActivity
   ├── res
   │    ├── layout
   │    │    ├── activity_join.xml
   │    │    ├── activity_meeting.xml

You have to set JoinActivity as Launcher activity.

  • Creating Joining Screen

Create a new Activity named JoinActivity

STEP 2: Video Chat Android SDK Creating UI for Joining Screen

The Joining screen will include,

  1. Create Button - This button will create a new meeting for you.

  2. TextField for Meeting ID - This text field will contain the meeting ID you want to join.

  3. Join Button - This button will join the meeting with meetingId you provided.

<?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=".JoinActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/material_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:contentInsetStart="0dp"
        android:background="?attr/colorPrimary"
        app:titleTextColor="@color/white" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">


        <Button
            android:id="@+id/btnCreateMeeting"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:text="Create Meeting" />

        <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 Meeting ID">

            <EditText
                android:id="@+id/etMeetingId"
                android:layout_width="250dp"
                android:layout_height="wrap_content" />
        </com.google.android.material.textfield.TextInputLayout>

        <Button
            android:id="@+id/btnJoinMeeting"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Join Meeting" />
    </LinearLayout>

</LinearLayout>

STEP 3: Video Chat Android SDK Integration of Create Meeting API

  1. Create a field sampleToken in JoinActivity which will hold the generated token from the VideoSDK dashboard. This token will be used in the VideoSDK config as well as generating meetingId.
class JoinActivity : AppCompatActivity() {

  //Replace with the token you generated from the Video SDK Dashboard
  private var sampleToken = ""

  override fun onCreate(savedInstanceState: Bundle?) {
    //...
  }
}
  1. On the Join ButtononClick event, we will navigate to MeetingActivity with token and meetingId.

     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.btnCreateMeeting)
             val btnJoin = findViewById<Button>(R.id.btnJoinMeeting)
             val etMeetingId = findViewById<EditText>(R.id.etMeetingId)
    
             //set title
             val toolbar = findViewById<Toolbar>(R.id.material_toolbar)
             toolbar.title = "OneToOneDemo"
             setSupportActionBar(toolbar)
    
             btnCreate.setOnClickListener { v: View? ->
                 // we will explore this method in the next step
                 createMeeting(sampleToken)
             }
             btnJoin.setOnClickListener { v: View? ->
                 val intent = Intent(this@JoinActivity, MeetingActivity::class.java)
                 intent.putExtra("token", sampleToken)
                 intent.putExtra("meetingId", etMeetingId.text.toString())
                 startActivity(intent)
             }
         }
    
         private fun createMeeting(token: String) {
         }
     }
    
    1. For the Create Button, under createMeeting method we will generate meetingId by calling API and navigating to MeetingActivity with token and generated meetingId.
    class JoinActivity : AppCompatActivity() {
      //...onCreate
     private fun createMeeting(token: String) {
        // we will make an API call to VideoSDK Server to get a roomId
        AndroidNetworking.post("https://api.videosdk.live/v2/rooms")
          .addHeaders("Authorization", token) //we will pass the token in the Headers
          .build()
          .getAsJSONObject(object : JSONObjectRequestListener {
              override fun onResponse(response: JSONObject) {
                  try {
                      // response will contain `roomId`
                      val meetingId = response.getString("roomId")

                      // starting the MeetingActivity with received roomId and our sampleToken
                      val intent = Intent(this@JoinActivity, MeetingActivity::class.java)
                      intent.putExtra("token", sampleToken)
                      intent.putExtra("meetingId", meetingId)
                      startActivity(intent)
                  } catch (e: JSONException) {
                      e.printStackTrace()
                  }
              }

              override fun onError(anError: ANError) {
                  anError.printStackTrace()
                        Toast.makeText(
                            this@JoinActivity, anError.message,
                            Toast.LENGTH_SHORT
                        ).show()
              }
          })
      }
    }
  1. Our App is completely based on audio and video commutation, that's why we need to ask for runtime permissions RECORD_AUDIO and CAMERA. So, we will implement permission logic on JoinActivity.
    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 listeneres
        checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID)
        checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)
      }
    }

You will get Unresolved reference: MeetingActivity error, but don't worry. It will be solved automatically once you create MeetingActivity.

  1. The Joining screen is now complete, and it is time to create the participant's view in the Meeting screen. The Joining screen will look like this.

Creating Meeting Screen
Create a new Activity named MeetingActivity.

STEP 4: Video Chat Android SDK Creating the UI for Meeting Screen

In /app/res/layout/activity_meeting.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:id="@+id/mainLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MeetingActivity">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/material_toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/black"
            app:contentInsetStart="0dp"
            app:titleTextColor="@color/white">

            <LinearLayout
                android:id="@+id/meetingLayout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="14dp"
                android:layout_marginTop="10dp"
                android:orientation="horizontal">

                <RelativeLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content">

                    <TextView
                        android:id="@+id/txtMeetingId"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:fontFamily="sans-serif-medium"
                        android:textColor="@color/white"
                        android:textFontWeight="600"
                        android:textSize="16sp" />

                    <ImageButton
                        android:id="@+id/btnCopyContent"
                        android:layout_width="22dp"
                        android:layout_height="22sp"
                        android:layout_marginLeft="7dp"
                        android:layout_toRightOf="@+id/txtMeetingId"
                        android:backgroundTint="@color/black"
                        android:src="@drawable/ic_outline_content_copy_24" />

                </RelativeLayout>

            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="end"
                android:layout_marginEnd="10dp">

                <ImageButton
                    android:id="@+id/btnSwitchCameraMode"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:background="@color/black"
                    android:contentDescription="Switch Camera mode"
                    android:src="@drawable/ic_baseline_flip_camera_android_24" />

            </LinearLayout>

        </com.google.android.material.appbar.MaterialToolbar>

        <FrameLayout
            android:id="@+id/participants_frameLayout"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@color/black">

            <androidx.cardview.widget.CardView
                android:id="@+id/ParticipantCard"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginLeft="12dp"
                android:layout_marginTop="3dp"
                android:layout_marginRight="12dp"
                android:layout_marginBottom="3dp"
                android:backgroundTint="#2B3034"
                android:visibility="gone"
                app:cardCornerRadius="8dp"
                app:strokeColor="#2B3034">

                <ImageView
                    android:layout_width="150dp"
                    android:layout_height="150dp"
                    android:layout_gravity="center"
                    android:src="@drawable/ic_baseline_person_24" />

                <live.videosdk.rtc.android.VideoView
                    android:id="@+id/participantView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:visibility="gone" />

            </androidx.cardview.widget.CardView>

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </FrameLayout>

            <androidx.cardview.widget.CardView
                android:id="@+id/LocalCard"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginLeft="12dp"
                android:layout_marginTop="3dp"
                android:layout_marginRight="12dp"
                android:layout_marginBottom="3dp"
                android:backgroundTint="#1A1C22"
                app:cardCornerRadius="8dp"
                app:strokeColor="#1A1C22">

                <ImageView
                    android:id="@+id/localParticipant_img"
                    android:layout_width="150dp"
                    android:layout_height="150dp"
                    android:layout_gravity="center"
                    android:src="@drawable/ic_baseline_person_24" />

                <live.videosdk.rtc.android.VideoView
                    android:id="@+id/localView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:visibility="gone" />

            </androidx.cardview.widget.CardView>

        </FrameLayout>

        <!-- add bottombar here-->

    </LinearLayout>
    <com.google.android.material.bottomappbar.BottomAppBar
            android:id="@+id/bottomAppbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:animateLayoutChanges="true"
            android:backgroundTint="@color/black"
            android:gravity="center_horizontal"
            android:paddingVertical="5dp"
            tools:ignore="BottomAppBar">

            <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingStart="16dp"
                android:paddingEnd="16dp">

                <com.google.android.material.floatingactionbutton.FloatingActionButton
                    android:id="@+id/btnLeave"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:contentDescription="Leave Meeting"
                    android:src="@drawable/ic_end_call"
                    app:backgroundTint="#FF5D5D"
                    app:fabSize="normal"
                    app:tint="@color/white" />

                <com.google.android.material.floatingactionbutton.FloatingActionButton
                    android:id="@+id/btnMic"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="90dp"
                    android:layout_toEndOf="@+id/btnLeave"
                    android:contentDescription="Toggle Mic"
                    android:src="@drawable/ic_mic_off"
                    app:backgroundTint="@color/white"
                    app:borderWidth="1dp"
                    app:fabSize="normal" />

                <com.google.android.material.floatingactionbutton.FloatingActionButton
                    android:id="@+id/btnWebcam"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="90dp"
                    android:layout_toEndOf="@+id/btnMic"
                    android:backgroundTint="@color/white"
                    android:contentDescription="Toggle Camera"
                    android:src="@drawable/ic_video_camera_off"
                    app:backgroundTint="@color/white"
                    app:borderWidth="1dp"
                    app:fabSize="normal" />

            </RelativeLayout>

        </com.google.android.material.bottomappbar.BottomAppBar>

Copy the required icons from here and paste them into your project`res/drawable res/drawable folder.

STEP 5: Video Chat Android SDK Initializing the Meeting

Once we have the token and meeting ID from the JoinActivity, here's what we need to do:

  1. Start up VideoSDK: We need to get VideoSDK ready to work.

  2. Set up VideoSDK with the token: This means giving VideoSDK the special key we got earlier.

  3. Initialize the meeting with required params such as meetingId, participantName, micEnabled, webcamEnabled,participantId and map of CustomStreamTrack.

  4. Join the room with meeting.join() method.

class MeetingActivity : AppCompatActivity() {

    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 toolbar = findViewById<Toolbar>(R.id.material_toolbar)
        toolbar.title = ""
        setSupportActionBar(toolbar)

        //
        val token = intent.getStringExtra("token")
        val meetingId = intent.getStringExtra("meetingId")

        // set participant name
        val localParticipantName = "Alex"

        // Initialize VideoSDK
        VideoSDK.initialize(applicationContext)

        // pass the token generated from api server
        VideoSDK.config(token)

        // create a new meeting instance
        meeting = VideoSDK.initMeeting(
            this@MeetingActivity, meetingId, localParticipantName,
            micEnabled, webcamEnabled, null, null
        )

        // join the meeting
        meeting?.join()

        //
        val textMeetingId = findViewById<TextView>(R.id.txtMeetingId)
        textMeetingId.text = meetingId

     // copy meetingId to clipboard
        (findViewById<View>(R.id.btnCopyContent) as ImageButton).setOnClickListener {
            if (meetingId != null) {
                copyTextToClipboard(meetingId)
            }
        }
   }

      private fun copyTextToClipboard(text: String) {
        val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
        val clip = ClipData.newPlainText("Copied text", text)
        clipboard.setPrimaryClip(clip)
        Toast.makeText(this@MeetingActivity, "Copied to clipboard!", Toast.LENGTH_SHORT).show()
    }

}

STEP 6: Video Chat Android SDK Handle Local Participant Media

We need to implement clicks for the following Views,

  • Mic Button

  • Webcam Button

  • Switch Camera Button

  • Leave Button

Add the following implementation,

class MeetingActivity : AppCompatActivity() {

    private var btnWebcam: FloatingActionButton? = null
    private var btnMic: FloatingActionButton? = null
    private var btnLeave: FloatingActionButton? = null
    private var btnSwitchCameraMode: ImageButton? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_meeting)

    //
    btnLeave = findViewById(R.id.btnLeave)
    btnSwitchCameraMode = findViewById(R.id.btnSwitchCameraMode)
    btnMic = findViewById(R.id.btnMic)
    btnWebcam = findViewById(R.id.btnWebcam)

    //...

    // actions
    setActionListeners()
  }

  private fun setActionListeners() {
        // Toggle mic
        btnMic!!.setOnClickListener { toggleMic() }

        // Toggle webcam
        btnWebcam!!.setOnClickListener { toggleWebCam() }

        // Leave meeting
        btnLeave!!.setOnClickListener { 
            // this will make the local participant leave the meeting
               meeting!!.leave()
        }

        // Switch camera
        btnSwitchCameraMode!!.setOnClickListener { 
        //a participant can change stream from front/rear camera during the meeting.
            meeting!!.changeWebcam() 
        }

   }
}
private fun toggleMic() {
        if (micEnabled) {
            // this will mute the local participant's mic
            meeting!!.muteMic()
        } else {
             // this will unmute the local participant's mic
            meeting!!.unmuteMic()
        }
        micEnabled = !micEnabled  
        // change mic icon according to micEnable status
        toggleMicIcon()
    }

    @SuppressLint("ResourceType")
    private fun toggleMicIcon() {
        if (micEnabled) {
            btnMic!!.setImageResource(R.drawable.ic_mic_on)
            btnMic!!.setColorFilter(Color.WHITE)
            var buttonDrawable = btnMic!!.background
            buttonDrawable = DrawableCompat.wrap(buttonDrawable!!)
            if (buttonDrawable != null) DrawableCompat.setTint(buttonDrawable, Color.TRANSPARENT)
            btnMic!!.background = buttonDrawable
        } else {
            btnMic!!.setImageResource(R.drawable.ic_mic_off)
            btnMic!!.setColorFilter(Color.BLACK)
            var buttonDrawable = btnMic!!.background
            buttonDrawable = DrawableCompat.wrap(buttonDrawable!!)
            if (buttonDrawable != null) DrawableCompat.setTint(buttonDrawable, Color.WHITE)
            btnMic!!.background = buttonDrawable
        }
    }
private fun toggleWebCam() {
        if (webcamEnabled) {
        // this will disable the local participant webcam
            meeting!!.disableWebcam()
        } else {
        // this will enable the local participant webcam
            meeting!!.enableWebcam()
        }
        webcamEnabled = !webcamEnabled
        // change webCam icon according to webcamEnabled status
        toggleWebcamIcon()
    }

    @SuppressLint("ResourceType")
    private fun toggleWebcamIcon() {
        if (webcamEnabled) {
            btnWebcam!!.setImageResource(R.drawable.ic_video_camera)
            btnWebcam!!.setColorFilter(Color.WHITE)
            var buttonDrawable = btnWebcam!!.background
            buttonDrawable = DrawableCompat.wrap(buttonDrawable!!)
            if (buttonDrawable != null) DrawableCompat.setTint(buttonDrawable, Color.TRANSPARENT)
            btnWebcam!!.background = buttonDrawable
        } else {
            btnWebcam!!.setImageResource(R.drawable.ic_video_camera_off)
            btnWebcam!!.setColorFilter(Color.BLACK)
            var buttonDrawable = btnWebcam!!.background
            buttonDrawable = DrawableCompat.wrap(buttonDrawable!!)
            if (buttonDrawable != null) DrawableCompat.setTint(buttonDrawable, Color.WHITE)
            btnWebcam!!.background = buttonDrawable
        }
    }

STEP 7: Setting up Local participant view

To set up participant view, we have to implement all the methods of ParticipantEventListener Abstract class and add the listener to Participant class using the addEventListener() method of Participant Class. ParticipantEventListener class has two methods.

  1. onStreamEnabled - Whenever any participant enables mic/webcam in the meeting, onStreamEnabled the event will trigger and return Stream.

  2. onStreamDisabled - Whenever any participant disables mic/webcam in the meeting, onStreamDisabled the event will trigger and return Stream.

class MeetingActivity : AppCompatActivity() {

    private var localView: VideoView? = null
    private var participantView: VideoView? = null

    private var localCard: CardView? = null
    private var participantCard: CardView? = null
    private var localParticipantImg: ImageView? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_meeting)

    //... 

    localCard = findViewById(R.id.LocalCard)
    participantCard = findViewById(R.id.ParticipantCard)
    localView = findViewById(R.id.localView)
    participantView = findViewById(R.id.participantView)                  localParticipantImg = findViewById(R.id.localParticipant_img)

    //...

    // setup local participant view
    setLocalListeners()
  }

  private fun setLocalListeners() {
        meeting!!.localParticipant
            .addEventListener(object : ParticipantEventListener() {
                override fun onStreamEnabled(stream: Stream) {
                    if (stream.kind.equals("video", ignoreCase = true)) {
                        val track = stream.track as VideoTrack
                        localView!!.visibility = View.VISIBLE
                        localView!!.addTrack(track)
                        localView!!.setZOrderMediaOverlay(true)
                        (localCard as View?)!!.bringToFront()
                    }
                }

                override fun onStreamDisabled(stream: Stream) {
                    if (stream.kind.equals("video", ignoreCase = true)) {
                        localView!!.removeTrack()
                        localView!!.visibility = View.GONE
                    }
                }
            })
    }
}

STEP 8: Setting up Remote participant view

private val participantEventListener: ParticipantEventListener =
        object : ParticipantEventListener() {
        // trigger when participant enabled mic/webcam
            override fun onStreamEnabled(stream: Stream) {
                if (stream.kind.equals("video", ignoreCase = true)) {
                    localView!!.setZOrderMediaOverlay(true)
                    (localCard as View?)!!.bringToFront()
                    val track = stream.track as VideoTrack
                    participantView!!.visibility = View.VISIBLE
                    participantView!!.addTrack(track)
                }
            }

    // trigger when participant disabled mic/webcam
            override fun onStreamDisabled(stream: Stream) {
                if (stream.kind.equals("video", ignoreCase = true)) {
                    participantView!!.removeTrack()
                    participantView!!.visibility = View.GONE
                }
            }
        }

STEP 9: Handle meeting events & manage participant's view

Add MeetingEventListener for listening events such as Meeting Join/Left and Participant Join/Left.

class MeetingActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_meeting)
    //...

    // handle meeting events
    meeting!!.addEventListener(meetingEventListener)
  }

  private val meetingEventListener: MeetingEventListener = object : MeetingEventListener() {
        override fun onMeetingJoined() {
        // change mic,webCam icon after meeting successfully joined
            toggleMicIcon()
            toggleWebcamIcon()
        }

        override fun onMeetingLeft() {
            if (!isDestroyed) {
                val intent = Intent(this@MeetingActivity, JoinActivity::class.java)
                startActivity(intent)
                finish()
            }
        }

        override fun onParticipantJoined(participant: Participant) {
        // Display local participant as miniView when other participant joined
            changeLocalParticipantView(true)
            Toast.makeText(
                this@MeetingActivity, participant.displayName + " joined",
                Toast.LENGTH_SHORT
            ).show()
            participant.addEventListener(participantEventListener)
        }

        override fun onParticipantLeft(participant: Participant) {
        // Display local participant as largeView when other participant left
            changeLocalParticipantView(false)
            Toast.makeText(
                this@MeetingActivity, participant.displayName + " left",
                Toast.LENGTH_SHORT
            ).show()
        }
    }
}
  • changeLocalParticipantView(isMiniView: Boolean) function handles whether the video of a local participant is displayed as a MiniView or a LargeView.

  • If the meeting has only one participant (local participant), then the local participant is displayed as LargeView.

  • When another participant (other than the local participant) joins,changeLocalParticipantView(true) is called. As a result, the local participant is shown as MiniView, while the other participant is shown as LargeView.

private fun changeLocalParticipantView(isMiniView: Boolean) {
        if (isMiniView) {
            // show localCard as miniView
            localCard!!.layoutParams =
                FrameLayout.LayoutParams(300, 430, Gravity.RIGHT or Gravity.BOTTOM)
            val cardViewMarginParams = localCard!!.layoutParams as MarginLayoutParams
            cardViewMarginParams.setMargins(30, 0, 60, 40)
            localCard!!.requestLayout()
            // set height-width of localParticipant_img
            localParticipantImg!!.layoutParams = FrameLayout.LayoutParams(150, 150, Gravity.CENTER)
            (localCard as View?)!!.bringToFront()
            participantCard!!.visibility = View.VISIBLE
        } else {
            // show localCard as largeView
            localCard!!.layoutParams =
                FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
            val cardViewMarginParams = localCard!!.layoutParams as MarginLayoutParams
            cardViewMarginParams.setMargins(30, 5, 30, 30)
            localCard!!.requestLayout()
            // set height-width of localParticipant_img
            localParticipantImg!!.layoutParams = FrameLayout.LayoutParams(400, 400, Gravity.CENTER)
            participantCard!!.visibility = View.GONE
        }
    }

STEP 10: Destroying everything

We need to release resources when the app is closed and is no longer being used. Override the onDestroy with the following code.

override fun onDestroy() {
        if (meeting != null) {
            meeting!!.removeAllListeners()
            meeting!!.localParticipant.removeAllListeners()
            meeting!!.leave()
            meeting = null
        }
        if (participantView != null) {
            participantView!!.visibility = View.GONE
            participantView!!.releaseSurfaceViewRenderer()
        }
        if (localView != null) {
            localView!!.visibility = View.GONE
            localView!!.releaseSurfaceViewRenderer()
        }
        super.onDestroy()
    }

This is how the meeting screen will look with two people participating.

java.lang.IllegalStateException: This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead.If you face this error at Runtime include these lines intheme.xmlfile.<item name="windowActionBar">false</item>``<item name="windowNoTitle">true</item

1-to-1 Video Chat App Demo

Tadaa!! Our app is ready. Easy, right?

I hope you had fun learning and working together on making a 1-to-1 video chat Android app using the VideoSDK.

Now, install the app on two different devices and make sure they're connected to the internet. You should see it working just like in the video below.

This app only supports 2 participants, it does not manage more than 2 participants. If you want to handle more than 2 participants then checkout our Group chat example here.

In this blog, we learned about VideoSDK, how to get an access token from its Dashboard, and how to make a 1-to-1 video chat app using it. Now, you can explore advanced features like screen-sharing, chat, and more by checking out our documentation.

To see the full implementation of the app, check out the GitHub repository: https://github.com/videosdk-live/videosdk-rtc-android-kotlin-sdk-example/tree/one-to-one-demo.

If you have any questions or comments, I invite you to Join the Video SDK Developer Discord community.

More Resources