Developer

Using Kotlin to Create Your First Audio Voice Call on Android with Dolby.io

Intro image for blog post
SUMMARY

Learn how to use Kotlin to build an Android application that supports Voice Calls.


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

What the App will Look Like

Here’s what the final app looks like. View the GitHub repo here!

Create conference call gif

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.

Dolby.io dashboard

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.

Android Studio create new project screen

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.

How to create an emulator

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.

Enabling Android emulator microphone

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!

Create conference call gif

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!

Conference call made in Dolby.io dashboard

Congrats! You’ve officially made your first audio conference call using Dolby.io!

Link to GitHub repo

Want to learn more? Visit our Android Communications SDK Docs here!

Leave a Comment

Denize Ignacio

Developer Relations

Get Started

Drive real-time interactions and engagement with sub-second latency

We are more than just a streaming solutions provider; we are a technology partner helping you build a streaming ecosystem that meets your goals. Get started for free and as you grow, we offer aggressive volume discounts protecting your margins.

Developer Resources

Explore learning paths and helpful resources as you begin development with Dolby.io.

Copy link
Powered by Social Snap