One of the biggest challenges facing any developer is building applications that can scale. Making a video-conferencing application that can scale is especially challenging because video data is pretty heavy. And as the number of participants rises, it becomes increasingly difficult to make sure that your application can keep up.
In this tutorial, I use Agora to build an application that can scale to up to 17 users by optimizing the bandwidth usage of the video streams.
TLDR: You can get the source code for the demo on GitHub.
implementation 'io.agora.rtc:full-sdk:3.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
All done! It’s time to start building the application.
Now we build the application. I’ll show you how to build the UI for the local video and then put all the remote views in a grid layout by using a RecyclerView.
The structure for the Kotlin files is pretty simple:
.
├── MainActivity.kt
└── ui
└── RemoteViewAdapter.kt
I’ll discuss the RemoteViewAdapter more later on.
We’ll first do some basic setup of the main activity so that you can see how this activity is structured:
This is a basic skeleton of the project. The App ID should be familiar if you followed the guide I have linked to above. The Channel can be any string. The important bit is that if any client wants to talk to another client, they have to be on the same channel. For the token, you can get a temporary token by following this guide. For production use cases, you need to set up a token server.
The workflow of the application looks like this:
First, we write the logic for checking if the required permissions are already granted:
We then write the logic for the method that is called when we want to request the permissions:
Finally, we write the logic for the callback that is executed when the user has interacted with the permission overlay:
If all the required permissions are granted, we call the initializeApplication method. However, if they are not granted, we call finish to destroy the activity.
Before we jump into implementing the final method of the application, let’s finish designing the UI. For the sake of simplicity, I’ve kept the UI minimal.
The FrameLayout here is the container into which the SurfaceView for the local video is injected. SurfaceView is where Agora usually renders its streams on Android. The RecyclerView will be used to display a grid of videos of the remote users. The Guideline is used here to divide the percentage of screen real estate used by each component.
It’s time to start implementing the initializeApplication method. Here we have the core application logic:
We also need to make sure to add the following property to our activity:
private var mRtcEngine: RtcEngine? = null
So, the first thing we do is create an RtcEngine object by giving it our App ID (which we discussed above and an event handler (which we will discuss soon). We do some basic configuration such as enabling the video and setting parameters like resolution, FPS, bitrate, and orientation. We set up our local video and then join the channel. And we enable dual-stream.
Dual-stream is an Agora feature that allows a client to publish two streams at the same time. One stream is for a higher resolution and bitrate, and the other stream is for a lower resolution and bitrate. With this dual-stream setup, when a remote client subscribes to your stream, they can switch to a lower stream based on their bandwidth requirements.
Now let’s set up the remote videos.
We will now make a grid layout of videos. We use RecyclerView to do this because we can’t load all the remote users into memory at the same time. Using RecyclerView allows us to lazy-load the required streams.
First, we create our adapter:
We are doing three main things:
Now that we have created our RecyclerView adapter, we can set up the RecyclerView in our initializeApplication method:
We use the GridLayoutManager to get our RecyclerView to use a grid layout. We set the span count to 2 so that we have a total of two rows in the grid. We set the orientation to horizontal.
Now we create an instance of the adapter we just wrote. Make sure to add the following properties to the activity:
private lateinit var remoteViewAdapter: RemoteViewAdapter
private var uidList = ArrayList()
We write the contents of the callbacks in the IRtcEngineEventHandler:
https://gist.github.com/samyak-jain/4ccc3da5cf07839978daa7098ec428f4
In the onUserJoined callback, we are doing the following:
In the onUserOffline callback, we are doing the following:
We need to add the following properties to our activity:
private val handler = Handler(Looper.getMainLooper())
private val lock = ReentrantLock()
Finally, it is time to write the onDestroy method. We clean up all the resources relevant to Agora here:
There you go! You should now have everything necessary to run the application.
Before you build the app, make sure you did the following:
After you build the app, you should see something like this:
Congratulations! You have learned how to make an application that has all the necessary optimizations to have scalable video-conferencing capabilities.
You can get the entire codebase from this repository.
If you want to test the app with a web client, you can use the following hosted demo, which has dual-stream enabled: demo
For more information about Agora applications, take a look at the Agora Video Call Quickstart Guide and Agora API Reference.
To learn more about dual-stream, you can take a look at our guide on stream fallback.
I also invite you to join the Agora Developer Slack community.