Android Integration

The main objective is to write your basic VidyoPlatfrom Connector Android application that could be easily integrated with your business flow.

Environment Setup

To get started with Android Connector SDK, the requirement is an Android Studio & Android SDK. Android Studio is the IDE for creating native Android apps. It includes the Android SDK, which will need to be configured for use in the command line. Android Studio is also used to create Android virtual devices, which are required for the Android emulator.

Installing Android Studio

Download Android Studio from the Android website. More detailed installation instructions can be found in the User Guide.

Installing the Android SDK

Once installed, open Android Studio. The IDE should detect that the Android SDK needs to be installed. In the SDK Components Setup screen, finish installing the SDK. Keep note of the Android SDK Location.

By default, the latest stable SDK Platform is installed, which includes a collection of packages required to target that version of Android.

To install system images and other minor SDK platform packages, you may need to ensure Show Package Details is checked at the bottom of the SDK Manager.

For future reference, the Android SDK can be managed with Android Studio in the Configure » SDK Manager menu of the Android Studio welcome screen or Tools » SDK Manager inside Android projects.

Configuring Command Line Tools

The Android SDK ships with useful command-line tools. Before they can be used, some environment variables must be set. The following instructions are for macOS and Linux. For Windows, check the documentation on setting and persisting environment variables in terminal sessions.

In ~/.bashrc, ~/.bash_profile, or similar shell startup scripts, make the following modifications:

  1. Set the ANDROID_SDK_ROOT environment variable. This path should be the Android SDK Location used in the previous section.

    For Mac:

    $ export ANDROID_SDK_ROOT=$HOME/Android/sdk

    For Linux/Windows:

    $ export ANDROID_SDK_ROOT=$HOME/Android/Sdk
  2. Add the Android SDK command-line directories to PATH. Each directory corresponds to the category of command-line tool.

    $ # avdmanager, sdkmanager$ 
    export PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin$ 
    # adb, logcat$ 
    export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools$ 
    # emulator$ 
    export PATH=$PATH:$ANDROID_SDK_ROOT/emulator

Java

Native Android apps are compiled with the Java programming language. Download JDK8 from the download page.

Gradle

Gradle is the build tool used in Android apps and must be installed separately. See the install page for details.

Project Setup

If you already have all the above tools installed, you should be able to get up and running within a few minutes. Let's start from scratch by creating a blank project.

Wait for the Gradle sync to complete. Now we are good to jump into the Connector SDK setup.

Connector SDK Setup

The Connector SDK offers the same APIs on all supported platforms, providing a fast learning curve and enabling rapid development on all device types.

Download & Locate

First of all, we have to Download the latest Connector SDK Package for Android. You can download it under Resources section of VidyoPlatfrom Space or use the following link:

Connector SDK for Android

  • Download and unzip VidyoClient-AndroidSDK package content.

  • Locate VidyoClient.aar under /VidyoClient-AndroidSDK/lib/android/

  • Copy VidyoClient.aar to your project under /app/libs/

In order to link the Connector SDK AAR Library we have to tell our Gradle sync to to look into /app/libs a folder and lookup for *.aar files specifically. Open app/build.gradle configurations file and apply fileTree dependency implementation:

implementation fileTree(dir: 'libs', include: ['*.aar'])

Hit Sync Now popup hint to apply the changes.

Now we are good to start over with Basic Connector SDK implementation.

Connector SDK Implementation

Connector SDK has a very simple API that only requires three basic steps:

  1. When using the client library, the first step is to initialize the Connector SDK

  2. Construct and pass the View where the preview and participants should be rendered.

  3. Connect to the live conference. A developer can specify the portal and room where they want all the live participants to connect.

Setup Android Permissions

Connector SDK requires Android-specific runtime permissions, which we have to request before initializing SDK along with the Basic permissions for Networking.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

<!-- RUNTIME PERMISSIONS -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Add those permissions to your AndroidManifest.xml

Now let's add the following code in order to request Runtime Permissions under our MainActivity:

private static final int PERMISSIONS_REQUEST_CODE = 0x144;
private static final String[] PERMISSIONS = new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    requestPermissions();
}

