Create a basic audio conference application

Learn how to build a basic audio conference application using the Voxeet SDK.

Overview

This step-by-step guide will take you through a detailed creation of a basic audio conference application. It starts with importing the Voxeet SDK and guides you through the next steps to launch your first conference call. You can follow this guide for the platform of your choice.

You can find our GitHub samples repositories here :

Requirements

To follow this tutorial, make sure that you have:

  • a Voxeet developer account (if you don't, register on the Voxeet developer portal)
  • a working webcam and a microphone
  • the Android Studio if you create an application for Android
  • the Xcode and Mac if you develop on iOS

Step 1: Create your project

Create your project with the following folders and files :

.
├── src/
│   ├── scripts/
│   │   └── client.js
│   │   └── ui.js
└── └── index.html
  • ui.js file defines all function relatives to the HTML DOM manipulation
  • client.js file defines the main function
  • index.html is the entry point of the application

Open the index.html file in a code editor and paste there the following code.

<!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@1.9.6" type="text/javascript"></script>
    <script src="./scripts/ui.js" type="text/javascript"></script>
</head>

<body>
    <div id="app">
        <div id="footer">
            <p>@Voxeet 2019 - Basic web client video call Sample</p>
        </div>
    </div>
    <script type="text/javascript" src="scripts/client.js"></script>
</body>

</html>

Note: For this tutorial we'll be using the Voxeet SDK v1.9.6.

Open Xcode 11 and create a new Single View App.

1. Get your credentials

Get a consumer key and consumer secret for your app from your developer account dashboard.

If you are a new user, you'll need to sign up for a Voxeet developer account and add an app. You can create one app with a trial account. Upgrade to a paid account for multiple apps and to continue using Voxeet after your trial expires. We will give you dedicated help to get you up and running fast.

2. Project setup

Enable background mode (go to your target settings -> 'Signing & Capabilities' -> add 'Capability' -> 'Background Modes')

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

If you want to support CallKit (receiving incoming call when application is killed) with VoIP push notification, enable 'Push Notifications' (you will need to upload your VoIP push certificate to the Voxeet developer portal).

Capabilities

Privacy permissions, add two new keys in the Info.plist:

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

3. Installation

Carthage

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

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

To integrate VoxeetSDK into your Xcode project using Carthage, specify it in your Cartfile:

github "voxeet/voxeet-ios-sdk" ~> 1.0

Run carthage update to build the frameworks and drag VoxeetSDK.framework and WebRTC.framework into your Xcode project. More information at https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos.

Manually

Download the lastest release zip:

VoxeetSDK: https://github.com/voxeet/voxeet-ios-sdk/releases

Unzip then drag and drop frameworks into your project, select 'Copy items if needed' with the right target. Then in the general tab of your target, add the VoxeetSDK.framework and WebRTC.framework into 'Frameworks, Libraries, and Embedded Content' with 'Embed & Sign' option selected for both frameworks.

Creation

Open Android Studio and create a new Java project. In this example, the project will be left with default options.

Gradle modifications

Update the build.gradle's dependencies

The following line must be added into the gradle's dependencies :

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

And to facilitate the integration of the sdk, this getting started will use butterknife to manage injection.

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

Minimum Versions

The SDK is only compatible with android 16+

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

Java 8 must be used by the compilation toolchain

android {
    ...

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

Update the layout

Edit the main_activity.xml from the app/src/main/res/layout/ folder and set its content as :

<?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. Please put below the layout modification with the open/close session step -->

    <!-- Step 2. Please put below the layout modification with the join conference step -->
    <!-- Step 3. The join conference step will be upgraded in the leave conference step -->

    <!-- Step 4. Please put below the layout modification with the video step -->
    <!-- Step 5. The start video step will be upgraded in the stop video step -->

    <!-- Step 6. Please put below the layout modification with the view participants step -->

</LinearLayout>

Preparing the events and logic integration

To help manage the lifecycle for the future, we will create a list of views which will be responsible to hold the views which will be enabled in the following modes :

  • when no session is connected
  • when a session is connected
  • when a conference is connected to
  • when no conference is connected to

Edit the MainActivity.java and set the following fields :

protected List<View> views = new ArrayList<>();

protected List<View> buttonsNotLoggedIn = new ArrayList<>();

protected List<View> buttonsInConference = new ArrayList<>();

protected List<View> buttonsNotInConference = new ArrayList<>();

And the following methods will help the overall getting started to be a great experience :

  • Butterknife library which will be responsible of the injected views
  • override the onResume method to update our views
  • a method to manage and update the views
  • a default error management method
  • 2 methods to manage the various contextual based list of Views
@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 Voxeet credentials

Be sure to have your consumerKey and consumerSecret, if not, go to your developer account.

First of all, open the client.js file and create an instance of the Voxeet SDK :

const voxeet = new VoxeetSdk();

In this tutorial, we will keep things simple and we will initialize the Voxeet SDK with a randome name. Stay in the client.js file and add a list of randomNames and pick a randomName in this list :

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

Then, create a function named main and cast the initialize method :

const main = () => {
    voxeet.initialize('consumerKey', 'consumerSecret', {name: randomName})
        .then((currentUserId) => {
            initUI();
        })
        .catch((err) => {
            console.log(err);
        });
};

main();

At this point, you are now logged in to Voxeet and able to use the SDK. Let's make this information visible on the ui.

To do so, open the ui.js file and define the function called initUI :

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

In the AppDelegate.swift, change the application(application:didFinishLaunchingWithOptions:) method with the following.

Logic implementation

import UIKit
import VoxeetSDK

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        // Voxeet SDK initialization.
        VoxeetSDK.shared.initialize(consumerKey: "YOUR_CONSUMER_KEY", consumerSecret: "YOUR_CONSUMER_SECRET")
        
        // Example of public variables to change the conference behavior.
        VoxeetSDK.shared.pushNotification.type = .none
        VoxeetSDK.shared.conference.defaultBuiltInSpeaker = true
        VoxeetSDK.shared.conference.defaultVideo = false
        VoxeetSDK.shared.conference.audio3D = false
        
        return true
    }
}

