2020 API AWARDS WINNER - BEST IN MEDIA APIs LEARN MORE

Create a Basic Audio Conference Application

This guide details how to create a basic audio conference application. It starts with importing the Dolby Interactivity APIs Client SDK and guides through the next steps to launch the first conference call.

You can find our GitHub sample repositories here :

Requirements

  • JavaScript
  • Swift
  • Java

Make sure that you have:

  • Dolby.io account
  • Working webcam and a microphone

For reference, see the GitHub sample repository: https://github.com/voxeet/voxeet-sdk-browser-gettingstarted

Make sure that you have:

  • Dolby.io account
  • Working webcam and a microphone
  • Xcode 11 and MacBook

For reference, see the GitHub sample repository: https://github.com/voxeet/voxeet-sdk-ios-gettingstarted

Make sure that you have:

  • Dolby.io account
  • Working webcam and a microphone
  • Android Studio

For reference, see the GitHub sample repository: https://github.com/voxeet/voxeet-sdk-android-gettingstarted

Step 1: Create your project

  • JavaScript
  • Swift
  • Java

1. Create the project folders and files, as in the following example:

.
├── src/
│   ├── scripts/
│   │   └── client.js
│   │   └── ui.js
└── └── index.html
  • ui.js will contain a code related to the UI management
  • client.js will contain a code related to the conference management
  • index.html will contain a code related to the main interface

2. Open the index.html file in a code editor and paste there the following code. Replace the VERSION with the latest Web SDK version.

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Basic Web Video Conference Application</title>
    <script
      src="https://unpkg.com/@voxeet/voxeet-web-sdk@VERSION"
      type="text/javascript"
    ></script>
    <script src="./scripts/ui.js" type="text/javascript"></script>
  </head>

  <body>
    <div id="app">
      <h1 id="name-message">You are logged out.</h1>
    </div>
    <script type="text/javascript" src="scripts/client.js"></script>
  </body>
</html>

Project setup

1. Open Xcode 11 and create a new single view iOS app. Select Swift for the Language and Storyboard for the User Interface option.

Note: New projects created with Xcode 11 are intended for deployment on iOS 13 or later only. To support an earlier version of iOS, some changes will need to be made to the project that has been created. See this blog post for the details on what to change.)

2. Enable the background mode. To do that, go to your target settings ▸ Signing & Capabilities ▸ add + CapabilityBackground Modes.

  • Turn on Audio, AirPlay, and Picture in Picture.
  • Turn on Voice over IP.

3. If you want to support CallKit (receiving incoming calls when the application is killed) with VoIP push notifications, enable Push Notifications. You also need to upload your VoIP push certificate to the Dolby.io developer portal.

Capabilities

4. To establish privacy permissions, add two new keys in Info.plist:

  • Privacy - Microphone Usage Description.
  • Privacy - Camera Usage Description.

Installation

Using Carthage

Carthage is a decentralized dependency manager that builds dependencies and provides binary frameworks.

1. Install Carthage using the Homebrew and the following command:

$ brew update
$ brew install carthage

2. To integrate Dolby Interactivity APIs iOS SDK in your Xcode project, create a file named Cartfile in the same folder as the project. Add the following line to the Cartfile. Include the latest iOS SDK version instead of the VERSION string.

binary "https://raw.githubusercontent.com/voxeet/voxeet-sdk-ios/master/VoxeetSDK.json" ~> VERSION

3. Run carthage update to build the frameworks. The built frameworks are located in Carthage/Build/iOS. Add VoxeetSDK.framework and WebRTC.framework to your Xcode project as members of the app target. Turn off Copy items if needed.

More information about using Carthage is available here.

4. In the General tab of your target, ensure that VoxeetSDK.framework and WebRTC.framework are added to the 'Frameworks, Libraries, and Embedded Content' with the 'Embed & Sign' option selected for both frameworks.

Manual installation

1. Download the lastest iOS SDK release zip.

2. Unzip it and drag and drop VoxeetSDK.framework and WebRTC.framework frameworks to your project.

3. In the dialog that is displayed, select the Copy items if needed option and make sure that the frameworks are added to the proper target.