/**
 * Verify or Request Runtime Permissions every time before attempting to initialize Connector SDK
 */
private void requestPermissions() {
        List<String> permissionsRequired = new ArrayList<>();
        for (String permission : PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED)
                permissionsRequired.add(permission);
        }

        if (!permissionsRequired.isEmpty()) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSIONS_REQUEST_CODE);
        } else {
            onPermissionsAllowed();
        }
} 

/**
 * Here we are safe to initialize a Connector SDK
 */
private void onPermissionsAllowed() {

}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == PERMISSIONS_REQUEST_CODE) {
        onPermissionsAllowed();
    }
}

Initialize Connector SDK

Once we have passed through sequential permissions requests (the app will ask for Camera & Audio permissions) we are good to initialize Connector SDK.

ConnectorPkg.initialize();
ConnectorPkg.setApplicationUIContext(this);

Obviously, ConnectorPkg will be highlighted as an unknown class. Use auto-import or correct the import package manually:

import com.vidyo.VidyoClient.Connector.ConnectorPkg;

Construct Connector API

Connector Object is the main communication instance with the Connector SDK by leveraging all the API calls and managing the SDK Library lifecycle. Base concept APIs that we are going to review in the scope of this Guide:

  • Construct Connector Object: new Connector

  • Update video renderer: showViewAt

  • Connect to the call: connectToRoomAsGuest

  • Disconnect from the call: disconnect

  • Get Connector state: getState

  • Manage Connector lifecycle: setMode

  • Destroy connector instance: disable

We are going to create a new connector instance right after SDK initialization. But first of all, let's have a look at the constructor parameters:

Connector(Object viewId, 
    Connector.ConnectorViewStyle viewStyle, 
    int remoteParticipants, 
    String logFileFilter, 
    String logFileName, 
    long userData);
NameDescription

viewId

A platform-specific view ID where the VidyoConnector's rendering window will be added as a child window.

viewStyle

Type of the composite renderer which represents the visual style and behaviour.

remoteParticipants

Number of remote participants to composite into the window. Setting the value to 0 (zero) will render the preview only.

logFileFilter

A space-separated (or comma-separated) sequence of names of log levels, each optionally followed by a category.

logFileName

Full path to the file where the log should be stored; otherwise, NULL or empty string, in order to use the default OS-dependent writable path.

userData

Arbitrary user data that can be retrieved later.

Guess we can populate everything except viewId. For this, we have to create a ViewGroup container and pass it as a viewId reference. Visit your activity_main.xml UI layout file and add any kind of ViewGroup expanding full screen.

<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/video_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now let's create a Connector Object:

public class MainActivity extends AppCompatActivity implements ViewTreeObserver.OnGlobalLayoutListener {
    
    // ...

    private Connector mConnector;
    private FrameLayout mVideoContainer;
    
    // ...

    /**
     * Here we are safe to initialize a Connector SDK
     */
    private void onPermissionsAllowed() {
         ConnectorPkg.initialize();
         ConnectorPkg.setApplicationUIContext(this);

         mVideoContainer = findViewById(R.id.video_container);
        
         mConnector = new Connector(videoContainer, 
                Connector.ConnectorViewStyle.VIDYO_CONNECTORVIEWSTYLE_Default, 
                8, 
                "warning debug@VidyoClient",
                "", 
                0);

        mVideoContainer.getViewTreeObserver().addOnGlobalLayoutListener(this)
    }

    @Override
    public void onGlobalLayout() {
       int width = mVideoContainer.getWidth();
       int height = mVideoContainer.getHeight();

       mConnector.showViewAt(mVideoContainer, 0, 0, width, height);
    }
}

In order to render self-view or any remote participant, you have to tell the library your View's dimension and renderer position:

mConnector.showViewAt(mVideoContainer, 0, 0, width, height);

Moreover, you have to do it every time the View's layout has changed or manipulated. In this guide, we'll use a robust OnGlobalLayoutListener, however, you are good to go with your own solution. Don't forget to unregister from OnGlobalLayoutListener updates:

@Override
protected void onDestroy() {
    super.onDestroy();
    mVideoContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}

Host and RoomKey

In the previous tutorial you created a room on your tenant and received a RoomLink:

As you may guess, the following chunk of the above link is your Host value:

And this one is a RoomKey:

bPFCSWVZnb

Host (or your tenant URL) and RoomKey uniquely identify the meeting room. In other words - these are two values that your client application should get in order to connect to the room.

In a real-world typically these values are sent to the client apps from the backend server application after the room has been created.

After we got the necessary parameters (host, roomKey, displayName) we can proceed with joining the call.

Connect to the Call

Before we start executing Connector API let's add some basic interaction UI. We are going to have Connect/Disconnect Button. That's all we need to hop on the call or disconnect. Additionally, we would like to indicate that the call connection is in progress with basic ProgressBar. Modify your activity_main.xml UI layout file:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/video_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/connect_disconnect_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="24dp"
        android:text="Connect"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ProgressBar
        android:id="@+id/connection_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.5" />
        
</androidx.constraintlayout.widget.ConstraintLayout>

In order to connect to the call, you should have your Host and RoomKey already prepared as stated in the section above or Getting Started section.

  • Execute connectToRoomAsGuest Connector API and connect to the call

  • Execute disconnect Connector API in order to disconnect from the call

  • To differentiate Connector's Connected|Disconnected state, we'll use getState API

// ...
private Button mConnectBtn;
private ProgressBar mConnectionProgress;

// ..

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

    mVideoContainer = findViewById(R.id.video_container);

    mConnectBtn = findViewById(R.id.connect_disconnect_btn);
    mConnectBtn.setOnClickListener(this);

    mConnectionProgress = findViewById(R.id.connection_progress_bar);
    mConnectionProgress.setVisibility(View.GONE);
    
    requestPermissions();
}

// ...

 @Override
 public void onClick(View view) {
    if (view == mConnectBtn) {
        boolean isConnected = mConnector.getState() == Connector.ConnectorState.VIDYO_CONNECTORSTATE_Connected;

        mConnectionProgress.setVisibility(View.VISIBLE); 
        mConnectBtn.setEnabled(false);
           
        if (!isConnected) {
            mConnectBtn.setText("Connecting...");
            
            mConnector.connectToRoomAsGuest("HOST", "DISPLAY NAME", "ROOM KEY", "ROOM PIN",
                    new Connector.IConnect() {

                        @Override
                        public void onSuccess() {
                            runOnUiThread(() -> {
                                mConnectBtn.setEnabled(true);
                                mConnectBtn.setText("Disconnect");
                                mConnectionProgress.setVisibility(View.GONE);
                            });
                        }

                        @Override
                        public void onFailure(Connector.ConnectorFailReason connectorFailReason) {
                            runOnUiThread(() -> {
                                mConnectBtn.setEnabled(true);
                                mConnectionProgress.setVisibility(View.GONE);
                                Toast.makeText(MainActivity.this, "Connection failed: " + connectorFailReason, Toast.LENGTH_LONG).show();
                            });
                        }

                        @Override
                        public void onDisconnected(Connector.ConnectorDisconnectReason connectorDisconnectReason) {
                            runOnUiThread(() -> {
                                mConnectBtn.setEnabled(true);
                                mConnectionProgress.setVisibility(View.GONE);
                                mConnectBtn.setText("Connect");
                            });
                        }
                    });
            } else {
                mConnectBtn.setText("Disconnecting...");
                mConnector.disconnect();
            }
    }
}

Callbacks

There are three callback functions in the API call above:

  • onSuccess(): will be fired when connection to the room went successfully. This place is an indicator of the fact that your application has joined the call.

  • onFailure(): will be fired when connection attempt was made, but something went wrong and your app hasn't joined the call. Reason parameter may provide you with details why connection attempt was not successful.

  • onDisconnected(): will be fired when your application disconnected from the call.

As you might notice, all the callbacks coming from Connector SDK are routed to the Main UI Thread with runOnUiThread Activity's API. It's because the library is executing them from its own Background Thread where you cannot touch or update UI.

Don't forget to populate your Conference Credentials:

"HOST" | "DISPLAY NAME" | "ROOM KEY" | "ROOM PIN"

Leave "Room Pin" as an empty "" if not specified.

Disconnect from the call

Connector disconnect API has been mentioned earlier, however, the important notice is to properly manage the disconnection state. It's not recommended to wrap the activity during the active conference, therefore, you have to disconnect first and wrap up the connector instance gracefully.

Assume, the user would like to quit the Activity. We have to prevent this action until further disconnection:

private boolean mUserQuitRequest = false;

// ....

@Override
public void onBackPressed() {
    if (mConnector.getState() == Connector.ConnectorState.VIDYO_CONNECTORSTATE_Connected) {
        mUserQuitRequest = true;
        mConnector.disconnect();
    } else {
        super.onBackPressed();
    }
}

// ...

@Override
 public void onClick(View view) {
    if (view == mConnectBtn) {
        boolean isConnected = mConnector.getState() == Connector.ConnectorState.VIDYO_CONNECTORSTATE_Connected;

        mConnectionProgress.setVisibility(View.VISIBLE); 
        mConnectBtn.setEnabled(false);
           
        if (!isConnected) {
            mConnectBtn.setText("Connecting...");
            
            mConnector.connectToRoomAsGuest("HOST", "DISPLAY NAME", "ROOM KEY", "ROOM PIN",
                    new Connector.IConnect() {

                        @Override
                        public void onSuccess() {
                            runOnUiThread(() -> {
                                mConnectBtn.setEnabled(true);
                                mConnectBtn.setText("Disconnect");
                                mConnectionProgress.setVisibility(View.GONE);
                            });
                        }

                        @Override
                        public void onFailure(Connector.ConnectorFailReason connectorFailReason) {
                            runOnUiThread(() -> {
                                mConnectBtn.setEnabled(true);
                                mConnectionProgress.setVisibility(View.GONE);
                                Toast.makeText(MainActivity.this, "Connection failed: " + connectorFailReason, Toast.LENGTH_LONG).show();
                                
                                if (mUserQuitRequest) {
                                    MainActivity.super.onBackPressed();
                                }
                            });
                        }

                        @Override
                        public void onDisconnected(Connector.ConnectorDisconnectReason connectorDisconnectReason) {
                            runOnUiThread(() -> {
                                mConnectBtn.setEnabled(true);
                                mConnectionProgress.setVisibility(View.GONE);
                                mConnectBtn.setText("Connect");

                                if (mUserQuitRequest) {
                                    MainActivity.super.onBackPressed();
                                }
                            });
                        }
                    });
            } else {
                mConnectBtn.setText("Disconnecting...");
                mConnector.disconnect();
            }
    }
}

mUserQuitRequest will hold the quit request state until onDisconnected or onFailure in order to re-test onBackPressed action.

Manage Lifecycle

You have to tell Connector Object about lifecycle state change so he can manage his internal flow accordingly. setMode is the proper API to do this.

@Override
protected void onResume() {
    super.onResume();
    if (mConnector != null)
        mConnector.setMode(Connector.ConnectorMode.VIDYO_CONNECTORMODE_Foreground);
}

@Override
protected void onPause() {
    super.onPause();
    if (mConnector != null)
        mConnector.setMode(Connector.ConnectorMode.VIDYO_CONNECTORMODE_Background);
}

Destruct Connector Instance

The last and most important part is the "Connector Object deallocation". Ideally, you have to do this along with the Activity lifecycle to prevent a further memory leak. To sync them together, we'll use onDestroy Activity's lifecycle callback. At this point, we are insured that mConnector state is not "Connected" and we are safe to call disable API.

 @Override
 protected void onDestroy() {
    super.onDestroy();
    mVideoContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    // Destroy connector instance    
    if (mConnector != null)
        mConnector.disable();
 }

Since we disconnect before "Quit" with our custom logic, we can guarantee that during onDestroy() callback the mConnector state is "Disconnected". In case your application flow is different, the only rule you have to remember is to call "disable" API at a "disconnected" state. We might change this behavior in the future so you will not bother about the connection state. Until now, it's a mandatory action to prevent crashes and memory leaks.

Last updated