This tutorial will walk you through the steps of setting up a basic Android client that uses the Vonage Video API.
All applications that use the Vonage Video API require both a client and a server component. The client-side code is what loads in an end-user’s device (phone, computer) and handles the majority of OpenTok functionality, including connecting to the session, publishing audio-video streams to the session, and subscribing to other clients’ streams. For more information on clients, servers, and sessions, see Video API Basics.
In this tutorial, you will be utilizing the OpenTok Android SDK, OpenTok's client-side library for Android devices, to quickly and easily build a real-time interactive video application.
Estimated completion time: 25 mins
Want to skip this tutorial? You can jump to the completed Android client code in the Basic-Video-Chat-Java folder of our Android sample app repo on GitHub. The repo includes a README with documentation for running and exploring the code. Also, this tutorial discusses the Java version of the Android sample app. For a Kotlin version with documentation, see the Android sample app repo.
To complete this tutorial, you’ll need:
Open Android Studio and select New Project from the File menu.
Set the minimum SDK for the app to be API 16 (Android 4.1, Jelly Bean).
Click through the wizard, ensuring that Empty Activity is selected.
Leave the Activity Name set to MainActivity
, and leave the Layout Name set to
activity_main
.
The app uses Maven to load the OpenTok Android SDK:
Edit the build.gradle for your project (at your project's root) and add the following code snippet to the
allprojects/repositories
section:
mavenCentral()
At this point the build.gradle file should look like this:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Modify build.gradle for your module (the app/build.gradle file) and add the following code snippet to the dependencies section:
implementation 'com.opentok.android:opentok-android-sdk:2.22.0'
When using OpenTok Android 2.16.0 or above, you will need to add the following to to the `android` section of the app's build.gradle file as well:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
At this point the build.gradle file for your module should look like this:
apply plugin: 'com.android.application'
android {
compileSdkVersion 32
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 16
targetSdkVersion 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'com.opentok.android:opentok-android-sdk:2.22.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Sync your project.
In order to connect to an OpenTok session, the client will need access to some authentication credentials — an API key, session ID, and token. In a production application, these credentials should be generated by a server, but to speed things up we will just hard code the values for now:
Create the OpenTokConfig
class (File | New | Java Class), add some static variables to store the API key, session ID, and token.
We'll also add some constants for our log message identifier and permissions:
public class OpenTokConfig {
public static final String API_KEY = "";
public static final String SESSION_ID = "";
public static final String TOKEN = "";
}
Adjust the code by hard coding the values for the API_KEY
, SESSION_ID
and TOKEN
.
To do this, log into your Video API account, either create a new OpenTok API project or use an existing OpenTok API project, then go to your project page and scroll down to the Project Tools section. From there, you can generate a session ID and token manually. Use the project’s API key along with the session ID and token you generated.
Important: You can continue to get the session ID and token values from your Account during testing and development, but before you go into production you must set up a server. We will discuss this in Next steps at the end of this tutorial.
For more information on sessions, tokens, and servers, check out Video API Basics.
Because our app uses audio and video from the user's device, we’ll need to add some code to request audio and video permissions. We'll use the EasyPermissions library to do this.
Start by adding the EasyPermissions library to your project — open the build.gradle for your module (the app/build.gradle file) and add the following code snippet to the dependencies section:
implementation 'pub.devrel:easypermissions:3.0.0'
In your AndroidManifest.xml file, add this code snippet inside the manifest
tags:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
In your MainActivity.java file, add a new method called onRequestPermissionsResult
:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
This is boilerplate code to use the EasyPermissions library.
Important: You will have to add imports manually, by clicking EasyPermissions (red text) and pressing the key combnation Option + Enter on macOS or Alt + Enter on Windows. This step may be required when pasting the code. You can also enable "Add unambigous imports on the fly" option (Preferences | Editor | Auto Import) to add imports automatically.
Add the PERMISSIONS_REQUEST_CODE
property at the top of the MainActivity.java file:
private static final int PERMISSIONS_REQUEST_CODE = 124;
Next we'll add a requestPermissions()
method:
@AfterPermissionGranted(PERMISSIONS_REQUEST_CODE)
private void requestPermissions() {
String[] perms = { Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };
if (EasyPermissions.hasPermissions(this, perms)) {
// initialize and connect to the session
} else {
EasyPermissions.requestPermissions(this, "This app needs access to your camera and mic to make video calls", PERMISSIONS_REQUEST_CODE, perms);
}
}
This checks if the permissions have already been granted. If they haven’t, we prompt the user for camera and mic permissions with the `EasyPermissions.requestPermissions` method.
Once permissions have been granted, this method will fire again due to the @AfterPermissionGranted(PERMISSIONS_REQUEST_CODE)
annotation. We'll add some code to initialize the session and view objects in the coming steps.
Now add requestPermission();
to call the method inside
the onCreate()
method:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermissions();
}
Next, we will connect to the OpenTok session. You must do this before you can publish your audio-video stream to the session or view other participants streams.
Add a session
property to the MainActivity class (right after the last lines you added in Step 3):
private Session session;
The Session class is defined in the OpenTok Android SDK. It represents an OpenTok session and includes methods for interacting with the session.
To facilicate logging, add a TAG
property at the top of
the MainActivity
class:
private static final String TAG = MainActivity.class.getSimpleName();
In the requestPermissions()
method you created in the last step, add the following lines of code under the // initialize and connect to the session
comment to instantiate the Session object and call its connect(token)
method:
initializeSession(OpenTokConfig.API_KEY, OpenTokConfig.SESSION_ID, OpenTokConfig.TOKEN);
Just below the requestPermissions()
method, add the following lines of code:
private void initializeSession(String apiKey, String sessionId, String token) {
Log.i(TAG, "apiKey: " + apiKey);
Log.i(TAG, "sessionId: " + sessionId);
Log.i(TAG, "token: " + token);
session = new Session.Builder(this, apiKey, sessionId).build();
session.setSessionListener(sessionListener);
session.connect(token);
}
This code uses the Session.Builder()
to instantiate a Session object. The constructor takes
three parameters:
The Session.Builder.build()
method returns a new Session instance.
The Session.setSessionListener()
method sets the object that will implement the
SessionListener interface. This interface includes callback methods are called in response to
session-related events. (We will implement them in the next steps).
The Session.connect()
method of the session
object connects the client application to the
OpenTok session. You must connect before sending or receiving audio-video streams in the session
(or before interacting with the session in any way). The connect()
method takes one parameter:
the authentication token for this client to connect to the OpenTok session.
Next we will create the sessionListener
property. Add
the following code at the top of the MainActivity
class:
private Session.SessionListener sessionListener = new Session.SessionListener() {
@Override
public void onConnected(Session session) {
Log.d(TAG, "onConnected: Connected to session: " + session.getSessionId());
}
@Override
public void onDisconnected(Session session) {
Log.d(TAG, "onDisconnected: Disconnected from session: " + session.getSessionId());
}
@Override
public void onStreamReceived(Session session, Stream stream) {
Log.d(TAG, "onStreamReceived: New Stream Received " + stream.getStreamId() + " in session: " + session.getSessionId());
}
@Override
public void onStreamDropped(Session session, Stream stream) {
Log.d(TAG, "onStreamDropped: Stream Dropped: " + stream.getStreamId() + " in session: " + session.getSessionId());
}
@Override
public void onError(Session session, OpentokError opentokError) {
Log.e(TAG, "Session error: " + opentokError.getMessage());
}
};
When the client connects to the OpenTok session, the implementation of the
SessionListener.onConnected(session)
method is called.
When the client disconnects from the OpenTok session, the implementation of the
SessionListener.onDisconnected(session)
method is called.
If the client fails to connect to the OpenTok session, the implementation of the
SessionListener.onError(session, error)
method is called.
When another client publishes a stream to the OpenTok session, the implementation of the
SessionListener.onStreamReceived(session, stream)
method is called.
When another client stops publishing a stream to the OpenTok session, the implementation
of the SessionListener.onStreamDropped(session, stream)
method is called.
For now, the app prints to the debugger console when any of these events occur.
Debug your application. If the app successfully connects to the OpenTok session, the
SessionListener.onConnected(session)
method logs to the debug console.
Add the methods below to the MainActivity
class to notify
the session about Activity lifecycle events:
@Override
protected void onPause() {
super.onPause();
if (session != null) {
session.onPause();
}
}
@Override
protected void onResume() {
super.onResume();
if (session != null) {
session.onResume();
}
}
The OpenTok Android SDK exposes videos you publish and subscribe to as View objects. You can
add these as children of ViewGroup
objects in your app. This sample app will use FrameLayout
objects (which extend ViewGroup
) as containers for the publisher and subscriber views:
In Android Studio, open the app/res/layout/activity_main.xml file. Click the "Text" tab at the bottom of the editor to display the XML code for the file.
Replace the file contents with the following code:
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="fill_parent"
android:layout_height="fill_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:id="@+id/subscriber_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<FrameLayout
android:id="@+id/publisher_container"
android:layout_width="90dp"
android:layout_height="120dp"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:background="#CCCCCC"
android:padding="2dp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Be sure to replace the entire content of the <TextView>
element (through the
closing />
tag).
Now declare publisherViewContainer
and subscriberViewContainer
as properties of
the MainActivity class (right after the declaration for the session
property):
private FrameLayout publisherViewContainer;
private FrameLayout subscriberViewContainer;
Finally, in the existing onCreate()
method, initialize these layout view objects by adding
the following lines of code under the setContentView()
method call:
publisherViewContainer = findViewById(R.id.publisher_container);
subscriberViewContainer = findViewById(R.id.subscriber_container);
At this point, your onCreate()
method should look like this:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
publisherViewContainer = findViewById(R.id.publisher_container);
subscriberViewContainer = findViewById(R.id.subscriber_container);
requestPermissions();
}
The steps above merely set up a sample layout for this app. Your own app can add publisher and subscriber
views as children of other ViewGroup
objects
When the app connects to the OpenTok session, we want it to publish an audio-video stream to the session, using the camera and microphone:
Add a publisher
property to the MainActivity class (after the declaration of the
session
property):
private Publisher publisher;
The Publisher class is defined in the OpenTok Android SDK.
Modify the implementation of the SessionListener.onConnected()
method to include
code to publish a stream to the session:
@Override
public void onConnected(Session session) {
Log.d(TAG, "onConnected: Connected to session: " + session.getSessionId());
publisher = new Publisher.Builder(MainActivity.this).build();
publisher.setPublisherListener(publisherListener);
publisher.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
publisherViewContainer.addView(publisher.getView());
if (publisher.getView() instanceof GLSurfaceView) {
((GLSurfaceView) publisher.getView()).setZOrderOnTop(true);
}
session.publish(publisher);
}
The code above uses Publisher.Builder()
to instantiate a Publisher object. The constructor takes
one parameter: the context associated with this process (although it may be different in a production app).
The Publisher.setPublisherListener()
method sets the object that will implement the
PublisherListener interface. This interface includes callback methods are called in response to
publisher-related events.
Important: The context you used depends on the specific use case. However usually it is desired for the session to live outside of the Activity (for example, between activities). For production applications, it's convenient to use application context instead of activity context.
The code then passes the Publisher object in as a parameter of the
Session.publish()
method. This method publishes an audio-video stream to the
OpenTok session, using the camera and microphone of the Android device. (Note that
in an Android virtual device, the OpenTok Android SDK uses a test video when publishing a
stream).
The Publisher object has a getView()
returns, which returns an Android View object.
This view displays the video captured from the device’s camera. The code
adds this view as a subview of the publisherViewContainer
object.
To log events, add a publisherListener
property to
the sessionListener
of the MainActivity
:
private PublisherKit.PublisherListener publisherListener = new PublisherKit.PublisherListener() {
@Override
public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
Log.d(TAG, "onStreamCreated: Publisher Stream Created. Own stream " + stream.getStreamId());
}
@Override
public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
Log.d(TAG, "onStreamDestroyed: Publisher Stream Destroyed. Own stream " + stream.getStreamId());
}
@Override
public void onError(PublisherKit publisherKit, OpentokError opentokError) {
Log.e(TAG, "PublisherKit onError: " + opentokError.getMessage());
}
};
Note that the Publisher class extends the PublisherKit class (also defined by the OpenTok Android SDK). The PublisherKit class is a base class you can use to create advanced publishers, (such as publishers that use custom video capturers or renderers).
This implements the PublisherListener
methods:
onStreamCreated(publisherKit, stream)
— Called when the publisher
starts streaming to the OpenTok session.
onStreamDestroyed(publisherKit, stream)
— Called when the publisher
stops streaming to the OpenTok session.
onError(error)
— Called when the client fails in publishing to
the OpenTok session. An OpentokError
object is passed into the method.
We want clients to be able to subscribe to (or view) other clients’ streams in the session:
Add a subscriber
property to the MainActivity class (after the declaration of the
publisher
property):
private Subscriber subscriber;
The Subscriber class is defined in the OpenTok Android SDK. It defines an object that a client uses to subscribe to (view) a stream published by another client.
Modify the implementation of the onStreamReceived(session, stream)
method (one of the
SessionListener callbacks) to include code to subscribe to other clients’ streams the session:
@Override
public void onStreamReceived(Session session, Stream stream) {
Log.d(TAG, "onStreamReceived: New Stream Received " + stream.getStreamId() + " in session: " + session.getSessionId());
if (subscriber == null) {
subscriber = new Subscriber.Builder(MainActivity.this, stream).build();
subscriber.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
subscriber.setSubscriberListener(subscriberListener);
session.subscribe(subscriber);
subscriberViewContainer.addView(subscriber.getView());
}
}
To log events Add subscriberListener
property just below sessionListener
of the MainActivity
:
SubscriberKit.SubscriberListener subscriberListener = new SubscriberKit.SubscriberListener() {
@Override
public void onConnected(SubscriberKit subscriberKit) {
Log.d(TAG, "onConnected: Subscriber connected. Stream: " + subscriberKit.getStream().getStreamId());
}
@Override
public void onDisconnected(SubscriberKit subscriberKit) {
Log.d(TAG, "onDisconnected: Subscriber disconnected. Stream: " + subscriberKit.getStream().getStreamId());
}
@Override
public void onError(SubscriberKit subscriberKit, OpentokError opentokError) {
Log.e(TAG, "SubscriberKit onError: " + opentokError.getMessage());
}
};
When another client publishes a stream to a session, this method is called, and a Stream object is passed in. The Stream class is defined in the OpenTok Android SDK, and it represents an audio-video stream in the OpenTok session.
The code initializes an instance of the Subscriber class, defined in the OpenTok Android SDK.
The Subscriber.Builder()
constructor takes two parameters:
The Android application context associated with this process.
The Stream object (for the stream you want to view)
The Session.subscribe(subscriber)
method subscribes to the stream that was just received.
subscriberViewContainer.addView(subscriber.getView())
places the new subscribed stream's view on the screen.
Modify the implementation of the onStreamDropped(Session session, Stream stream)
method (another one of the
SessionListener callbacks):
@Override
public void onStreamDropped(Session session, Stream stream) {
Log.i(TAG, "Stream Dropped");
if (subscriber != null) {
subscriber = null;
subscriberViewContainer.removeAllViews();
}
}
subscriberViewContainer.removeAllViews()
removes a subscriber's view once the stream has dropped.
Now that your code is complete, you can run the app in the Android Studio emulator. This will create a simulated publisher video — since the emulator cannot access your webcam, the publisher video will display an animated graphic instead of your camera feed.
To add a second publisher (which will display as a subscriber in your emulator), either run the app a second time in a connected Android device or use the OpenTok Playground to connect to the session in a supported web browser by following the steps below:
Go to OpenTok Playground (must be logged into your Account)
Select the Join existing session tab
Copy the session ID you used in your MainActivity.java file and paste it in Session ID input field
Click Join Session
On the next screen, click Connect, then click Publish Stream
You can adjust the Publisher options (not required), then click Continue to connect and begin publishing and subscribing
At this point you should see the stream being published from your webcam as well as the stream being published by the emulator. Returning to the emulator, you should also see the new publisher displayed on the emulated screen.
Congratulations! You've finished the 'Set up a Basic Android Client' tutorial.
You can continue to play with and adjust the code you've developed here for the client-side of your application, but keep in mind that you'll need to implement the server component of your application before going to production (see Setting up your server below).
You can view a completed version of this sample app in the Basic-Video-Chat-Java folder of the opentok-android-sdk-samples repo on GitHub. This completed version adds code to load the session ID, token, and API key from a web service (instead of using hard-coded values).
When you're finished here, continue building and enhancing your OpenTok application with these helpful resources:
In the tutorial above, we had you hard code your authentication credentials. However, for a production application, the sessionId
and token
values in your code must be generated by your app server and passed to the client. Here are a couple of reasons why you don't want hard coded credentials in your production app:
You can continue testing your application with hard coded values, but when you're ready to set up a server there are a number of ways to do so:
This is probably the fastest way to get a server up and running, but it has limited functionality. Simply click the Heroku button below, at which point you'll be sent to Heroku's website and prompted for your OpenTok API Key and API Secret — you can get these values on your project page in your Video API account. If you don't have a Heroku account, you'll need to sign up (it's free).
Want to explore the code? The button above launches server code from the learning-opentok-php GitHub repo. Visit the repo to review the code as well as additional documentation — you can even fork the repo and make changes before deploying.
Prefer Node.js? Visit the learning-opentok-node repo for the same functionality using Node.js (including a Heroku deploy button).
Once the server is deployed on Heroku, you'll need to add some code to your project to fetch the credentials from the server. Check client implementation in the Basic-Video-Chat-Java / Basic-Video-Chat-Kotlin project.
Option 1 uses REST endpoints for transmitting credentials to the client, but that's only one of many ways to implement a server with OpenTok. If you want a greater level of customization, you can review OpenTok's Server SDK documentation for the server-side language of your choice (available for PHP, Node.js, Java, .NET, Python and Ruby). The documentation goes over the setup process and the various methods you'll need to generate sessions and tokens, as well as other server-side functionality.