4. In the General tab of your target, add the VoxeetSDK.framework and WebRTC.framework to the 'Frameworks, Libraries, and Embedded Content' with the 'Embed & Sign' option selected for both frameworks.

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 following command into the dependencies section of the Gradle script build.gradle for “Module: app”, not the project. Replace VERSION with the latest Java SDK version.

dependencies {
    ...

    implementation ("com.voxeet.sdk:public-sdk:VERSION") {
        transitive = true
    }
}

2. Use butterknife to facilitate the integration of the SDK and manage injection:

dependencies {
    ...

    implementation 'com.jakewharton:butterknife:7.0.1'
    annotationProcessor 'com.jakewharton:butterknife:7.0.1'
}

3. Make sure to not use Android SDK version older than 16. The SDK is only compatible with android 16+. This change is for the android section of the same Gradle script.

android {
    ...
    defaultConfig {
        ...
        minSdkVersion 16
        ...
    }
    ...
}

4. Make sure that a compilation toolchain uses the Java 8.

android {
    ...

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

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 main_activity.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"?>
<LinearLayout 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">

    <!-- 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>

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.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import androidx.core.app.ActivityCompat;

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

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.PromiseExec;
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.events.promises.ServerErrorException;
import com.voxeet.sdk.json.internal.ParamsHolder;
import com.voxeet.sdk.json.RecordingStatusUpdatedEvent;
import com.voxeet.sdk.json.ParticipantInfo;
import com.voxeet.sdk.models.Conference;
import com.voxeet.sdk.models.Participant;
import com.voxeet.sdk.models.v1.CreateConferenceResult;
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.views.VideoView;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

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

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.

  • The Butterknife library that is responsible for the injected views.
  • 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 {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        //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;
    }

}

Step 2: Initialize the SDK with your Dolby.io credentials

There are two methods to initialize the SDK. You can initialize using a token, which requires a server to return the token to the application. Alternately, you can initialize with the secrets, which is not secure. In this example, we are initializing with the secrets. However, we recommend using a token to initialize the SDK. For more information, see Initializing.

Locate your Consumer Key and Consumer Secret. The instructions on how to create or find them is in the Prerequiste section of Initializing. It is recommended that you create a new Sample application for this tutorial.

  • JavaScript
  • Swift
  • Java

Open the client.js file, create a function named main, and call the VoxeetSDK.initialize method, replacing the consumerKey and consumerSecret strings with the values you just created.

const main = async () => {
  VoxeetSDK.initialize("consumerKey", "consumerSecret")
}

main()

Change the application(application:didFinishLaunchingWithOptions:) method from the AppDelegate.swift file, as in the following example. Replace the consumerKey and consumerSecret strings with the values you just created.

import UIKit
import VoxeetSDK

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // VoxeetSDK initialization.
        VoxeetSDK.shared.initialize(consumerKey: "YOUR_CONSUMER_KEY", consumerSecret: "YOUR_CONSUMER_SECRET")

        // Example of public variables to change the conference behavior.
        VoxeetSDK.shared.notification.push.type = .none
        VoxeetSDK.shared.conference.defaultBuiltInSpeaker = true
        VoxeetSDK.shared.conference.defaultVideo = false
        VoxeetSDK.shared.conference.audio3D = false

        return true
    }
}

1. Add the following code to the onCreate method from the MainActivity.java file. Replace the consumerKey and consumerSecret strings with the values you just created.

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

    //we now initialize the sdk
    VoxeetSDK.initialize("consumerKey", "consumerSecret");
}

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();
}

Step 3: 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.

  • JavaScript
  • Swift
  • Java

1. Open the client.js file and add a list of random names at the top of the file.

const avengersNames = [
    "Thor",
    "Cap",
    "Tony Stark",
    "Black Panther",
    "Black Widow",
    "Hulk",
    "Spider-Man",
]
let randomName = avengersNames[Math.floor(Math.random() * avengersNames.length)]

2. Edit the main function and open the session just after the SDK initialization. Use the open method from the SessionService:

const main = async () {
    VoxeetSDK.initialize('consumerKey', 'consumerSecret')
    try {
        // Open the session here !!!!
        await VoxeetSDK.session.open({ name: randomName })
    } catch (e) {
        alert('Something went wrong : ' + e)
    }
}

This step logs in to Dolby.io and enables using the SDK. To make this information visible in the UI, follow the next steps.

3. To highlight the logged in status in the UI, open the ui.js file and define a function called initUI.

// ui.js
const initUI = () => {
    const nameMessage = document.getElementById("name-message")
    nameMessage.innerHTML = `You are logged in as ${randomName}`
}

4. Go back the client.js file and, after opening the session, call initUI.

try {
    ...
    await VoxeetSDK.session.open({ name: randomName })
    initUI();
}

1. In ViewController.swift, add some variables in the ViewController class to refer to the user interface elements that will be created in step 2.

import VoxeetSDK

class ViewController: UIViewController {
    // Session UI.
    var sessionTextField: UITextField!
    var logInButton: UIButton!
    var logoutButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

2. In the ViewController class, add a function to create the user interface for the Dolby.io session and assign a random name.

override func viewDidLoad() {
    ...

    initSessionUI()
}

func initSessionUI() {
    let statusBarHeight = UIApplication.shared.statusBarFrame.height
    let avengersNames = [
        "Thor",
        "Cap",
        "Tony Stark",
        "Black Panther",
        "Black Widow",
        "Hulk",
        "Spider-Man",
    ]

    // Session text field.
    sessionTextField = UITextField(frame: CGRect(x: 8, y: statusBarHeight + 16, width: 84, height: 30))
    sessionTextField.borderStyle = .roundedRect
    sessionTextField.placeholder = "Username"
    sessionTextField.autocorrectionType = .no
    sessionTextField.text = avengersNames.randomElement()
    self.view.addSubview(sessionTextField)

    // Open session button.
    logInButton = UIButton(type: .system) as UIButton
    logInButton.frame = CGRect(x: 100, y: statusBarHeight + 16, width: 100, height: 30)
    logInButton.isEnabled = true
    logInButton.isSelected = true
    logInButton.setTitle("LOG IN", for: .normal)
    logInButton.addTarget(self, action: #selector(logInButtonAction), for: .touchUpInside)
    self.view.addSubview(logInButton)

    // Close session button.
    logoutButton = UIButton(type: .system) as UIButton
    logoutButton.frame = CGRect(x: 200, y: statusBarHeight + 16, width: 100, height: 30)
    logoutButton.isEnabled = false
    logoutButton.isSelected = true
    logoutButton.setTitle("LOGOUT", for: .normal)
    logoutButton.addTarget(self, action: #selector(logoutButtonAction), for: .touchUpInside)
    self.view.addSubview(logoutButton)
}

3. Add methods to log in and log out of the Voxeeet session.

@objc func logInButtonAction(sender: UIButton!) {
    // Open particpant session.
    let info = VTParticipantInfo(externalID: nil, name: sessionTextField.text, avatarURL: nil)
    VoxeetSDK.shared.session.open(info: info) { error in
        self.logInButton.isEnabled = false
        self.logoutButton.isEnabled = true
    }
}

@objc func logoutButtonAction(sender: UIButton!) {
    // Close participant session.
    VoxeetSDK.shared.session.close { error in
        self.logInButton.isEnabled = true
        self.logoutButton.isEnabled = false
    }
}

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 field for MainActivity:
@Bind(R.id.user_name)
EditText user_name;
  • New methods for MainActivity:
@OnClick(R.id.login)
public void onLogin() {

}

@OnClick(R.id.logout)
public void 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();
    user_name.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(user_name.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().isSocketOpen()) {
        setEnabled(buttonsNotLoggedIn, true);
        return;
    }
}

Step 4: Add a joining option

  • JavaScript
  • Swift
  • Java

1. To edit the layout, open the index.html file and add a div with the id form. In this new div, add an input element with the id alias-input and a button element with the id join-btn.

<div id="app">
  <h1 id="name-message">You are logged out.</h1>
  <!-- Add the form just after name-message h1 -->
  <div id="form">
    <label>Conference alias :</label>
    <input id="alias-input" value="Avengers meeting" />
    <button id="join-btn" disabled>Join</button>
  </div>
</div>

2. To add interface linking, open the ui.js file and edit the initUI function. Declare joinButton and conferenceAliasInput with references to the input and button elements added in the previous step. Enable the joinButton.

const initUI = () => {
    ...
    const joinButton = document.getElementById('join-btn')
    const conferenceAliasInput = document.getElementById('alias-input')

    joinButton.disabled = false
}

Reminder: The initUI is called when the session has been opened successfully. At this point, the user can start to participate in a conference and the joinButton is enabled in initUI.

3. To add the logic implementation, write the onclick handler for the joinButton. This handler:

  • Creates a conference using the ConferenceService.create method. It takes the conferenceAlias in the parameter object.
  • After creating a conference, the conference is joined using the ConferenceService.join method. The first parameter is the conference object returned from the create method.

Edit the initUI to write the joinButton onclick handler:

const initUI = () => {
    ...

    joinButton.onclick = () => {
        let conferenceAlias = conferenceAliasInput.value;

        /*
        1. Create a conference room with an alias
        2. Join the conference with its id
        */
        VoxeetSDK.conference.create({ alias: conferenceAlias })
            .then((conference) => VoxeetSDK.conference.join(conference, {}))
            .then(() => {
                joinButton.disabled = true;
            })
            .catch((e) => console.log('Something wrong happened : ' + e))
    };
};

Add UI for starting a conference in ViewController.swift.

1. In ViewController.swift, add some variables to the ViewController class to refer to the user interface elements that will be created in step 2.

class ViewController: UIViewController {
    ...

    // Conference UI.
    var conferenceTextField: UITextField!
    var startButton: UIButton!

    override func viewDidLoad() {
    ...
}

2. Add a method to extend the user interface for the Dolby.io conference.

override func viewDidLoad() {
    ...

    initSessionUI()
    initConferenceUI()
}

...

func initConferenceUI() {
    // Session text field.
    conferenceTextField = UITextField(frame: CGRect(x: 8, y: sessionTextField.frame.origin.y + sessionTextField.frame.height + 16, width: 84, height: 30))
    conferenceTextField.borderStyle = .roundedRect
    conferenceTextField.placeholder = "Conference"
    conferenceTextField.autocorrectionType = .no
    conferenceTextField.text = "Avengers meeting"
    self.view.addSubview(conferenceTextField)

    // Conference create/join button.
    startButton = UIButton(type: .system) as UIButton
    startButton.frame = CGRect(x: 100, y: sessionTextField.frame.origin.y + sessionTextField.frame.height + 16, width: 100, height: 30)
    startButton.isEnabled = false
    startButton.isSelected = true
    startButton.setTitle("START", for: .normal)
    startButton.addTarget(self, action: #selector(startButtonAction), for: .touchUpInside)
    self.view.addSubview(startButton)
}

3. Enable and disable the start button as appropriate.

@objc func logInButtonAction(sender: UIButton!) {
    // Open particpant session.
    let info = VTParticipantInfo(externalID: nil, name: sessionTextField.text, avatarURL: nil)
    VoxeetSDK.shared.session.open(info: info) { error in

        ...
        self.startButton.isEnabled = true /* Update start button state */

    }
}

@objc func logoutButtonAction(sender: UIButton!) {
    // Close participant session.
    VoxeetSDK.shared.session.close { error in

        ...
        self.startButton.isEnabled = false /* Update start button state */

    }
}

4. Add a method to start the Voxeeet conference.

...

@objc func startButtonAction(sender: UIButton!) {
    // Create a conference room with an alias.
    let options = VTConferenceOptions()
    options.alias = conferenceTextField.text ?? ""
    VoxeetSDK.shared.conference.create(options: options, success: { conference in
        // Join the conference with its id.
        VoxeetSDK.shared.conference.join(conference: conference, success: { response in
            self.logoutButton.isEnabled = false
            self.startButton.isEnabled = false
        }, fail: { error in })
    }, fail: { error in })
}

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 field for MainActivity:
@Bind(R.id.conference_name)
EditText conference_name;
  • New method for MainActivity:
@OnClick(R.id.join)
public void 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
    conference_name.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() {
    VoxeetSDK.conference().create(conference_name.getText().toString())
            .then((PromiseExec<CreateConferenceResult, Conference>) (result, solver) -> {
                VoxeetSDK.conference().fetchConference(result.conferenceId)
                        .then( (conference) -> {
                            ConferenceJoinOptions.Builder joinOptions = new ConferenceJoinOptions.Builder(conference);
                            solver.resolve(VoxeetSDK.conference().join(joinOptions.build()));
                        })
                .error(((error_in) -> {
                    Toast.makeText(MainActivity.this, "Could not find conference", Toast.LENGTH_SHORT).show();
                }));
            })
            .then((result, solver) -> {
                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);
    }
}

Step 5: Add a leaving option

  • JavaScript
  • Swift
  • Java

1. Open the index.html file and add a new button element with the id leave-btn.

...
<button id="join-btn">Join</button>
<!-- Add the button after join-btn -->
<button id="leave-btn" disabled>Leave</button>

2. Open the ui.js file and declare the leaveButton in the initUI function.

const initUI = () => {
    ...
    const leaveButton = document.getElementById('leave-btn')
}

3. To enable users to leave the conference, enable the leaveButton after joining the conference.

  • Open the ui.js file and edit the onclick handler for the joinButton.
joinButton.onclick = () => {
    ... VoxeetSDK.conference.join(conference))
        .then(() => {
            joinButton.disabled = true
            // Enable the leaveButton after resolving the join promise
            leaveButton.disabled = false
        })
    ...
}
  • Edit the initUI function to write the leaveButton click handler.
leaveButton.onclick = () => {
    VoxeetSDK.conference
        .leave()
        .then(() => {
            joinButton.disabled = false
            leaveButton.disabled = true
        })
        .catch(err => {
            console.log(err)
        })
}

Add UI for leaving a conference in ViewController.swift.

1. In ViewController.swift, add a variable to the ViewController class to refer to the user interface element that will be created in step 2.

class ViewController: UIViewController {
    ...

    // Conference UI.
    ...
    var leaveButton: UIButton!

    ...
}

2. Modify initConferenceUI to extend the user interface for the Dolby.io conference.

...

func initConferenceUI() {
    ...

    // Conference leave button.
    leaveButton = UIButton(type: .system) as UIButton
    leaveButton.frame = CGRect(x: 200, y: sessionTextField.frame.origin.y + sessionTextField.frame.height + 16, width: 100, height: 30)
    leaveButton.isEnabled = false
    leaveButton.isSelected = true
    leaveButton.setTitle("LEAVE", for: .normal)
    leaveButton.addTarget(self, action: #selector(leaveButtonAction), for: .touchUpInside)
    self.view.addSubview(leaveButton)
}
...

3. Enable and disable the leave button as appropriate.

@objc func logInButtonAction(sender: UIButton!) {
    // Open particpant session.
    let info = VTParticipantInfo(externalID: nil, name: sessionTextField.text, avatarURL: nil)
    VoxeetSDK.shared.session.open(info: info) { error in

        ...
        self.leaveButton.isEnabled = false /* Update leave button state */

    }
}

@objc func logoutButtonAction(sender: UIButton!) {
    // Close participant session.
    VoxeetSDK.shared.session.close { error in

        ...
        self.leaveButton.isEnabled = false /* Update leave button state */

    }
}

@objc func startButtonAction(sender: UIButton!) {
    ...
        // Join the conference with its id.
        VoxeetSDK.shared.conference.join(conference: conference, success: { response in

            ...
            self.leaveButton.isEnabled = true /* Update leave button state */

        }, fail: { error in })
    }, fail: { error in })
}
...

4. Add a method to leave the Voxeeet conference.

...

@objc func leaveButtonAction(sender: UIButton!) {
    VoxeetSDK.shared.conference.leave { error in
        self.logoutButton.isEnabled = true
        self.startButton.isEnabled = true
        self.leaveButton.isEnabled = false
    }
}

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:
@OnClick(R.id.leave)
public void 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());
}