In the MainActivity.java, change the onCreate method with the following content. Remember to edit the key and secret :

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

    //we now initialize the sdk
    VoxeetSdk.initialize("key", "secret");
}

Add the following line in the onResume

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

    ...

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

Right after, we also ask to unregister from the sdk when the MainActivity is going in background

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

    super.onPause();
}

Step 3: Open/close a session

Log in with a user name to enable creating and joining conferences. In this tutorial, we keep it very simple by assigning a random name to the user.

Note: this step is embedded in the previous one for javascript. You can jump directly to step 4.

You can now open a session in ViewController.swift.

Layout modification

import VoxeetSDK

class ViewController: UIViewController {
    // Session UI.
    var sessionTextField: UITextField!
    var logInButton: UIButton!
    var logoutButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

Interface linking

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

func initSessionUI() {
    let statusBarHeight = UIApplication.shared.statusBarFrame.height
    
    // 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
    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)
}

Logic implementation

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

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

Layout modification

Edit the main_activity.xml with the following items :

<LinearLayout ...>

    <!-- Step 1. Please put below the layout modification with the open/close session step -->
    <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>

</LinearLayout>

Interface linking

Fields for MainActivity

@Bind(R.id.user_name)
EditText user_name;

Add the following into 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);
}

Methods for MainActivity

And create the following methods :

@OnClick(R.id.login)
public void onLogin() {

}

@OnClick(R.id.logout)
public void onLogout() {

}

Session implementation

Implementation for onLogin

public void onLogin() {
    VoxeetSdk.session().open(new UserInfo(user_name.getText().toString(), "", ""))
            .then((result, solver) -> {
                Toast.makeText(MainActivity.this, "started...", Toast.LENGTH_SHORT).show();
                updateViews();
            })
            .error(error());
}

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

login implementation for updateViews

private void updateViews() {
    //disable every views
    setEnabled(views, false);

    //if the user is not connected, we will only enabled the not logged
    if (!VoxeetSdk.session().isSocketOpen()) {
        setEnabled(buttonsNotLoggedIn, true);
        return;
    }
}

Step 4: Join a conference

Layout editing

Open the index.html and add a div element named form as a child of the app div in which we'll expose an input for the conference alias and a button to join the conference :

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

Interface linking

Open the ui.js file, declare the joinButton and the conferenceAliasInput in the initUI function and enable the joinButton.

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

    joinButton.disabled = false;
};

When a user clicks on the "join" button, two things happen:

  1. 1 - the conference is created using the create method with the conferenceAlias as a parameter
  2. 2 - once the conference is created, the user can join it using the join method with the conference.id (retrieved as the return value of the create method)

In the initUI function, write an onclick function for the joinButton :

