Create a basic audio conference application

This guide informs how to create a basic audio conference application. It starts with importing the Voxeet SDK and guides through the next steps to launch the first conference call.

You can find our GitHub samples repositories here :

Requirements

To follow this tutorial, make sure that you have:

  • Voxeet developer account (if you do not have it, register in the Voxeet developer portal).
  • Working webcam and a microphone.
  • Android Studio if you develop on Android.
  • Xcode 11 and Mac if you develop on iOS.

Step 1: Create your project

1. Create the project folder as in the following example:

.
├── 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

2. Open the index.html file in a code editor and paste there the following code. Include the latest JavaScript SDK version instead of the VERSION word.

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

Open Xcode 11 and create a new single view application.

Project setup

1. 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.

2. 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 Voxeet developer portal.

Capabilities

3. To establish privacy permissions, add two new keys in the 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 VoxeetSDK in your Xcode project, specify it in your Cartfile. Include the latest iOS SDK version instead of the VERSION word.

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

3. Run the carthage update to build the frameworks and drag the VoxeetSDK.framework and WebRTC.framework to your Xcode project. More information about it is available here.

Manual installation

1. Download the lastest VoxeetSDK release zip.

2. Unzip it and drag and drop frameworks to your project.

3. Select Copy items if needed option with 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. In this example, only default options are included.

1. Add the following command into the build.gradle dependencies. Include the latest Java SDK version instead of the VERSION word.

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

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

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+.

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. To update the layout, edit the main_activity.xml file from the app/src/main/res/layout/ folder. 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. 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 -->

    <!-- Step 7. Please put below the layout modification with the screen sharing step (start) -->
    <!-- Step 7.2 Please put below the layout modification with the screen sharing step (stop) -->


    <!-- Step 8. Please put below the layout modification with the start recording step -->
    <!-- Step 8.2 Please put below the layout modification with the stop recording step -->


</LinearLayout>

6. 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

7. Edit the MainActivity.java file and set the following values:

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

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

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

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

8. To simplify creating conferences, use the following:

  • 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.
@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

Prepare your consumerKey and consumerSecret. If you do not have it, check it on your developer account.

Open the client.js file, create a function named main, and use the initialize method:

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

main();

Change the application(application:didFinishLaunchingWithOptions:) method from the AppDelegate.swift file, as in the following example.

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.notification.type = .none
        VoxeetSDK.shared.conference.defaultBuiltInSpeaker = true
        VoxeetSDK.shared.conference.defaultVideo = false
        VoxeetSDK.shared.conference.audio3D = false
        
        return true
    }
}

1. Replace the onCreate method from the MainActivity.java file, 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");
}

2. Add the following line in the 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.

@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 the user name. In this tutorial, random user names are assigned.

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

This step logs in to Voxeet and enables using the SDK. To make this information visible on the UI, follow the further steps.

3. To highlight logged in status in the UI, 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}`;
};

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

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

Open a session in the ViewController.swift.

1. Modify the layout, as in the example.

import VoxeetSDK

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

2. Modify 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)
}

3. Edit the logic implementation.

@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 with the following content:

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

2. Modify interface linking.

  • Fields for MainActivity:
@Bind(R.id.user_name)
EditText user_name;

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);
}
  • Methods for MainActivity:

Create the following methods:

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

}

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

}

3. Implement a session:

  • Use the following 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());
}
  • 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 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: Add a joining option

1. To edit the layout, open the index.html file and create a div with a name form. Include in it an input and a button element with the alias-input name and 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 in it the joinButton and the conferenceAliasInput. Then, enable the joinButton, as in the example.

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

    joinButton.disabled = false;
}

Reminder: The initUI is called when the user successfully opens a session. At this point, the user can participate in a conference. This is why the joinButton can be enabled in initUI.

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

  • Creates a conference using the create method from the ConferenceService. It includes the conferenceAlias parameter.
  • After creating a conference, it joins it using the join method from the ConferenceService. It includes the conference object (retrieved as the return value of 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))
    };
};

Include audio in the ViewController.swift.

1. Modify the layout, as in the example.

class ViewController: UIViewController {
    ...

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

        initConferenceUI()
    }
    
    ...
}

2. Modify the 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 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 */
    
    }
}

3. Edit the logic implementation:

...

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

2. Modify 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() {

}

3. Edit the logic implementation.

  • 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 into 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) ->
                    solver.resolve(VoxeetSDK.conference().join(result.conferenceId)))
            .then((result, solver) -> {
                Toast.makeText(MainActivity.this, "started...", Toast.LENGTH_SHORT).show();
                updateViews();
            })
            .error(error());
}
  • Implement joinining option into 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: Add a leaving option

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

    ...
    <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 any time after joining, 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);
            });
    };

Implement audio and video in the ViewController.swift.

1. Modify the layout, as in the example.

class ViewController: UIViewController {
    ...

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

2. Modify 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 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 })
}

...

3. Edit the 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
    }
}

1. To modify the layout, edit the main_activity.xml file, as in the following example.

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

2. Modify interface linking.

  • The 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() {

}

3. Edit the 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

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

Option 1

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.

If you want to serve your application on your machine localhost, please use Option 2.

Option 2

1. Start an HTTP server to serve your files in the src/ folder. It's recommended to use the live-server to allow live reloading, it is handy to develop.

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 and request the remote Voxeet server, generate the SSL certificate. Use the mkcert that allows 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, the Unsecure Connection Error may appear when you try to reach your application. In this case, follow the steps below:

1. Go to Chrome settingsAdvancedPrivacy and securityManage certificates and search for "mkcert".

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.

Use the following 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.notification.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 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.