Step 6: Run your application

  • JavaScript
  • Swift
  • Java

There are two ways of testing the application: an easier one (option 1) and a harder one (option 2).

Option 1

This option only works with browsers on your computer. To serve your application to another computer, use Option 2 This option does not work with Safari, which requires a secure HTTPS connection to enable WebRTC applications. It is recommended to use Chrome for this option. 1. Open the index.html file in a browser.

2. Check if you joined a conference with enabled audio and if you can turn your video on and off.

3. Join the same conference from another browser tab and try to speak. Check if you can hear yourself.

Option 2

1. Start an HTTP server to serve your files in the src/ folder. It is recommended to use live-server which allows live reloading of the application. Live reloading of the application can be useful during development.

To use live-server, make sure that you have node.js and npm installed.

2. Install the live-server using the following command.

npm install live-server -g

3. To serve the files using HTTPS, generate an SSL certificate. Use mkcert for generating self-signed certificates.

  • Go to mkcert github and follow the Installation section for your operating system.

Note: If you don’t want to generate SSL certificates, you can use ngrok to create a tunnel to a public https URL.

  • After installation of the mkcert, use the following command to install it in the system trust store:
mkcert -install

4. Generate a certificate.

  • Create a directory at the root of your project folder, name it config. Use it for storing certificates and configuration files for HTTPS.
  • Open a terminal and navigate to path/to/your_project/config.
  • Generate SSL certificate for localhost using the following command:
mkcert localhost

You should now have two files localhost-key.pem and localhost.pem in your config folder.

  • Create a https-config.js file in the config/ folder. Include in it the following content:
const fs = require("fs")

module.exports = {
  cert: fs.readFileSync(__dirname + "/localhost.pem"),
  key: fs.readFileSync(__dirname + "/localhost-key.pem"),
  passphrase: "12345",
}

This file will be passed as an argument of live-server to use HTTPS protocol when serving the src/ folder.

  • Open a terminal and navigate to the src folder in your project. Paste there the following command:
live-server --port=4444 --https=../config/https-config.js

This starts live reloading server on port 4444 using HTTPS protocol.

On Chrome on macOS, the Unsecure Connection Error may appear when you try to reach your application. In this case, follow the steps below:

1. If you are using Chrome version 81 or later, go to Chrome settingsPrivacy and securityMoreManage certificates and search for "mkcert". For earlier versions of Chrome, the setting is located at Chrome settingsAdvancedPrivacy and securityManage certificates.

2. Look for your certificate.

3. Double click on the certificate and expand the Trust section.

4. Set every entry to the Always Trust value.

5. Restart your Chrome browser.

Now, you should have secure access to https://localhost:4444.

The app should now build and run.

For reference, the complete contents of the files should be:

AppDelegate.swift

import UIKit
import VoxeetSDK

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // VoxeetSDK initialization.
        VoxeetSDK.shared.initialize(consumerKey: "YOUR_CONSUMER_KEY", consumerSecret: "YOUR_CONSUMER_SECRET")

        // Example of public variables to change the conference behavior.
        VoxeetSDK.shared.notification.push.type = .none
        VoxeetSDK.shared.conference.defaultBuiltInSpeaker = true
        VoxeetSDK.shared.conference.defaultVideo = false
        VoxeetSDK.shared.conference.audio3D = false

        return true
    }
}

ViewController.swift

import UIKit
import VoxeetSDK

class ViewController: UIViewController {
    // Session UI.
    var sessionTextField: UITextField!
    var logInButton: UIButton!
    var logoutButton: UIButton!

    // Conference UI.
    var conferenceTextField: UITextField!
    var startButton: UIButton!
    var leaveButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        initSessionUI()
        initConferenceUI()
    }

    func initSessionUI() {
        let statusBarHeight = UIApplication.shared.statusBarFrame.height
        let avengersNames = [
            "Thor",
            "Cap",
            "Tony Stark",
            "Black Panther",
            "Black Widow",
            "Hulk",
            "Spider-Man",
        ]

        // Session text field.
        sessionTextField = UITextField(frame: CGRect(x: 8, y: statusBarHeight + 16, width: 84, height: 30))
        sessionTextField.borderStyle = .roundedRect
        sessionTextField.placeholder = "Username"
        sessionTextField.autocorrectionType = .no
        sessionTextField.text = avengersNames.randomElement()
        self.view.addSubview(sessionTextField)

        // Open session button.
        logInButton = UIButton(type: .system) as UIButton
        logInButton.frame = CGRect(x: 100, y: statusBarHeight + 16, width: 100, height: 30)
        logInButton.isEnabled = true
        logInButton.isSelected = true
        logInButton.setTitle("LOG IN", for: .normal)
        logInButton.addTarget(self, action: #selector(logInButtonAction), for: .touchUpInside)
        self.view.addSubview(logInButton)

        // Close session button.
        logoutButton = UIButton(type: .system) as UIButton
        logoutButton.frame = CGRect(x: 200, y: statusBarHeight + 16, width: 100, height: 30)
        logoutButton.isEnabled = false
        logoutButton.isSelected = true
        logoutButton.setTitle("LOGOUT", for: .normal)
        logoutButton.addTarget(self, action: #selector(logoutButtonAction), for: .touchUpInside)
        self.view.addSubview(logoutButton)
    }

    func initConferenceUI() {
        // Session text field.
        conferenceTextField = UITextField(frame: CGRect(x: 8, y: sessionTextField.frame.origin.y + sessionTextField.frame.height + 16, width: 84, height: 30))
        conferenceTextField.borderStyle = .roundedRect
        conferenceTextField.placeholder = "Conference"
        conferenceTextField.autocorrectionType = .no
        conferenceTextField.text = "Avengers meeting"
        self.view.addSubview(conferenceTextField)

        // Conference create/join button.
        startButton = UIButton(type: .system) as UIButton
        startButton.frame = CGRect(x: 100, y: sessionTextField.frame.origin.y + sessionTextField.frame.height + 16, width: 100, height: 30)
        startButton.isEnabled = false
        startButton.isSelected = true
        startButton.setTitle("START", for: .normal)
        startButton.addTarget(self, action: #selector(startButtonAction), for: .touchUpInside)
        self.view.addSubview(startButton)

        // Conference leave button.
        leaveButton = UIButton(type: .system) as UIButton
        leaveButton.frame = CGRect(x: 200, y: sessionTextField.frame.origin.y + sessionTextField.frame.height + 16, width: 100, height: 30)
        leaveButton.isEnabled = false
        leaveButton.isSelected = true
        leaveButton.setTitle("LEAVE", for: .normal)
        leaveButton.addTarget(self, action: #selector(leaveButtonAction), for: .touchUpInside)
        self.view.addSubview(leaveButton)
    }

    @objc func logInButtonAction(sender: UIButton!) {
        // Open particpant session.
        let info = VTParticipantInfo(externalID: nil, name: sessionTextField.text, avatarURL: nil)
        VoxeetSDK.shared.session.open(info: info) { error in
            self.logInButton.isEnabled = false
            self.logoutButton.isEnabled = true
            self.startButton.isEnabled = true
            self.leaveButton.isEnabled = false
        }
    }

    @objc func logoutButtonAction(sender: UIButton!) {
        // Close participant session.
        VoxeetSDK.shared.session.close { error in
            self.logInButton.isEnabled = true
            self.logoutButton.isEnabled = false
            self.startButton.isEnabled = false
            self.leaveButton.isEnabled = false
        }
    }

    @objc func startButtonAction(sender: UIButton!) {
        // Create a conference room with an alias.
        let options = VTConferenceOptions()
        options.alias = conferenceTextField.text ?? ""
        VoxeetSDK.shared.conference.create(options: options, success: { conference in
            // Join the conference with its id.
            VoxeetSDK.shared.conference.join(conference: conference, success: { response in
                self.logoutButton.isEnabled = false
                self.startButton.isEnabled = false
                self.leaveButton.isEnabled = true
            }, fail: { error in })
        }, fail: { error in })
    }

    @objc func leaveButtonAction(sender: UIButton!) {
        VoxeetSDK.shared.conference.leave { error in
            self.logoutButton.isEnabled = true
            self.startButton.isEnabled = true
            self.leaveButton.isEnabled = false
        }
    }
}

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

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

What’s next?

You are now able to launch a conference from your device. If you want to learn more about creating video conference applications, check the Integrate video tutorial.