Android SDK tutorial

In the tutorial, you will create a one-on-one video chat app using the basic features of the Android SDK. The app will have the ability to display a list of users currently connected to the server, select which user to call, call, answer and hang up a one-to-one video conversation.

The application to be created in this tutorial will be the same as one-to-one video chat provided in the samples. If you want to try the finished app, download the source code and follow “build” section of this tutorial.

Use ECLWebRTC to start a video chat session between two devices
Use ECLWebRTC to start a video chat session between two devices
Screenshot of video chat
Screenshot of video chat

Preparations

Obtain an ECLWebRTC API Key

For customers who have not completed a developer registration, do so from New Registration of the Community Edition. For those who had registered already, or have just completed the registration, Login to Dashboard and create an application to obtain an API key.

Application settings on the Dashboard are as follows.

Field Explanation of Item What to set it to for this tutorial
Application Description A short description of the application. This is only used when displaying applications on the Dashboard to help identify them.
Must be less than 128 characters.
ECLWebRTC Tutorial Application
Available Domains Domain names of the servers that will be serving the app. Multiple domains can be specified.
Example: foobar.com
localhost
Enabled TURN Allow users to use TURN (Traversal Using Relay around NAT) servers. The TURN server makes communication possible by relaying media and data, even when P2P communication is not possible because the communication has to go through firewalls. The TURN server closest to the user will be selected automatically. ON
Enable SFU Allow users to use SFU (Selective Forwarding Unit) server. SFU’s allow sending/receiving video and voice through a central media relay server, avoiding many disadvantages of using multiple P2P connections. Please refer to the SFU documentation for details. ON
Enabled listAllPeers API Allow users to use the listAllPeers API, which gets a list of the PeerIDs of all connected users. Refer to API Reference for details. ON
Enabled API Key authentication Enables authentication function to prevent unauthorized use. Refer to this GitHub repository for details on how to use authentication. OFF

Preparing the development environment

We have verified this tutorial with the following environment.

  • Android Studio 2.3.3
  • Verified device
    • Nexus 6
  • OS Version
    • Android 7.1
  • Development language
    • Java
  • Authentication

Creating the project

Download the Android Studio project used for this tutorial by cloning the repository below.

Add the SDK to the project

  1. Download the SDK on GitHub.
  2. Unzip the file and move skyway.aar directly into the app/libs directory.
  3. Open the development project with an IDE (e.g. Android Studio), and complete build tool (e.g. Gradle) settings.
After the SDK is added to the project
After the SDK is added to the project

