Dolby.io provides Communication APIs that allow developers to create their own types of conference calling applications in a self-serve way. Here at Dolby.io, we provide an Android SDK that makes it as simple as possible to set up in your own application. In this blog post, you’ll learn how to create an audio-only conference call using Dolby.io and Android Studio.
Pre-requisites
- Have latest version of Android Studio installed
- Physical Android device or emulator
- A Dolby.io account
What the App will Look Like
Here’s what the final app looks like. View the GitHub repo here!
Setup
Creating a Dolby.io Account
Before coding, we’re going to have to create a Dolby.io account. Go to Dolby.io and click on Sign Up to get started. Once you’re registered, click on “Add a New App” in the dashboard page.
Feel free to name the application anything you’d like! For the sake of this post, we’ll name it Android Audio Conference Call.
Once we’ve got that created, you’ll see a new application under the Applications tab on the dashboard. Now whenever we need the Communications API keys, we can access them by either clicking on our application under the Applications tab or on the left side-bar.
Setting Up Android Studio
Now that we’ve created an account and application, let’s start coding!
Open up Android Studio and create a new empty project. Go ahead and fill out the required fields and make sure to select Kotlin as the app’s language. Once you’re done filling that out, click finish.
Gradle Setup
After your app finishes loading, open up the build.gradle (app) file. Here we’ll include the VoxeetSDK depdendency and enable viewBinding.
You can find the latest version of the Android SDK here!
android {
...
buildFeatures {
viewBinding = true
}
}
dependencies {
...
implementation ("com.voxeet.sdk:sdk:${version}") {
transitive = true
}
}
Then go into your settings.gradle
file and add the Voxeet maven url under the repositories section.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven { url "https://android-sdk.voxeet.com/release" }
}
}
After adding the dependency, sync the project.
Adding Permissions to AndroidManifest.xml
Next go to the AndroidManifest.xml
file and add these permissions between the manifest and application tag to enable internet and record audio in your app.
<manifest xmlns_android="https://schemas.android.com/apk/res/android"
package="com.dolbyio.android_audio_conference_call">
<uses-permission android_name="android.permission.INTERNET" />
<uses-permission android_name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android_name="android.permission.RECORD_AUDIO" />
<application
...
Creating an Emulator (Optional)
Open the Device Manager in the top right of Android Studio.
If you don’t have an existing emulator, click on Device Manager > Create Device > Emulator of choice, select a system image and click on finish.
For this post, we created a Pixel 4 XL emulator using API 30.
Enabling Android Emulator Microphone (Optional)
Run the emulator and click on settings. In the settings window, click on the Microphone option and enable all fields.
Implementing the Conference Call
Request Microphone Permissions
Although we’ve enabled the emulator microphone, we still need to add the necessary microphone permissions to our app.
In your MainActivity.kt
file, we’ll be setting up our microphone permissions!
const val REQUEST_CODE = 200
class MainActivity : AppCompatActivity() {
...
private var permissions = arrayOf(Manifest.permission.RECORD_AUDIO)
private var permissionGranted = false
override fun onCreate(savedInstanceState: Bundle?) {
...
val permissionGranted = ActivityCompat.checkSelfPermission(this@MainActivity, permissions[0]) == PackageManager.PERMISSION_GRANTED
if (!permissionGranted) {
ActivityCompat.requestPermissions(this@MainActivity, permissions, REQUEST_CODE)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE) {
permissionGranted = (grantResults[0] == PackageManager.PERMISSION_GRANTED)
}
}
}
Notice that we also implemented the onRequestPermissionResult function. After the user has allowed mic permissions, this updates our permissions if any changes have been made.
With this, our emulator we’ll be able to hear audio!
Initializing viewBinding and the SDK
Now that we’ve set up our Dolby.io dashboard and Android Studio, let’s move onto viewBinding.
Inside the MainActivity.kt
, we’ll initialize a viewBinding variable which, will let us reference the different views within our app.
Remember to grab your consumer key and secret from the Dolby.io dashboard. We’ll initialize the Communications SDK after we initialize our binding variable.
Feel free to look at this guide if you’d like to learn more about initializing the SDK.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
...
// initialize binding
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// initialize SDK
VoxeetSDK.initialize("consumerKey", "consumerSecret")
}
}
Creating the Layout for the App
Next let’s create the layout of the MainActivity.kt
XML file.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns_android="https://schemas.android.com/apk/res/android"
xmlns_app="https://schemas.android.com/apk/res-auto"
xmlns_tools="https://schemas.android.com/tools"
android_layout_width="match_parent"
android_layout_height="match_parent"
tools_context=".MainActivity">
<EditText
android_id="@+id/etPodcastName"
android_layout_width="match_parent"
android_layout_height="wrap_content"
android_layout_marginLeft="12dp"
android_layout_marginRight="12dp"
android_ems="10"
android_hint="Enter podcast name here..."
android_inputType="textPersonName"
app_layout_constraintBottom_toBottomOf="parent"
app_layout_constraintLeft_toLeftOf="parent"
app_layout_constraintRight_toRightOf="parent"
app_layout_constraintTop_toTopOf="parent" />
<Button
android_id="@+id/btnCreate"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_text="Create"
app_layout_constraintBottom_toBottomOf="parent"
app_layout_constraintEnd_toEndOf="parent"
app_layout_constraintStart_toStartOf="parent"
app_layout_constraintTop_toBottomOf="@+id/etPodcastName"
app_layout_constraintVertical_bias="0.03" />
<Button
android_id="@+id/btnLeaveCall"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_marginBottom="100dp"
android_enabled="false"
android_text="Leave Call"
android_visibility="invisible"
app_layout_constraintBottom_toBottomOf="parent"
app_layout_constraintEnd_toEndOf="parent"
app_layout_constraintHorizontal_bias="0.498"
app_layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Implementing the Conference Call Helper Functions
Once we’ve set up our XML, let’s implement the functions that will help us create a conference call.
Add these function definitions in your MainActivity.kt
file.
class MainActivity : AppCompatActivity() {
...
// open a session for the participant creating a call
private fun openSession(
name: String,
externalID: String = "",
avatarURL: String = ""
): Promise<Boolean> {...}
// create a call Promise object
private fun createConferencePromise(
conferenceName: String
): PromiseInOut<Conference, Conference> {...}
// returns a Promise object allowing us to join the call
private fun joinCall(conferencePromise: Promise<Conference>): PromiseInOut<Conference, Conference> {...}
// closes the participant's session
// called when participant leaves the call
private fun closeSession() {...}
// logs the error to logcat if VoxeetSDK create or join promise fails
private fun error(): ErrorPromise? {...}
}
Implementing openSession()
function
The openSession()
function will allow us to register the participants’ info in the Voxeet service. Before even creating a conference, a user must always open a session.
private fun openSession(
name: String,
externalID: String = "",
avatarURL: String = ""
): Promise<Boolean> {
// opens a new session for participant
return VoxeetSDK.session().open(ParticipantInfo(name, externalID, avatarURL))
}
Implementing createConferencePromise()
function
Once we have an open session, we can now create a conference call using the VoxeetSDK.
private fun createConferencePromise(
conferenceName: String
): PromiseInOut<Conference, Conference> {
// set paramaters for our conference call
val paramsHolder = ParamsHolder()
paramsHolder.setDolbyVoice(isDolbyVoice)
paramsHolder.setVideoCodec("VP8")
paramsHolder.setAudioOnly(true)
// add parameters to conference builder
val conferenceCreateOptions = ConferenceCreateOptions.Builder()
.setConferenceAlias(conferenceName)
.setParamsHolder(paramsHolder)
.build()
val createPromise = VoxeetSDK.conference().create(conferenceCreateOptions)
return joinCall(createPromise)
}
So what we’ve done so far is set up our parameters and create a promise for the conference call. The next step is to handle that promise object in the joinCall()
function we’ll be implementing!
Implementing joinCall()
function
The joinCall()
function will allow us to now hop into the audio-only conference we create when we call the createConferencePromise()
function handling the promise returned.
private fun joinCall(conferencePromise: Promise<Conference>): PromiseInOut<Conference, Conference> {
val joinPromise = conferencePromise.then(ThenPromise<Conference, Conference> { conference ->
val conferenceJoinOptions: ConferenceJoinOptions = ConferenceJoinOptions.Builder(conference).build()
return@ThenPromise VoxeetSDK.conference().join(conferenceJoinOptions)
})
return joinPromise
}
Implementing closeCall()
function
private fun closeSession() {
VoxeetSDK.session().close()
.then { result: Boolean?, solver: Solver<Any?>? ->
Toast.makeText(this@MainActivity, "closed session", Toast.LENGTH_SHORT).show()
updateViews(true)
}.error{
Log.e("MainActivity", "Error with closing session")
}
}
Implementing error()
function
fun error(): ErrorPromise {
return ErrorPromise { error: Throwable ->
Log.e("MainActivity", error.printStackTrace().toString())
}
}
At this point, we have set up the required functions that will handle creating a conference call!
Updating the views
Next we’ll create a few helper functions to update the views and handle any onClick events in our app.
private fun initializeBtnCreateCall() {...}
private fun initializeBtnEndCall() {...}
private fun updateViews(enabled: Boolean) {...}
We can now use the binding variable we initialized in the onCreate() function to listen for any button clicks.
Implementing initializeBtnCreateCall()
function
private fun initializeBtnCreateCall() {
binding.btnCreate.setOnClickListener {
val podcastName = binding.etPodcastName.text.toString()
if (podcastName.isNotEmpty()) {
val session = openSession("Person 1")
session.then { result: Boolean?, solver: Solver<Any?>? ->
Toast.makeText(this@MainActivity, "Opened session", Toast.LENGTH_SHORT).show()
createConferencePromise(
podcastName
).then<Any>(ThenVoid { conference: Conference? ->
Toast.makeText(this@MainActivity, "${conference?.alias} conference started...", Toast.LENGTH_SHORT ).show()
updateViews(false)
})
.error {
Log.e("MainActivity", "error creating a conference")
}
}.error {
Log.e("MainActivity", "error opening a session")
}
} else {
Toast.makeText(this@MainActivity, "Podcast name can't be empty", Toast.LENGTH_LONG).show()
}
}
}
Implementing initializeBtnEndCall()
function
private fun initializeBtnEndCall() {
binding.btnLeaveCall.setOnClickListener {
VoxeetSDK.conference().leave()
.then { result: Boolean?, solver: Solver<Any?>? ->
closeSession()
}.error(error())
}
}
Implementing updateViews()
function
private fun updateViews(enabled: Boolean) {
binding.btnCreate.isEnabled = enabled
binding.etPodcastName.isEnabled = enabled
binding.btnLeaveCall.isEnabled = !enabled
if (!enabled) {
binding.btnLeaveCall.visibility = View.VISIBLE
} else {
binding.btnLeaveCall.visibility = View.INVISIBLE
}
}
Putting it all together
Now we have all the necessary parts to create our first conference call using the Communications APIs!
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val permissionGranted = ActivityCompat.checkSelfPermission(this@MainActivity, permissions[0]) == PackageManager.PERMISSION_GRANTED
if (!permissionGranted) {
ActivityCompat.requestPermissions(this@MainActivity, permissions, REQUEST_CODE)
}
VoxeetSDK.initialize("consumerKey", "consumerSecret")
initializeBtnCreateCall()
initializeBtnEndCall()
}
}
Run the App!
Let’s run the app and try creating a call!
When you check the Dolby.io dashboard, you’ll see a conference has been created. To get here, click on the application we created earlier and go to the monitor section under Communications APIs. Here you’ll be able to see the all the conference details including if the call is live, name, conference ID, and more!
Congrats! You’ve officially made your first audio conference call using Dolby.io!
Want to learn more? Visit our Android Communications SDK Docs here!