This sample app shows how to accomplish basic tasks using the [OpenTok Android SDK] 1. It connects the user with another client so that they can share an OpenTok audio-video chat session. The app uses the OpenTok Android SDK to implement the following:
- Connect to an OpenTok session
- Publish an audio-video stream to the session
- Subscribe to another client's audio-video stream
- Record the session, stop the recording, and view the recording
- Implement text chat
The code for this sample is found the following git branches:
-
step-0 -- This branch shows you how to set up your project to use the OpenTok Android SDK.
-
step-3 -- This branch shows you how to connect to the OpenTok session.
-
step-4 -- This branch shows you how publish a stream to the OpenTok session.
-
step-5 -- This branch shows you how to subscribe to a stream on the OpenTok session.
-
archiving.step-1 -- This branch shows you how to record the session.
-
signaling.step-1 -- This branch shows you how to use the OpenTok signaling API.
-
signaling.step-2 -- This branch shows you how to impliment text chat using the OpenTok signaling API.
-
signaling.step-3 -- This branch adds some UI improvements for the text chat feature.
You will also need to clone the OpenTok PHP Getting Started repo and run its code on a PHP-enabled web server. See the next section for more information.
The step-0 branch includes a basic Android application. Complete the following steps to get it running in Android Studio (and to add the OpenTok iOS SDK):
-
In Android Studio, select the File > Import Project command. Navigate to the root directory of this project, select the build.gradle file, and then click the OK button. The project opens in a new window.
The Java code for the application is the ChatActivity class in the com.tokbox.android.demo.learningopentok package.
-
Download the OpenTok Android SDK.
-
Locate the opentok-android-sdk-2.4.0.jar file in the OpenTok/libs directory of the OpenTok Android SDK, and drag it into the app/libs directory of the Android Studio project explorer.
-
If the app/src/main/jniLibs directory does not exist in the Android Studio project explorer, right-click the app/src/main directory and select the New Resource Directory command, enter jniLibs as the directory name, and then click OK.
-
Locate the armeabi and x86 directories in the OpenTok/libs directory of the OpenTok Android SDK, and drag them into the app/src/main/jniLibs directory of the Android Studio project explorer.
-
Debug the project on a supported device.
For a list of supported devices, see the "Developer and client requirements" on [this page] 1.
Before you can test the application, you need to set up a web service to handle some OpenTok-related API calls. The web service securely creates an OpenTok session.
The Learning OpenTok PHP repo includes code for setting up a web service that handles the following API calls:
-
"/service" -- The Android client calls this endpoint to get an OpenTok session ID, token, and API key.
-
"/start" -- The Android client calls this endpoint to start recording the OpenTok session to an archive.
-
"/stop" -- The Android client calls this endpoint to stop recording the archive.
-
"/view" -- The Android client load this endpoint in a web browser to display the archive recording.
Download the repo and run its code on a PHP-enabled web server,
The HTTP POST request to the /service endpoint returns a response that includes the OpenTok session ID and token.
The web service also creates a token that the client uses to connect to the OpenTok session. The HTTP GET request to the /service endpoint returns a response that includes the OpenTok session ID and token.
You will want to authenticate each user (using your own server-side authentication techniques) before sending an OpenTok token. Otherwise, malicious users could call your web service and use tokens, causing streaming minutes to be charged to your OpenTok developer account. Also, it is a best practice to use an HTTPS URL for the web service that returns an OpenTok token, so that it cannot be intercepted and misused.
The code for this section is added in the step-3 branch of the repo.
First, set the app to use the web service described in the previous two sections:
-
Open the WebServiceCoordinator.java file. This is in the com.tokbox.android.demo.learningopentok package.
-
Edit the
CHAT_SERVER_URL
andSESSION_INFO_ENDPOINT
values to match the URL and end-point of the web service:private static final String CHAT_SERVER_URL = "https://example.com"; private static final String SESSION_INFO_ENDPOINT = CHAT_SERVER_URL + "/session";
You can now test the app in the debugger. On successfully connecting to the session, the app logs "Session Connected" to the debug console.
The onCreate()
method of the main ChatActivity object instantiates a WebServiceCoordinator
object and calls its fetchSessionConnectionData()
method. This method makes an API call to
the /session endpoint of the web service to obtain the OpenTok API key, session ID, and a token
to connect to the session.
Once the session ID is obtained, the WebServiceCoordinator calls the
onSessionConnectionDataReady()
method of the ChatActivity object, passing in the OpenTok API key,
session ID, and token. This method sets the properties to these values and then calls the
initializeSession()
method:
private void initializeSession() {
mSession = new Session(this, mApiKey, mSessionId);
mSession.setSessionListener(this);
mSession.connect(mToken);
}
The Session class is defined by the OpenTok Android SDK. It represents the OpenTok session (which connects users).
This app sets this
as the listener for Session events, defined by the Session.SessionListener
interface. The ChatActivity class implements this interface, and overrides its methods, such as
the onConnected(Session session)
method:
@Override
public void onConnected(Session session) {
Log.i(LOG_TAG, "Session Connected");
}
The Session.SessionListener interface also defined methods for handling other session-related events, which we will look at in the following sections.
Finally, the connect(token)
method of the Session object connects the app 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 code for this section is added in the step-4 branch of the repo.
First, let's test the code in this branch:
-
Find the test.html file in the root of the project. You will use the test.html file to connect to the OpenTok session and view the audio-video stream published by the Android app:
-
Edit the test.html file and set the
sessionCredentialsUrl
variable to match theksessionCredentialsUrl
property used in the iOS app. -
Add the test.html file to a web server. (You cannot run WebRTC videos in web pages loaded from the desktop.)
-
In a browser, load the test.html file from the web server.
-
-
Run the Android app. The Android app publishes an audio-video stream to the session and the the web client app subscribes to the stream.
Now lets look at the Android code. In addition to initializing and connecting to the session, the
onSessionConnectionDataReady()
method calls the initializePublisher()
method:
private void initializePublisher() {
mPublisher = new Publisher(this);
mPublisher.setPublisherListener(this);
mPublisher.setCameraListener(this);
mPublisher.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
BaseVideoRenderer.STYLE_VIDEO_FILL);
mPublisherViewContainer.addView(mPublisher.getView());
}
The Publisher object is defined in the OpenTok Android SDK. A Publisher object acquires an audio and video stream from the device's microphone and camera. These can then be published to the OpenTok session as an audio-video stream.
The Publisher class is a subclass of the PublisherKit class, also defined in the OpenTok Android SDK. The PublisherKit class lets you define custom video drivers (capturers and renderers). The Publisher class uses the device's camera as as the video source, and it implements a pre-built video capturer and renderer.
The ChatActivity object sets itself to implement the PublisherKit.PublisherListener interface. As such it implements method of that interface to handle publisher-related events:
mPublisher.setPublisherListener(this);
The following code sets the Publisher to scale the video to fill the entire area of the renderer, with cropping as needed:
mPublisher.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
BaseVideoRenderer.STYLE_VIDEO_FILL);
The 'getView()` method of the Publisher object returns a View object that displays a view of the camera. This code displays that view in the app:
mPublisherViewContainer.addView(mPublisher.getView());
The mPublisherViewContainer
object is a FrameLayout object set to the publisher_container
view defined in the main layout XML file.
Upon successfully connecting to the OpenTok session (see the previous section), the
onConnected(Session session)
method is called. In this branch of the repo, this method
includes a call to the publish(publisherKit)
method of the Session object:
@Override
public void onConnected(Session session) {
Log.i(LOG_TAG, "Session Connected");
if (mPublisher != null) {
mSession.publish(mPublisher);
}
}
The publish(publisherKit)
method of the Session object publishes an audio-video stream to
the OpenTok session.
Upon successfully publishing the stream, the implementation of the
onStreamCreated(publisherKit, stream)
method (defined in the PublisherKit.PublisherListener
interface) is called:
@Override
public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
Log.i(LOG_TAG, "Publisher Stream Created");
}
If the publisher stops sending its stream to the session, the implementation of the
onStreamDestroyed(publisherKit, stream)
method is called:
@Override
public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
Log.i(LOG_TAG, "Publisher Stream Destroyed");
}
The code for this section is added in the step-5 branch of the repo.
First, let's test the code in this branch:
-
Find the test.html file in the root of the project. You will use the test.html file to connect to the OpenTok session and view the audio-video stream published by the Android app:
-
Edit the test.html file and set the
sessionCredentialsUrl
variable to match theksessionCredentialsUrl
property used in the iOS app. -
Add the test.html file to a web server. (You cannot run WebRTC videos in web pages loaded from the desktop.)
-
In a browser, load the test.html file from the web server.
-
-
Run the Android app. The Android app subscribes to the audio-video stream published by the web page.
The onStreamReceived(Session session, Stream stream)
method (defined in the
Session.SessionListener interface) is called when a new stream is created in the session.
The app implements this method with the following:
@Override
public void onStreamReceived(Session session, Stream stream) {
Log.i(LOG_TAG, "Stream Received");
if (mSubscriber == null) {
mSubscriber = new Subscriber(this, stream);
mSubscriber.setSubscriberListener(this);
mSubscriber.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
BaseVideoRenderer.STYLE_VIDEO_FILL);
mSession.subscribe(mSubscriber);
}
}
The method is passed a Session and Stream object, which are both defined by the OpenTok Android
SDK. The Stream object represents the stream that another client is publishing. Although this app
assumes that only one other client is connecting to the session and publishing, the method checks
to see if the app is already subscribing to a stream (if the mSubscriber
property is null).
If not, the method initializes an Subscriber object (mSubscriber
), used to subscribe to the
stream, passing in the OTStream object to the constructor function. It also sets the ChatActivity
object as the implementor of the SubscriberKit.SubscriberListener interface. This interface defines
methods that handle events related to the subscriber.
The Subscriber class is also defined in the OpenTok Android SDK. It is a subclass of SubscriberKit, which lets you define a custom video renderer. The Subscriber object implements a built-in video renderer.
The following code sets the Subscriber to scale the video to fill the entire area of the renderer, with cropping as needed:
mSubscriber.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
BaseVideoRenderer.STYLE_VIDEO_FILL)
The app then calls the subscribe(SubscriberKit)
method of the Session object to have the app
subscribe to the stream.
When the app starts receiving the subscribed stream, the implementation of the
onConnected(subscriberKit)
method (defined by the SubscriberKit.SubscriberListener interface)
is called:
@Override
public void onConnected(SubscriberKit subscriberKit) {
Log.i(LOG_TAG, "Subscriber Connected");
mSubscriberViewContainer.addView(mSubscriber.getView());
}
It adds view of the subscriber stream (returned by the getView()
method of the Subscriber object)
as a subview of the mSubscriberViewContainer
View object.
If the subscriber's stream is dropped from the session (perhaps the client chose to stop publishing
or to disconnect from the session), the implementation of the
Session.SessionListener.onStreamDropped(session, stream)
method is called:
@Override
public void onStreamDropped(Session session, Stream stream) {
Log.i(LOG_TAG, "Stream Dropped");
if (mSubscriber != null) {
mSubscriber = null;
mSubscriberViewContainer.removeAllViews();
}
}
The code for this section is added in the archiving.step-1 branch of the repo. This code builds upon the code in the step-5 branch of the repo.
The OpenTok archiving API lets you record audio-video streams in a session to MP4 files. You use server-side code to start and stop archive recordings. In the WebServiceCoordinator file, you set the following property to the base URL and endpoints of the web service the app calls to start archive recording, stop recording, and play back the recorded video:
private static final String CHAT_SERVER_URL = BuildConfig.CHAT_SERVER_URL;
private static final String SESSION_INFO_ENDPOINT = CHAT_SERVER_URL + "/session";
private static final String ARCHIVE_START_ENDPOINT = CHAT_SERVER_URL + "/start/:sessionId";
private static final String ARCHIVE_STOP_ENDPOINT = CHAT_SERVER_URL + "/stop/:archiveId";
private static final String ARCHIVE_PLAY_ENDPOINT = CHAT_SERVER_URL + "/view/:archiveId"
When the user selects the Start Archive, Stop Archive, and Play Archive menu items from the action
bar or the options menu, the app calls the startArchive()
and stopArchive()
, and playArchive()
methods. These call web services that call server-side code start and stop archive recordings.
(See Creating a session and defining archive REST API calls.)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
switch(item.getItemId()) {
case R.id.action_settings:
return true;
case R.id.action_start_archive:
startArchive();
return true;
case R.id.action_stop_archive:
stopArchive();
return true;
case R.id.action_play_archive:
playArchive();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Note that the ChatActivity class now implements the Session.ArchiveListener interface. This means that it implements methods for handling archive-related events.
When archive recording starts, the implementation of the
onArchiveStarted(Session session, String archiveId, String archiveName)
method (defined
by the Session.ArchiveListener interface) is called:
@Override
public void onArchiveStarted(Session session, String archiveId, String archiveName) {
mCurrentArchiveId = archiveId;
setStopArchiveEnabled(true);
mArchivingIndicatorView.setVisibility(View.VISIBLE);
}
The method stores the archive ID (identifying the archive) to an mCurrentArchiveId
property.
The method also calls the setStopArchiveEnabled(true)
method, which causes the Stop Recording
menu item to be displayed. And it causes the mArchivingIndicatorView
to be displayed (which
displays an archiving indicator image).
When the user selects the Stop Archive command, the app passes the archive ID along to the web service that stops the archive recording.
When archive recording stops, the implementation of the
onArchiveStopped(Session session, String archiveId)
method (defined
by the Session.ArchiveListener interface) is called:
@Override
public void onArchiveStopped(Session session, String archiveId) {
mPlayableArchiveId = archiveId;
mCurrentArchiveId = null;
setPlayArchiveEnabled(true);
setStartArchiveEnabled(true);
mArchivingIndicatorView.setVisibility(View.INVISIBLE);
}
The method stores the archive ID (identifying the archive) to an mPlayableArchiveId
property
(and sets mCurrentArchiveId
to null
). The method also calls the setPlayArchiveEnabled(false)
method, which disables the Play Archive menu item, and it calls setStartArchiveEnabled(true)
to
enable the Start Archive menu item. And it causes the mArchivingIndicatorView
to be hidden.
When the user clicks the Play Archive button, the playArchive()
method
opens a web page (in the device's web browser) that displays the archive recording, by calling
the archive playback REST API, defined in Creating a session and defining archive REST API calls.
The code for this section is added in the signaling.step-1 branch of the repo. This code builds upon the code in the step-5 branch of the repo.
The OpenTok signaling API lets clients send text messages to other clients connected to the OpenTok session. You can send a signal message to a specific client, or you can send a message to every client connected to the session.
In this branch, the following code is added to the initializeSession()
method:
mSession.setSignalListener(this);
This sets the ChatActivity object as the implementor of the SubscriberKit.SignalListener interface. This interface defines the onSignalReceived(session, type, data, connection)
methods. This method
is called when the client receives a signal from the session:
@Override
public void onSignalReceived(Session session, String type, String data, Connection connection) {
Toast toast = Toast.makeText(this, data, Toast.LENGTH_LONG);
toast.show();
}
This app uses an android.widget.Toast object to display received signals.
In the onConnected(session)
method, the following code sends a signal when the app connects to the
session:
mSession.sendSignal("", "Hello, Signaling!");
This signal is sent to all clients connected to the session. The method has two parameters:
-
type
(String) -- An optional parameter that can be used as a filter for types of signals. -
data
(String) -- The data to send with the signal.
The code for this section is added in the signaling.step-2 branch of the repo.
In this branch, the following code is added to the initializeSession()
method:
mSendButton = (Button)findViewById(R.id.send_button);
mMessageEditText = (EditText)findViewById(R.id.message_edit_text);
// Attach handlers to UI
mSendButton.setOnClickListener(this);
The main layout XML file adds a Button and and EditText element to the main view. This code adds
properties to reference these objects. It also sets the ChatActivity object as the implementor of
the View.OnClickListener interface. This interface defines the onClick(View v)
method.
In the onConnected(Session session)
method (called when the app connects to the OpenTok session)
the following line of code is added in this branch:
enableMessageViews();
The enableMessageViews()
method enables the Message text field and the Send button:
private void enableMessageViews() {
mMessageEditText.setEnabled(true);
mSendButton.setEnabled(true);
}
The onClick(View v)
method is called when the clicks the Send button:
@Override
public void onClick(View v) {
if (v.equals(mSendButton)) {
sendMessage();
}
}
The sendMessage()
method sends the text chat message (defined in the Message text field)
to the OpenTok session:
private void sendMessage() {
disableMessageViews();
mSession.sendSignal(SIGNAL_TYPE_MESSAGE, mMessageEditText.getText().toString());
mMessageEditText.setText("");
enableMessageViews();
}
Note that in this branch, the type
of the signal is set to SIGNAL_TYPE_MESSAGE
(a string defined as "message"). The onSignalReceived()
method checks to see if the
signal received is of this type:
@Override
public void onSignalReceived(Session session, String type, String data, Connection connection) {
switch (type) {
case SIGNAL_TYPE_MESSAGE:
showMessage(data);
break;
}
}
The code for this section is added in the signaling.step-3 branch of the repo.
First, let's test the code in this branch:
-
Find the test.html file in the root of the project. You will use the test.html file to connect to the OpenTok session and view the audio-video stream published by the Android app:
-
Edit the test.html file and set the
sessionCredentialsUrl
variable to match theksessionCredentialsUrl
property used in the iOS app. -
Add the test.html file to a web server. (You cannot run WebRTC videos in web pages loaded from the desktop.)
-
In a browser, load the test.html file from the web server.
-
-
Run the Android app. Enter some chat message in the Message text field, then click the Send button. The web page displays the text message sent by the Android app. You can also send a message from the web page to the Android app.
Instead of using a Toast object to display received signals, the code in this branch uses an
android.widget.ListView object. This lets the app display more than one message at a time.
This branch adds the following code to the onCreate()
method:
mMessageHistoryListView = (ListView)findViewById(R.id.message_history_list_view);
// Attach data source to message history
mMessageHistory = new ChatMessageAdapter(this);
mMessageHistoryListView.setAdapter(mMessageHistory);
This branch adds code that differentiates between signals (text chat messages) sent from the local
Android client and those sent from other clients connected to the session. The
onSignalReceived(session, type, data, connection)
method checks the Connection object for
the received signal with the Connection object returned by mSession.getConnection()
:
@Override
public void onSignalReceived(Session session, String type, String data, Connection connection) {
boolean remote = !connection.equals(mSession.getConnection());
switch (type) {
case SIGNAL_TYPE_MESSAGE:
showMessage(data);
showMessage(data, remote);
break;
}
}
The Connection object of the received signal represents the connection to the session for the client
that sent the signal. This will only match the Connection object returned by
mSession.getConnection()
if the signal was sent by the local client.
The showMessage(messageData, remote)
method has a new second parameter: remote. This is set
to true
if the message was sent another client (and false
if it was sent by the local
Android client):
private void showMessage(String messageData, boolean remote) {
ChatMessage message = ChatMessage.fromData(messageData);
message.setRemote(remote);
mMessageHistory.add(message);
}
The ChatMessage.fromData()
method converts the message data (the data in the received signal)
into a ChatMessage object. The mMessageHistoryListView uses the mMessageHistory object as
the adaptor for the data in the list view. The mMessageHistory property is an
android.widget.ArrayAdapter object. This tutorial focuses on the OpenTok Android SDK API. For more
information on the Android classes used in this text chat implementation, see the docs for the
following:
See the following: