Create a Basic Audio Conference Application for Android

Prerequisites

Make sure that you have:

  • A Dolby.io account
  • A working webcam and a microphone
  • Android Studio

For reference, see the GitHub sample repository.

Procedure

Create your project

Open Android Studio and create a new Java project. Select the “Empty activity” template. In this example, only default options are included.

1. Add the SDK dependency.

Include the Dolby.io Communications SDK for Android as a dependency in the app/build.gradle file, in the dependencies section:

implementation "io.dolby:sdk:3.10.1"

2. Use viewBinding to facilitate the integration of the SDK and manage injection, in the app/build.gradle file, in the android section:

buildFeatures {
    viewBinding = true
}

3. Make sure to not use Android SDK version older than 21. The SDK is only compatible with android 21+. This change is for the android section in the file app/build.gradle.

compileSdkVersion 33
buildToolsVersion "31.0.0"
defaultConfig {
    minSdkVersion 21
    targetSdkVersion 31
}

4. Make sure that a compilation toolchain uses the Java 11. This change is for the android section in the file app/build.gradle.

compileOptions {
    sourceCompatibility JavaVersion.VERSION_11
    targetCompatibility JavaVersion.VERSION_11
}

5. Android Studio displays a banner above the text editor that informs that the Gradle files have changed. Click the “Sync Now” link.

6. To update the layout, edit the activity_main.xml file from the app/src/main/res/layout/ folder. In Android Studio, this file is found in app/res/layout. Modify its content as in the following example:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    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:orientation="vertical"
    tools:context=".MainActivity">

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

        <!-- Step 1. Put the layout changes for the open/close session step here -->

        <!-- Step 2. Put the layout changes for the join conference step here -->

        <!-- Step 3. Put the layout changes for the video step here -->

        <!-- Step 4. Put the layout changes for the view participants step here -->

        <!-- Step 5. Put the layout changes for the screen sharing step here -->

        <!-- Step 6. Put the layout changes for the recording step here -->

    </LinearLayout>
</ScrollView>

7. Edit the MainActivity.java file and add the following imports:

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import com.example.myapplication.databinding.ActivityMainBinding;
import com.voxeet.VoxeetSDK;
import com.voxeet.android.media.MediaStream;
import com.voxeet.android.media.stream.MediaStreamType;
import com.voxeet.promise.solve.ErrorPromise;
import com.voxeet.promise.solve.ThenPromise;
import com.voxeet.sdk.events.promises.ServerErrorException;
import com.voxeet.sdk.events.v2.ParticipantAddedEvent;
import com.voxeet.sdk.events.v2.ParticipantUpdatedEvent;
import com.voxeet.sdk.events.v2.StreamAddedEvent;
import com.voxeet.sdk.events.v2.StreamRemovedEvent;
import com.voxeet.sdk.events.v2.StreamUpdatedEvent;
import com.voxeet.sdk.json.ParticipantInfo;
import com.voxeet.sdk.json.RecordingStatusUpdatedEvent;
import com.voxeet.sdk.json.internal.ParamsHolder;
import com.voxeet.sdk.models.Conference;
import com.voxeet.sdk.models.Participant;
import com.voxeet.sdk.services.builders.ConferenceCreateOptions;
import com.voxeet.sdk.services.builders.ConferenceJoinOptions;
import com.voxeet.sdk.services.conference.information.ConferenceInformation;
import com.voxeet.sdk.services.screenshare.RequestScreenSharePermissionEvent;
import com.voxeet.sdk.services.screenshare.ScreenCapturerService;
import com.voxeet.sdk.views.VideoView;

import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

8. To prepare events and logic integration, create a list of views responsible for keeping the views enabled in the following modes:

  • when there are no connected sessions
  • when a session is connected
  • when a conference is connected
  • when there are no connected conferences
  • when your video is not started
  • when your video is started
  • when you are sharing your screen
  • when you are not sharing your screen

Edit the MainActivity class in MainActivity.java and add the following values:

public class MainActivity extends AppCompatActivity {

    protected List<View> views = new ArrayList<>();
    protected List<View> buttonsNotLoggedIn = new ArrayList<>();
    protected List<View> buttonsInConference = new ArrayList<>();
    protected List<View> buttonsNotInConference = new ArrayList<>();
    protected List<View> buttonsInOwnVideo = new ArrayList<>();
    protected List<View> buttonsNotInOwnVideo = new ArrayList<>();
    protected List<View> buttonsInOwnScreenShare = new ArrayList<>();
    protected List<View> buttonsNotInOwnScreenShare = new ArrayList<>();

    ...
}

9. To simplify creating conferences, add the following code to the MainActivity class. The onCreate method created with the project is replaced.

  • Overridden onResume method to update views.
  • A method for views updates and management.
  • The default error management method.
  • Two methods for managing contextual lists of views.
public class MainActivity extends AppCompatActivity {
    ...
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);

        // All the logic of the onCreate will be put after this comment
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Here will be put the permission check

        // We update the various views to enable or disable the ones we want to
        updateViews();
    }

    private void updateViews() {
        // This method will be updated step by step
    }


    private ErrorPromise error() {
        return error -> {
            Toast.makeText(MainActivity.this, "ERROR...", Toast.LENGTH_SHORT).show();
            error.printStackTrace();
            updateViews();
        };
    }

    private void setEnabled(List<View> views, boolean enabled) {
        for (View view : views) view.setEnabled(enabled);
    }

    private MainActivity add(List<View> list, int id) {
        list.add(findViewById(id));
        return this;
    }

}

Initialize the SDK with your Dolby.io credentials

Initialize the SDK using the secure authentication method that uses a token in the application. For more information, see the Initializing the SDK document. For the purpose of this example, we are using a client access token generated from the Dolby.io dashboard. It is recommended that you create a new Sample application for this tutorial.

1. Add the following code to the onCreate method from the MainActivity.java file. Replace the TestClientAccessToken string with the token generated from the Dolby.io dashboard.

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Generate a client access token from the Dolby.io dashboard and insert into accessToken variable
    String accessToken = "<ClientAccessToken>";
    VoxeetSDK.initialize(accessToken, (isExpired, tokenCallback) -> {
        tokenCallback.ok(accessToken);
    });
}

2. Add the following lines at the end of onResume:

@Override
protected void onResume() {
    super.onResume();

    ...

    // Register the current activity in the SDK
    VoxeetSDK.instance().register(this);
}

3. Unregister from the SDK when the MainActivity is in the background by adding onPause.

@Override
protected void onPause() {
    // Register the current activity in the SDK
    VoxeetSDK.instance().unregister(this);

    super.onPause();
}

Open and close a session

To allow creating and joining conferences, log in with a user name. In this tutorial, random user names are assigned.

1. To modify the layout, edit the main_activity.xml file, adding the following content for Step 1:

<LinearLayout ...>

    <!-- Step 1. Put the layout changes for the open/close session step here -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="user session" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/user_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="horizontal">

            <Button
                android:id="@+id/login"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="log in" />

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

    <!-- Step 2. ... -->

</LinearLayout>

2. Modify the interface linking in the MainActivity class in MainActivity.java:

  • New methods for MainActivity:
public void onLogin() {

}

public void onLogout() {

}

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Add onClickListeners for onLogin() and onLogout()
    binding.login.setOnClickListener(_view -> onLogin());
    binding.logout.setOnClickListener(_view -> onLogout());
  • Add the following code to the onCreate method:
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Adding the user_name, login and logout views related to the open/close and conference flow
    add(views, R.id.login);
    add(views, R.id.logout);

    add(buttonsNotLoggedIn, R.id.login);
    add(buttonsNotLoggedIn, R.id.user_name);

    add(buttonsInConference, R.id.logout);

    add(buttonsNotInConference, R.id.logout);

    // Set a random user name
    String[] avengersNames = {
        "Thor",
        "Cap",
        "Tony Stark",
        "Black Panther",
        "Black Widow",
        "Hulk",
        "Spider-Man",
    };
    Random r = new Random();
    binding.userName.setText(avengersNames[r.nextInt(avengersNames.length)]);
}

3. Add the following logic to the application:

  • Use the following implementation for onLogin:
public void onLogin() {
    VoxeetSDK.session().open(new ParticipantInfo(binding.userName.getText().toString(), "", ""))
            .then((result, solver) -> {
                Toast.makeText(MainActivity.this, "log in successful", Toast.LENGTH_SHORT).show();
                updateViews();
            })
            .error(error());
}
  • Use the following implementation for onLogout:
public void onLogout() {
    VoxeetSDK.session().close()
            .then((result, solver) -> {
                Toast.makeText(MainActivity.this, "logout done", Toast.LENGTH_SHORT).show();
                updateViews();
            })
            .error(error());
}
  • Use the following implementation for updateViews:
private void updateViews() {
    // Disable every view
    setEnabled(views, false);

    // If the user is not connected, we will only enabled the not logged in buttons
    if (!VoxeetSDK.session().isOpen()) {
        setEnabled(buttonsNotLoggedIn, true);
        return;
    }
}

Add a joining option

When event handlers are set, implement the join and leave conference function in the client.

1. To modify the layout, edit the main_activity.xml file with the following content for Step 2:

<LinearLayout ...>
    ...

    <!-- Step 2. Put the layout changes for the join conference step here -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="conference name :" />

    <EditText
        android:id="@+id/conference_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/join"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="start" />

        <!-- Step 2.2. The layout will be upgraded in the leave conference step -->
    </LinearLayout>

    <!-- Step 3. ... -->
</LinearLayout>

2. Modify the interface linking in the MainActivity class in MainActivity.java:

  • New method for MainActivity:
public void onJoin() {

}

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Add onClickListener for onJoin()     
    binding.join.setOnClickListener(_view -> onJoin());
  • Add the following code to the onCreate method:
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Add the join button and enable it only when not in a conference
    add(views, R.id.join);
    add(buttonsNotInConference, R.id.join);

    // Set a default conference name
    binding.conferenceName.setText("Avengers meeting");
}

3. Add the following logic to the application:

  • Configure permission management in MainActivity onResume. Simplify the permission flow to ask for microphone and camera permissions when the application resumes.
@Override
protected void onResume() {
    ...

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
            ||
            ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}, 0x20);
    }
}
  • Bind the join flow implementation in the onJoin method created previously. The method creates and joins the conference.
public void onJoin() {
    ParamsHolder paramsHolder = new ParamsHolder();
    paramsHolder.setDolbyVoice(true);

    ConferenceCreateOptions conferenceCreateOptions = new ConferenceCreateOptions.Builder()
            .setConferenceAlias(binding.conferenceName.getText().toString())
            .setParamsHolder(paramsHolder)
            .build();

    VoxeetSDK.conference().create(conferenceCreateOptions)
            .then((ThenPromise<Conference, Conference>) conference -> {
                ConferenceJoinOptions conferenceJoinOptions = new ConferenceJoinOptions.Builder(conference)
                        .build();

                return VoxeetSDK.conference().join(conferenceJoinOptions);
            })
            .then(conference -> {
                Toast.makeText(MainActivity.this, "started...", Toast.LENGTH_SHORT).show();
                updateViews();
            })
            .error((error_in) -> {
                Toast.makeText(MainActivity.this, "Could not create conference", Toast.LENGTH_SHORT).show();
            });
}
  • In updateViews, enable and disable buttons based on the conference state.
private void updateViews() {
    ...

    ConferenceInformation current = VoxeetSDK.conference().getCurrentConference();
    // We can now add the logic to manage our basic state
    if (null != current && VoxeetSDK.conference().isLive()) {
        setEnabled(buttonsInConference, true);
    } else {
        setEnabled(buttonsNotInConference, true);
    }
}

Add a leaving option

1. To modify the layout, edit the main_activity.xml file, adding the following content for Step 2.2:

<LinearLayout ...>
    ...
    <LinearLayout ...>

        <!-- Step 2.2. The layout will be upgraded in the leave conference step -->
        <Button
            android:id="@+id/leave"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="leave" />

    </LinearLayout>

    <!-- Step 3. ... -->

</LinearLayout>

2. Modify the interface linking in the MainActivity class in MainActivity.java:

  • New method for MainActivity:
public void onLeave() {

}

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Add onClickListeners for onLeave()    
    binding.leave.setOnClickListener(_view -> onLeave());   
  • Add the following code to the onCreate method:
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Add the leave button and enable it only while in a conference
    add(views, R.id.leave);
    add(buttonsInConference, R.id.leave);
}

3. Add the following logic to the application:

  • Use the following implementation for onLeave:
public void onLeave() {
    VoxeetSDK.conference().leave()
            .then((result, solver) -> {
                updateViews();
                Toast.makeText(MainActivity.this, "left...", Toast.LENGTH_SHORT).show();
            })
            .error(error());
}

Run your application

On Android Studio, click on run. Make sure that you have:

  • a configured Android Virtual Device (AVD) available on your machine
  • a connected Android Debug Bridge (ADB) ready to use with an Android device