Here is an overview of each of the files contained in the project.

  • app/src/main/java/com.ntt.ecl.webrtc.sample_p2p_videochat/MainActivity
    • Contains the main code for this app. You will be working with this file exclusively for this tutorial.
  • app/src/main/java/com.ntt.ecl.webrtc.sample_p2p_videochat/PeerListDialogFragment
    • Generates the ListDialog to display a list of PeerIDs.
    • The completed version is included in the repository and will not be referenced in this tutorial.
  • res/**
    • For resources and layout, the completed version are included in the repository and will not be referenced in this tutorial.

Import the classes

Java

//
// Import for SkyWay
//
import io.skyway.Peer.Browser.Canvas;
import io.skyway.Peer.Browser.MediaConstraints;
import io.skyway.Peer.Browser.MediaStream;
import io.skyway.Peer.Browser.Navigator;
import io.skyway.Peer.CallOption;
import io.skyway.Peer.MediaConnection;
import io.skyway.Peer.OnCallback;
import io.skyway.Peer.Peer;
import io.skyway.Peer.PeerError;
import io.skyway.Peer.PeerOption;

Add features and permissions to manifest file

Enable the following features and permissions in the manifest file.

Java

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

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

Build

Connect the device and click build. We expect some errors during here but the preparation is complete if the build passes.

Connect to the ECLWebRTC server

Defining variables

Add constants to MainActivity that are used in the program. Set API_KEY, to the API key that was generated on the Dashboard earlier. For DOMAIN, specify one of the Available Domains you set in the Dashboard (e.g. “localhost”).

Java

//
// Set your APIkey and Domain
//
private static final String API_KEY = "apikey";
private static final String DOMAIN = "domain";

Declare variables used in the program.

  • _peer : Peer Object
  • _localStream : Own MediaStream Object
  • _remoteStream : Opponent MediaStream Object
  • _mediaConnection : MediaConnection Object

Java

//
// declaration
//
private Peer			_peer;
private MediaStream		_localStream;
private MediaStream		_remoteStream;
private MediaConnection	_mediaConnection;

private String			_strOwnId;
private boolean			_bConnected;

private Handler			_handler;

UI

At the beginning of onCreate method, hide the title of the main window and create a Handler for UI thread processing.

Java

//
// Windows title hidden
//
Window wnd = getWindow();
wnd.addFlags(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);

//
// Set UI handler
//
_handler = new Handler(Looper.getMainLooper());
final Activity activity = this;

Create Peer Object

Create a Peer object in the onCreate method.

When creating the Peer object, specify the API key, domain name and debug level by using the PeerOption class.

Java

//
// Initialize Peer
//
PeerOption option = new PeerOption();
option.key = API_KEY;
option.domain = DOMAIN;
option.debug = Peer.DebugLevelEnum.ALL_LOGS;
_peer = new Peer(this, option);

Refer to API Reference for other options that can be specified in Peer object.

Handling connection success, errors and disconnections

Add the appropriate event callbacks to handle each event.

Open Event

Emitted when the connection to the ECLWebRTC server is ready to use. You should wait for this event before calling any other methods on the Peer object. An ID which uniquely identifies the client, known as the PeerID, can be obtained from the callback function. The PeerID can be specified or randomly generated on the server if one isn’t specified. This code displays the PeerID after the connection to the server is established.

Java

//
// Set Peer event callbacks
//

// OPEN
_peer.on(Peer.PeerEventEnum.OPEN, new OnCallback() {
  @Override
  public void onCallback(Object object) {

    // Show my ID
    _strOwnId = (String) object;
    TextView tvOwnId = (TextView) findViewById(R.id.tvOwnId);
    tvOwnId.setText(_strOwnId);

    }
});

Obtaining video/audio streams from the camera/microphone

In the callback of the OPEN event, add code to get media streams from the camera/microphone.

Requesting access to the camera/microphone 1

Check if the app has permission to access the camera and microphone and request permission if the app doesn’t. Once you have access, call startLocalStream(), which we will define later, to get a MediaStream.

Java

// Request permissions
if (ContextCompat.checkSelfPermission(activity,
    Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(activity,
    Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
  ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO},0);
}
else {

  // Get a local MediaStream & show it
  startLocalStream();
}

Requesting access to the camera/microphone 2

If permissions were not already granted and had to be obtained with requestPermissions(), call startLocalStream() in the onRequestPermissionResult() callback when permission is granted.

Java

//
// onRequestPermissionResult
//
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case 0: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startLocalStream();
            }
    else {
                Toast.makeText(this,"Failed to access the camera and microphone.\nclick allow when asked for permission.", Toast.LENGTH_LONG).show();
            }
            break;
        }
    }
}

Options

Options for getting video/audio can be set by MediaConstraints class. Here is a simple summary of some of the options.

  • maxWidth: The upper limit in pixels of the horizontal size of the video
  • maxHeight: The upper limit in pixels of the vertical size of the video
  • cameraPosition: Select which device camera to use(defaults to front camera)
    • set to FRONT to use the front camera
    • set to BACK to use the back camera

Refer to API Reference for other options.

Java

//
// Get a local MediaStream & show it
//
void startLocalStream() {
  MediaConstraints constraints = new MediaConstraints();
  constraints.maxWidth = 960;
  constraints.maxHeight = 540;
  constraints.cameraPosition = MediaConstraints.CameraPositionEnum.FRONT;

  // continued below ...
}

Playing the MediaStream

Initialize the Navigator class, specify the constraints for the getUserMedia method and execute it to obtain your local MediaStream (local stream). Use the addVideoRenderer method to allocate a video renderer (Canvas object) for the MediaStream object you got from getUserMedia.

Java

//
// Get a local MediaStream & show it
//
void startLocalStream() {

  // ... continued from above

  Navigator.initialize(_peer);
  _localStream = Navigator.getUserMedia(constraints);
  Canvas canvas = (Canvas) findViewById(R.id.svLocalView);
  _localStream.addVideoRenderer(canvas,0);
}       

Error Event

Emitted when any error occurs. Makes it possible to determine the cause of the error and handle it appropriately (e.g. displaying the message in the log).

Java

// ERROR
_peer.on(Peer.PeerEventEnum.ERROR, new OnCallback() {
  @Override
  public void onCallback(Object object) {
    PeerError error = (PeerError) object;
    Log.d(TAG, "[On/Error]" + error);
  }
});

Close Event

Emitted when the connection with another Peer is broken. We don’t handle this event in this tutorial.

Java

// CLOSE
_peer.on(Peer.PeerEventEnum.CLOSE, new OnCallback()	{
  @Override
  public void onCallback(Object object) {
    Log.d(TAG, "[On/Close]");
  }
});

Disconnected Event

Emitted when the connection with the ECLWebRTC server is broken. We don’t handle this event in this tutorial.

Java

// DISCONNECTED
_peer.on(Peer.PeerEventEnum.DISCONNECTED, new OnCallback() {
  @Override
  public void onCallback(Object object) {
    Log.d(TAG, "[On/Disconnected]");
  }
});

Calling/Disconnecting/Receiving Process

Calling

Select the PeerID of the partner you want to call and call him/her.

Obtain the PeerID of your partner

Tap the Make Call button. If you aren’t already connected to a peer, use the showPeerIDs method to obtain a list of all other user’s PeerIDs.

Java

// Set GUI event listner for Button (make/hang up a call)
Button btnAction = (Button) findViewById(R.id.btnAction);
btnAction.setEnabled(true);
btnAction.setOnClickListener(new View.OnClickListener()	{
  @Override
  public void onClick(View v)	{
    v.setEnabled(false);

    if (!_bConnected) {

      // Select remote peer & make a call
      showPeerIDs();
    }
    else {

    }

    v.setEnabled(true);
  }
});

Connecting to the partner

In the showPeerIDs method, use the listAllPeers() method to obtain a list of all other user’s PeerIDs. Delete your own ID from the obtained list and show the list with the PeerListDialogFragment.

Java

//
// Listing all peers
//
void showPeerIDs() {
  if ((null == _peer) || (null == _strOwnId) || (0 == _strOwnId.length())) {
    Toast.makeText(this, "Your PeerID is null or invalid.", Toast.LENGTH_SHORT).show();
    return;
  }

  // Get all IDs connected to the server
  final Context fContext = this;
  _peer.listAllPeers(new OnCallback() {
    @Override
    public void onCallback(Object object) {
      if (!(object instanceof JSONArray)) {
        return;
      }

      JSONArray peers = (JSONArray) object;
      ArrayList<String> _listPeerIds = new ArrayList<>();
      String peerId;

      // Exclude my own ID
      for (int i = 0; peers.length() > i; i++) {
        try {
          peerId = peers.getString(i);
          if (!_strOwnId.equals(peerId)) {
            _listPeerIds.add(peerId);
          }
        } catch(Exception e){
          e.printStackTrace();
        }
      }

      // Show IDs using DialogFragment
      if (0 < _listPeerIds.size()) {
        FragmentManager mgr = getFragmentManager();
        PeerListDialogFragment dialog = new PeerListDialogFragment();
        dialog.setListener(
          new PeerListDialogFragment.PeerListDialogFragmentListener() {
            @Override
            public void onItemClick(final String item) {
              _handler.post(new Runnable() {
                @Override
                public void run() {
                  onPeerSelected(item);
                }
              });
            }
          });
        dialog.setItems(_listPeerIds);
        dialog.show(mgr, "peerlist");
      }
      else{
        Toast.makeText(fContext, "PeerID list (other than your ID) is empty.", Toast.LENGTH_SHORT).show();
      }
    }
  });

}

Calling

If a PeerID is selected in the PeerListDialogFragment, the onPeerSelected() method will be called. Call the call() method, passing the PeerID of the partner and your own localStream as arguments, to connect to the specified PeerID. After calling, set up necessary event callbacks. Details of setMediaCallbacks will be explained later.

Java

//
// Create a MediaConnection
//
void onPeerSelected(String strPeerId) {
  if (null == _peer) {
    return;
  }

  if (null != _mediaConnection) {
    _mediaConnection.close();
  }

  CallOption option = new CallOption();
  _mediaConnection = _peer.call(strPeerId, _localStream, option);

  if (null != _mediaConnection) {
    setMediaCallbacks();
    _bConnected = true;
  }

  updateActionButtonTitle();
}

Disconnecting

Disconnect the connection with the partner.

Terminate MediaConnection

If the connection is alive when the actionButton (Make Call button) is tapped, use the close() method of the MediaConnection object to disconnect the specified MediaConnection and clean up using closeRemoteStream, which will be explained later.

Java

// Set GUI event listner for Button (make/hang up a call)
Button btnAction = (Button) findViewById(R.id.btnAction);
btnAction.setEnabled(true);
btnAction.setOnClickListener(new View.OnClickListener()	{
  @Override
  public void onClick(View v)	{
    v.setEnabled(false);

    if (!_bConnected) {

      // 省略

    }
    else {

      // Hang up a call
      closeRemoteStream();
      _mediaConnection.close();

    }

    v.setEnabled(true);
  }
});

Close MediaStream

When the close() method of the MediaConnection object is called, use the removeVideoRenderer() method to remove the video renderer assigned to the MediaStream.

Java

//
// Close a remote MediaStream
//
void closeRemoteStream(){
  if (null == _remoteStream) {
    return;
  }

  Canvas canvas = (Canvas) findViewById(R.id.svRemoteView);
  _remoteStream.removeVideoRenderer(canvas,0);
  _remoteStream.close();
}

Receiving calls

Answer when a partner requests a connection. When someone is trying to establish a media connection with you, Peer.PeerEventEnum.CALL will fire. Call the answer() method on the MediaConnection object you get from the callback to accept the connection. If you set your own _localStream, you will be able to send video and voice to your partner. Use setMediaCallbacks to set event handlers like you did after call. Details will be explained later.

Java

// CALL (Incoming call)
_peer.on(Peer.PeerEventEnum.CALL, new OnCallback() {
  @Override
  public void onCallback(Object object) {
    if (!(object instanceof MediaConnection)) {
      return;
    }

    _mediaConnection = (MediaConnection) object;
    setMediaCallbacks();
    _mediaConnection.answer(_localStream);

    _bConnected = true;
    updateActionButtonTitle();
  }
});

Event handlers on the MediaConnection object

Here we set up the event handlers on the MediaConnection object we got above. MediaConnection.MediaEventEnum.STREAM will fire when a MediaStream is received from your partner.

In the callback, update the connection status on the UI and use the addVideoRenderer method on the MediaStream object of the partner, to play the received stream.

Java

//
// Set callbacks for MediaConnection.MediaEvents
//
void setMediaCallbacks() {

  _mediaConnection.on(MediaConnection.MediaEventEnum.STREAM, new OnCallback() {
    @Override
    public void onCallback(Object object) {
      _remoteStream = (MediaStream) object;
      Canvas canvas = (Canvas) findViewById(R.id.svRemoteView);
      _remoteStream.addVideoRenderer(canvas,0);
    }
  });

  // continues below ...
}

MediaConnection.MediaEventEnum.CLOSE will fire if the partner disconnects. The necessary steps to clean up will be run in the callback. Details will be explained later.

Java

//
// Set callbacks for MediaConnection.MediaEvents
//
void setMediaCallbacks() {

  // ... continued from above

  _mediaConnection.on(MediaConnection.MediaEventEnum.CLOSE, new OnCallback()	{
    @Override
    public void onCallback(Object object) {
      closeRemoteStream();
      _bConnected = false;
      updateActionButtonTitle();
    }
  });

  // continues below ...
}

MediaConnection.MediaEventEnum.ERROR will fire when any error occurs. Makes it possible to determine the cause of the error and handle it appropriately (e.g. displaying the message in the log)

Java

//
// Set callbacks for MediaConnection.MediaEvents
//
void setMediaCallbacks() {

  // ... continued from above

  _mediaConnection.on(MediaConnection.MediaEventEnum.ERROR, new OnCallback()	{
    @Override
    public void onCallback(Object object) {
      PeerError error = (PeerError) object;
      Log.d(TAG, "[On/MediaError]" + error);
    }
  });

}

Activity Lifecycle Events

Override Lifecycle methods

Override and add code to handle lifecycle changes. In the onDestroy method, call destroyPeer() to destroy the Peer object. Details will be explained later.

Java

//
// Activity Lifecycle
//
@Override
protected void onStart() {
  super.onStart();

  // Disable Sleep and Screen Lock
  Window wnd = getWindow();
  wnd.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
  wnd.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

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

  // Set volume control stream type to WebRTC audio.
  setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}

@Override
protected void onPause() {
  // Set default volume control stream type.
  setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
  super.onPause();
}

@Override
protected void onStop()	{
  // Enable Sleep and Screen Lock
  Window wnd = getWindow();
  wnd.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  wnd.clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
  super.onStop();
}

@Override
protected void onDestroy() {
  destroyPeer();
  super.onDestroy();
}

Cleaning up when destroying the Peer

Add necessary processes at the timing when Activity is destroyed. An outline of the processes executed here is as follows.

  • Close remote/local mediaStream
  • Release callback events set on the MediaConnection object.(unsetMediaCallbacks)
  • Reset the Navigator object
  • Release callback events set on the Peer object.(unsetPeerCallback)
  • Disconnect from the signaling server
  • Destroy Peer objects

Details of unsetMediaCallbacks and unsetPeerCallback will be explained later.

Java

//
// Clean up objects
//
private void destroyPeer() {
  closeRemoteStream();

  if (null != _localStream) {
    Canvas canvas = (Canvas) findViewById(R.id.svLocalView);
    _localStream.removeVideoRenderer(canvas,0);
    _localStream.close();
  }

  if (null != _mediaConnection)	{
    if (_mediaConnection.isOpen()) {
      _mediaConnection.close();
    }
    unsetMediaCallbacks();
  }

  Navigator.terminate();

  if (null != _peer) {
    unsetPeerCallback(_peer);
    if (!_peer.isDisconnected()) {
      _peer.disconnect();
    }

    if (!_peer.isDestroyed()) {
      _peer.destroy();
    }

    _peer = null;
  }
}

Cleaning up the event handlers

Unset the event handlers on the MediaConnection and Peer objects.

Java

//
// Unset callbacks for PeerEvents
//
void unsetPeerCallback(Peer peer) {
  if(null == _peer){
    return;
  }

  peer.on(Peer.PeerEventEnum.OPEN, null);
  peer.on(Peer.PeerEventEnum.CONNECTION, null);
  peer.on(Peer.PeerEventEnum.CALL, null);
  peer.on(Peer.PeerEventEnum.CLOSE, null);
  peer.on(Peer.PeerEventEnum.DISCONNECTED, null);
  peer.on(Peer.PeerEventEnum.ERROR, null);
}

//
// Unset callbacks for MediaConnection.MediaEvents
//
void unsetMediaCallbacks() {
  if(null == _mediaConnection){
    return;
  }

  _mediaConnection.on(MediaConnection.MediaEventEnum.STREAM, null);
  _mediaConnection.on(MediaConnection.MediaEventEnum.CLOSE, null);
  _mediaConnection.on(MediaConnection.MediaEventEnum.ERROR, null);
}

Setup UI

Add methods related to the UI. As actionButton is used in toggle mode, change the label depending on the connection status by implementing updateActionButtonTitle().

Java

//
// Update actionButton title
//
void updateActionButtonTitle() {
  _handler.post(new Runnable() {
    @Override
    public void run() {
      Button btnAction = (Button) findViewById(R.id.btnAction);
      if (null != btnAction) {
        if (false == _bConnected) {
          btnAction.setText("Make Call");
        } else {
          btnAction.setText("Hang up");
        }
      }
    }
  });
}

Switching Cameras

Finally, we’re going to add a way to switch between device cameras. Use the switchCamera() method on a MediaStream, to switch between the FRONT and BACK cameras.

Java

//
// Action for switchCameraButton
//
Button switchCameraAction = (Button)findViewById(R.id.switchCameraAction);
switchCameraAction.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v)	{
    if(null != _localStream){
      Boolean result = _localStream.switchCamera();
      if(true == result)	{
        //Success
      }
      else {
        //Failed
      }
    }

  }
});

Trying it out

Build and deploy to two devices. Call a PeerID obtained using listAllPeers. It should connect and a video chat between the two devices should start. If you only have one Android device, you can try connecting to a JavaScript web-app that uses the same API key.