const initUI = () => {
    ...

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

        /*
        1. Create a conference room with an alias
        2. Join the conference with its id
        */
        voxeet.createConference({ alias: conferenceAliasValue })
            .then((res) => voxeet.joinConference(res.conferenceId))
            .then(() => {
                joinButton.disabled = true;
            })
            .catch((e) => alert('Something wrong happened : ' + e))
    };
};

Let's implement audio in ViewController.swift.

Layout modification

class ViewController: UIViewController {
    ...

    // Conference UI.
    var conferenceTextField: UITextField!
    var startButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ...

        initConferenceUI()
    }
    
    ...
}

Interface linking

...

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

@objc func logInButtonAction(sender: UIButton!) {
    // Open user session.
    let user = VTUser(externalID: nil, name: sessionTextField.text, avatarURL: nil)
    VoxeetSDK.shared.session.connect(user: user) { error in
    
        ...
        self.startButton.isEnabled = true /* Update start button state */
    
    }
}

@objc func logoutButtonAction(sender: UIButton!) {
    // Close user session
    VoxeetSDK.shared.session.disconnect { error in
    
        ...
        self.startButton.isEnabled = false /* Update start button state */
    
    }
}

Logic implementation

...

@objc func startButtonAction(sender: UIButton!) {
    // Create a conference room with an alias.
    let parameters = ["conferenceAlias": conferenceTextField.text ?? ""]
    VoxeetSDK.shared.conference.create(parameters: parameters, success: { response in
        guard let conferenceID = response?["conferenceId"] as? String else { return }
        
        // Join the conference with its id.
        VoxeetSDK.shared.conference.join(conferenceID: conferenceID, success: { response in
            self.logoutButton.isEnabled = false
            self.startButton.isEnabled = false
        }, fail: { error in })
    }, fail: { error in })
}

Layout modification

Edit the main_activity.xml with the following items :

<LinearLayout ...>
    ...

    <!-- Step 2. Please put below the layout modification with the join conference step -->
    <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 Below will be put the block concerning the leave functionnality -->
    </LinearLayout>
</LinearLayout>

Interface linking

Fields for MainActivity

@Bind(R.id.conference_name)
EditText conference_name;

onCreate modification for MainActivity

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

    //we add the join button to let it enable only when not in a conference
    add(views, R.id.join);

    add(buttonsNotInConference, R.id.join);
}

Methods for MainActivity

@OnClick(R.id.join)
public void onJoin() {

}

Logic implementation

Permission management in MainActivity's onResume

We will simplify the permission flow to ask for microphone and camera permissions when the app starts

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

Join implementation for onJoin

Bind the join flow implementation into the onJoin method previously created. The method will create and join the conference

public void onJoin() {
    VoxeetSdk.conference().create(conference_name.getText().toString())
            .then((PromiseExec<CreateConferenceResult, Conference>) (result, solver) ->
                    solver.resolve(VoxeetSdk.conference().join(result.conferenceId)))
            .then((result, solver) -> {
                Toast.makeText(MainActivity.this, "started...", Toast.LENGTH_SHORT).show();
                updateViews();
            })
            .error(error());
}

Join implementation for updateViews

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

Step 5: Leave a conference

Layout editing

Open the index.html file and add a button named leave-button in the form div of the index.html :

<div id="form">
    ...
    <!-- Add the leave button here-->
    <button id="leave-button" disabled="true">Leave</button>
</div>

Interface linking

Open the ui.js file and declare the leaveButton inside the initUI function :

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

Enable the leaveButton when the user has joined the conference. To do so, edit the joinButton onclick handler :

joinButton.onclick = () => {
    ...
    voxeet.createConference({ alias: conferenceAliasValue })
        .then((res) => voxeet.joinConference(res.conferenceId))
        .then(() => {
            // Enable the leave button here
            leaveButton.disabled = false;
        });
    ...
};

In the initUI function, write an onclick function for the leaveButton :

    leaveButton.onclick = () => {
        voxeet.leaveConference()
            .then(() => {
                joinButton.disabled = false;
                leaveButton.disabled = true;
            })
            .catch((err) => {
                console.log(err);
            });
    };

Let's implement audio and video in ViewController.swift.

Layout modification

class ViewController: UIViewController {
    ...

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

Interface linking

...

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

@objc func logInButtonAction(sender: UIButton!) {
    // Open user session.
    let user = VTUser(externalID: nil, name: sessionTextField.text, avatarURL: nil)
    VoxeetSDK.shared.session.connect(user: user) { error in
    
        ...
        self.leaveButton.isEnabled = false /* Update leave button state */
        
    }
}

@objc func logoutButtonAction(sender: UIButton!) {
    // Close user session
    VoxeetSDK.shared.session.disconnect { 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(conferenceID: conferenceID, success: { response in
        
            ...
            self.leaveButton.isEnabled = true /* Update leave button state */
            
        }, fail: { error in })
    }, fail: { error in })
}

...

Logic implementation

...

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

Layout modification

Edit the main_activity.xml with the following items below the previously added comment :

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

        <!-- Step 3. Below will be put the block concerning the leave functionnality -->
        <Button
            android:id="@+id/leave"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="leave" />

    </LinearLayout>
</LinearLayout>

Interface linking

onCreate modification for MainActivity

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

    //we add the leave button to be available while in conference
    add(views, R.id.leave);

    add(buttonsInConference, R.id.leave);
}

Methods for MainActivity

@OnClick(R.id.leave)
public void onLeave() {

}

Logic implementation

Implementation for onLeave

public void onLeave() {
    VoxeetSdk.conference().leave()
            .then((result, solver) -> updateViews()).error(error());
}

Implementation for updateViews

private void updateViews() {
    ...

    //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 6: Run your application

Option 1 (easy)

You can test your application in a very simple way by just opening your index.html file in your browser. At this point you will be able to join a conference with audio only and to start and stop your video once in the conference. You can also join the same conference from another browser tab and try to speak : you should hear yourself !

If you want to serve your application on your machine localhost, please check the Option 2 section

Option 2

To test your application, you should first start a http server to serve your files in the src/ folder. We will be using live-server to have live reloading allowed, which can be handy for development, but you're free to use the http server of your choice.

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

Install live-server using the following command :

npm install live-server -g

Beside we need to serve the files using HTTPS to request the remote Voxeet server. To generate a SSL certificate we will be using mkcert, that allows to generate self-signed certificates.

Go to mkcert github and follow the Installation section for your systemOS.

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

Once you've installed mkcert, use the following command to install it in the system trust store :

mkcert -install

Generate the certificate

First, create a directory at the root of your project folder named config. We will use this folder to store our certificate and our config file for HTTPS.

Open a terminal and navigate to path/to/your_project/config. Then generate SSL certificate for localhost using this command :

mkcert localhost

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

Let's create a file named https-config.js in the config/ folder with 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, navigate to the src folder of your project and paste this command :

live-server --port=4444 --https=./config/https-config.js

This will start a live reloading server on port 4444 using HTTPS protocol.

Now open your browser and go to https://localhost:4444. You should have access to your application.

/!\ On Chrome, you may face a Unsecure Connection Error when trying to reach for your application. In this case, do the

  • Go to Chrome settings > Advanced > Privacy and security > Manage certificates and search for "mkcert". You should find your certificate. Now double click on it and expend the Trust section. Set every entries to Always Trust. Restart your Chrome browser and now you should have a secure access to https://localhost:4444.

To sum up here is the complete files.

AppDelegate.swift

import UIKit
import VoxeetSDK

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        // Voxeet SDK initialization.
        VoxeetSDK.shared.initialize(consumerKey: "YOUR_CONSUMER_KEY", consumerSecret: "YOUR_CONSUMER_SECRET")
        
        // Example of public variables to change the conference behavior.
        VoxeetSDK.shared.pushNotification.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
        
        // 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
        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
        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 user session.
        let user = VTUser(externalID: nil, name: sessionTextField.text, avatarURL: nil)
        VoxeetSDK.shared.session.connect(user: user) { error in
            self.logInButton.isEnabled = false
            self.logoutButton.isEnabled = true
            self.startButton.isEnabled = true
            self.leaveButton.isEnabled = false
        }
    }
    
    @objc func logoutButtonAction(sender: UIButton!) {
        // Close user session
        VoxeetSDK.shared.session.disconnect { 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 parameters = ["conferenceAlias": conferenceTextField.text ?? ""]
        VoxeetSDK.shared.conference.create(parameters: parameters, success: { response in
            guard let conferenceID = response?["conferenceId"] as? String else { return }
            
            // Join the conference with its id.
            VoxeetSDK.shared.conference.join(conferenceID: conferenceID, 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 to have

  • an AVD device configured and available on the machine
  • connected an adb ready android device

What's next?

You are now able to launch a conference from your web browser, Android, or iOS device. If you want to learn more about creating video conference applications, move to the next step => Integrate video