In the field of smartphones, video calling feature is yet another breakthrough and it turned out to be a pretty cool feature. Getting to see the person live you are talking to is really a great step towards narrowing the communication gap among people. Today, we have a large number of apps in play store which provide the video call utility such as Imo, Google Duo, WhatsApp, Messenger and the list goes on. So here I have a detailed Video Call Android Tutorial for you.
If your are reading this, that means you wish to implement this cool utility in your android application. Well, if you just think for a while as a developer of creating a complete workflow for a mobile video call facility, you would begin with something like this. First, you need to send the video feed of the phone’s camera to the server and that too in realtime. Now that requires you select a proper protocol to do so. Then receiving the data from the server on the other phone. After thinking all of this, you end up having no clue of how to get started with this exactly.
Well, what if I tell you that you could implement this utility in your app without giving a damn about the data transmission, server and anything else for that matter. Yes, with a  SDK called Sinch, it is very much possible. All you need to do is import the SDK in your project and  you’ll be good to go.
Now that we are done with the overview, we shall directly start working on the project for our Video Call Android Tutorial.
Table of Contents
Video Call Android Tutorial
Registering your App on Sinch
Go to  the Sinch website and  scroll down the homepage to see the screen below.
Select video on the above screen and you shall see the screen below
Click on GET STARTED button to move ahead and you shall see this register page down below.
Quickly fill in details and register to see the welcome page as shown below.
Here, you need to verify your phone number and it shall grant you a credit of 2$ for free. So go ahead and quickly verify your mobile number.
Once the verification is done, you shall see a welcome page saying you’re all set as shown below.
Hit  the Get Started button to go further and you’ll see this page asking for your app’s name and description.
Fill in the required details and hit Create and you shall see a page asking you to choose from available services as shown below.
Select Video Calling in the above screen and you shall be asked to select the development platform in the screen below.
Choose Android in the above screen and you’ll  be shown something like below:-
- In the above screen you can see the three unique keys have been assigned to your app. Copy them somewhere as you’re gonna need these in this app.
- Hit the Download Android SDK button and wait for it to be downloaded.
Creating a new project
- Begin with creating a new project in Android Studio.
Importing Sinch SDK into your project
- Unzip the downloaded file and go to the following directory-:
- sinch-android-rtc-3.8.0-VIDEO-20151119.112353-6\sinch-android-rtc-3.8.0-VIDEO-SNAPSHOT\libs
- You shall see something like below
- Now copy the jar file from the above directory.
- Go to this  directory  YourAppName/app/libs  in your project and paste the jar file in the libs folder.
- RightClick on the jar file and click on Add as Library and wait for the Gradle to be synced.
- Now, go to the folder from where you copied the jar file and this time copy the two folders by the name armeabia-v7a and x86.
- Next, in the following directory YourAppName/app/src/main of your project, create a new directory  by the name jniLibs in the main folder.
- Paste the two folders that your previously copied in the jniLibs folder
And you have successfully imported the Sinch SDK in your project and you’re good to code.
Understanding the workflow of this app:-
- In an activity called LoginActivity, a user enters a unique username and logs in.
- In the next Activity called PlaceCallActivity, the user enters the name of the another  user to whom the call is to be placed. Mind it that the other user must remain logged in order to receive the call.
- If the user on the other end is found to be logged in. He/She shall recieve the video call.
- Once the call is answered, the users on both the ends would be sharing their camera feeds.
Coding the App
Before coding the activities, there are certain classes used by the all the activities.
They are as follows:
- SinchService.java
- BaseActivity.java
- AudioPlayer.java
Begin with SinchService.java. Create new class named SinchService.java and add the following code to it:-
This class is responsible to create a session for your app on the server each time you try to use it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
package techheromanish.example.com.videochatapp; import com.sinch.android.rtc.AudioController; import com.sinch.android.rtc.ClientRegistration; import com.sinch.android.rtc.Sinch; import com.sinch.android.rtc.SinchClient; import com.sinch.android.rtc.SinchClientListener; import com.sinch.android.rtc.SinchError; import com.sinch.android.rtc.video.VideoController; import com.sinch.android.rtc.calling.Call; import com.sinch.android.rtc.calling.CallClient; import com.sinch.android.rtc.calling.CallClientListener; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; public class SinchService extends Service { private static final String APP_KEY = "de6fd769-0a6c-4b08-8660-3473b3f0ea07"; private static final String APP_SECRET = "bqIItwIyNUySmhzH9Njfqg=="; private static final String ENVIRONMENT = "sandbox.sinch.com"; public static final String CALL_ID = "CALL_ID"; static final String TAG = SinchService.class.getSimpleName(); private SinchServiceInterface mSinchServiceInterface = new SinchServiceInterface(); private SinchClient mSinchClient; private String mUserId; private StartFailedListener mListener; @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { if (mSinchClient != null && mSinchClient.isStarted()) { mSinchClient.terminate(); } super.onDestroy(); } private void start(String userName) { if (mSinchClient == null) { mUserId = userName; mSinchClient = Sinch.getSinchClientBuilder().context(getApplicationContext()).userId(userName) .applicationKey(APP_KEY) .applicationSecret(APP_SECRET) .environmentHost(ENVIRONMENT).build(); mSinchClient.setSupportCalling(true); mSinchClient.startListeningOnActiveConnection(); mSinchClient.addSinchClientListener(new MySinchClientListener()); mSinchClient.getCallClient().addCallClientListener(new SinchCallClientListener()); mSinchClient.start(); } } private void stop() { if (mSinchClient != null) { mSinchClient.terminate(); mSinchClient = null; } } private boolean isStarted() { return (mSinchClient != null && mSinchClient.isStarted()); } @Override public IBinder onBind(Intent intent) { return mSinchServiceInterface; } public class SinchServiceInterface extends Binder { public Call callUserVideo(String userId) { return mSinchClient.getCallClient().callUserVideo(userId); } public String getUserName() { return mUserId; } public boolean isStarted() { return SinchService.this.isStarted(); } public void startClient(String userName) { start(userName); } public void stopClient() { stop(); } public void setStartListener(StartFailedListener listener) { mListener = listener; } public Call getCall(String callId) { return mSinchClient.getCallClient().getCall(callId); } public VideoController getVideoController() { if (!isStarted()) { return null; } return mSinchClient.getVideoController(); } public AudioController getAudioController() { if (!isStarted()) { return null; } return mSinchClient.getAudioController(); } } public interface StartFailedListener { void onStartFailed(SinchError error); void onStarted(); } private class MySinchClientListener implements SinchClientListener { @Override public void onClientFailed(SinchClient client, SinchError error) { if (mListener != null) { mListener.onStartFailed(error); } mSinchClient.terminate(); mSinchClient = null; } @Override public void onClientStarted(SinchClient client) { Log.d(TAG, "SinchClient started"); if (mListener != null) { mListener.onStarted(); } } @Override public void onClientStopped(SinchClient client) { Log.d(TAG, "SinchClient stopped"); } @Override public void onLogMessage(int level, String area, String message) { switch (level) { case Log.DEBUG: Log.d(area, message); break; case Log.ERROR: Log.e(area, message); break; case Log.INFO: Log.i(area, message); break; case Log.VERBOSE: Log.v(area, message); break; case Log.WARN: Log.w(area, message); break; } } @Override public void onRegistrationCredentialsRequired(SinchClient client, ClientRegistration clientRegistration) { } } private class SinchCallClientListener implements CallClientListener { @Override public void onIncomingCall(CallClient callClient, Call call) { Log.d(TAG, "Incoming call"); Intent intent = new Intent(SinchService.this, IncomingCallScreenActivity.class); intent.putExtra(CALL_ID, call.getCallId()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); SinchService.this.startActivity(intent); } } } |
Hop over to the top  of this class and you’ll notice the following three constants:-
1 2 3 4 5 |
private static final String APP_KEY = "de6fd769-0a6c-4b08-8660-3473b3f0ea07"; private static final String APP_SECRET = "bqIItwIyNUySmhzH9Njfqg=="; private static final String ENVIRONMENT = "sandbox.sinch.com"; |
Replace the values of these constants with the ones that you got after the registration of this app on the Sinch website
- Next, create a class named BaseActivity.java and add the following code to it:-
- This class is extended by all the activities which enables them to bind the SinchService to themselves.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package techheromanish.example.com.videochatapp; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.support.v7.app.AppCompatActivity; public abstract class BaseActivity extends AppCompatActivity implements ServiceConnection { private SinchService.SinchServiceInterface mSinchServiceInterface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getApplicationContext().bindService(new Intent(this, SinchService.class), this, BIND_AUTO_CREATE); } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { if (SinchService.class.getName().equals(componentName.getClassName())) { mSinchServiceInterface = (SinchService.SinchServiceInterface) iBinder; onServiceConnected(); } } @Override public void onServiceDisconnected(ComponentName componentName) { if (SinchService.class.getName().equals(componentName.getClassName())) { mSinchServiceInterface = null; onServiceDisconnected(); } } protected void onServiceConnected() { // for subclasses } protected void onServiceDisconnected() { // for subclasses } protected SinchService.SinchServiceInterface getSinchServiceInterface() { return mSinchServiceInterface; } } |
Next on the list is AudioPlayer.java. So, create  a new class named AudioPlayer.java and add the following code:-
This class manages the audios to be played to notify the user of the incoming video call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
package techheromanish.example.com.videochatapp; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.media.MediaPlayer; import android.net.Uri; import android.util.Log; import java.io.FileInputStream; import java.io.IOException; public class AudioPlayer { static final String LOG_TAG = AudioPlayer.class.getSimpleName(); private Context mContext; private MediaPlayer mPlayer; private AudioTrack mProgressTone; private final static int SAMPLE_RATE = 16000; public AudioPlayer(Context context) { this.mContext = context.getApplicationContext(); } public void playRingtone() { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); // Honour silent mode switch (audioManager.getRingerMode()) { case AudioManager.RINGER_MODE_NORMAL: mPlayer = new MediaPlayer(); mPlayer.setAudioStreamType(AudioManager.STREAM_RING); try { mPlayer.setDataSource(mContext, Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.phone_loud1)); mPlayer.prepare(); } catch (IOException e) { Log.e(LOG_TAG, "Could not setup media player for ringtone"); mPlayer = null; return; } mPlayer.setLooping(true); mPlayer.start(); break; } } public void stopRingtone() { if (mPlayer != null) { mPlayer.stop(); mPlayer.release(); mPlayer = null; } } public void playProgressTone() { stopProgressTone(); try { mProgressTone = createProgressTone(mContext); mProgressTone.play(); } catch (Exception e) { Log.e(LOG_TAG, "Could not play progress tone", e); } } public void stopProgressTone() { if (mProgressTone != null) { mProgressTone.stop(); mProgressTone.release(); mProgressTone = null; } } private static AudioTrack createProgressTone(Context context) throws IOException { AssetFileDescriptor fd = context.getResources().openRawResourceFd(R.raw.progress_tone); int length = (int) fd.getLength(); AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, length, AudioTrack.MODE_STATIC); byte[] data = new byte[length]; readFileToBytes(fd, data); audioTrack.write(data, 0, data.length); audioTrack.setLoopPoints(0, data.length / 2, 30); return audioTrack; } private static void readFileToBytes(AssetFileDescriptor fd, byte[] data) throws IOException { FileInputStream inputStream = fd.createInputStream(); int bytesRead = 0; while (bytesRead < data.length) { int res = inputStream.read(data, bytesRead, (data.length - bytesRead)); if (res == -1) { break; } bytesRead += res; } } } |
Coding the screens
The Login Screen
The first page of this app would be the Login Screen. So go ahead and create a new layout resource file by the name
login.xml and add the following code to it:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="0dip" tools:context=".LoginActivity"> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:orientation="vertical" android:paddingLeft="40dp" android:paddingRight="40dp"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="40dp" android:text="Enter your name" android:textAllCaps="true" android:textSize="20sp" /> <EditText android:id="@+id/loginName" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:inputType="textPersonName" android:padding="10dp" android:textSize="32sp" > <requestFocus /> </EditText> </LinearLayout> <Button android:id="@+id/loginButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_gravity="center_horizontal" android:layout_marginBottom="40dp" android:minHeight="56dp" android:minWidth="132dp" android:text="Login" android:textSize="22sp" /> </RelativeLayout> |
The above code would generate the following layout
Next, let’s set the backend of this screen. To do so, create a class named LoginActivity.java and add the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
package techheromanish.example.com.videochatapp; import com.sinch.android.rtc.SinchError; import android.Manifest; import android.app.ProgressDialog; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.annotation.RequiresApi; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class LoginActivity extends BaseActivity implements SinchService.StartFailedListener { private Button mLoginButton; private EditText mLoginName; private ProgressDialog mSpinner; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login); //asking for permissions here if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.READ_PHONE_STATE},100); } //initializing UI elements mLoginName = (EditText) findViewById(R.id.loginName); mLoginButton = (Button) findViewById(R.id.loginButton); mLoginButton.setEnabled(false); mLoginButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { loginClicked(); } }); } //this method is invoked when the connection is established with the SinchService @Override protected void onServiceConnected() { mLoginButton.setEnabled(true); getSinchServiceInterface().setStartListener(this); } @Override protected void onPause() { if (mSpinner != null) { mSpinner.dismiss(); } super.onPause(); } @Override public void onStartFailed(SinchError error) { Toast.makeText(this, error.toString(), Toast.LENGTH_LONG).show(); if (mSpinner != null) { mSpinner.dismiss(); } } //Invoked when just after the service is connected with Sinch @Override public void onStarted() { openPlaceCallActivity(); } //Login is Clicked to manually to connect to the Sinch Service private void loginClicked() { String userName = mLoginName.getText().toString(); if (userName.isEmpty()) { Toast.makeText(this, "Please enter a name", Toast.LENGTH_LONG).show(); return; } if (!getSinchServiceInterface().isStarted()) { getSinchServiceInterface().startClient(userName); showSpinner(); } else { openPlaceCallActivity(); } } //Once the connection is made to the Sinch Service, It takes you to the next activity where you enter the name of the user to whom the call is to be placed private void openPlaceCallActivity() { Intent mainActivity = new Intent(this, PlaceCallActivity.class); startActivity(mainActivity); } private void showSpinner() { mSpinner = new ProgressDialog(this); mSpinner.setTitle("Logging in"); mSpinner.setMessage("Please wait..."); mSpinner.show(); } } |
The above code has been commented to help you understand the codeflow.
The PlaceCall Screen
Next is the PlaceCallActivity where you enter the name of the user to whom the call is to be placed.
To create the UI for the screen, create a new layout resource file by the name main.xml and add the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="0dip"> <LinearLayout android:id="@+id/numberInputLayout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:orientation="vertical" android:paddingLeft="40dp" android:paddingRight="40dp"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="40dp" android:text="Enter recipient name" android:textAllCaps="true" android:textSize="20sp" /> <EditText android:id="@+id/callName" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:inputType="textNoSuggestions" android:padding="10dp" android:textSize="32sp"> <requestFocus /> </EditText> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/numberInputLayout" android:layout_centerHorizontal="true" android:paddingBottom="24dp"> <TextView android:id="@+id/infoText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Logged in as: " android:textAllCaps="true" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/loggedInName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Someone" android:textAllCaps="true" android:textAppearance="?android:attr/textAppearanceSmall" android:textStyle="bold" /> </LinearLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true"> <Button android:id="@+id/callButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:minHeight="56dp" android:minWidth="132dp" android:text="Call" android:textSize="20sp" /> <Button android:id="@+id/stopButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/callButton" android:layout_centerHorizontal="true" android:layout_marginBottom="10dp" android:background="@android:color/transparent" android:text="Stop service" android:textAllCaps="true" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@color/sinch_purple" /> </RelativeLayout> </RelativeLayout> |
The above code shall generate the following layout
Now that the UI is set up, we need to configure the Java part for this activity. To do so, create a class named PlaceActivity.java and add the following code:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
package techheromanish.example.com.videochatapp; import com.sinch.android.rtc.calling.Call; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class PlaceCallActivity extends BaseActivity { private Button mCallButton; private EditText mCallName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //initializing UI elements mCallName = (EditText) findViewById(R.id.callName); mCallButton = (Button) findViewById(R.id.callButton); mCallButton.setEnabled(false); mCallButton.setOnClickListener(buttonClickListener); Button stopButton = (Button) findViewById(R.id.stopButton); stopButton.setOnClickListener(buttonClickListener); } // invoked when the connection with SinchServer is established @Override protected void onServiceConnected() { TextView userName = (TextView) findViewById(R.id.loggedInName); userName.setText(getSinchServiceInterface().getUserName()); mCallButton.setEnabled(true); } @Override public void onDestroy() { if (getSinchServiceInterface() != null) { getSinchServiceInterface().stopClient(); } super.onDestroy(); } //to kill the current session of SinchService private void stopButtonClicked() { if (getSinchServiceInterface() != null) { getSinchServiceInterface().stopClient(); } finish(); } //to place the call to the entered name private void callButtonClicked() { String userName = mCallName.getText().toString(); if (userName.isEmpty()) { Toast.makeText(this, "Please enter a user to call", Toast.LENGTH_LONG).show(); return; } Call call = getSinchServiceInterface().callUserVideo(userName); String callId = call.getCallId(); Intent callScreen = new Intent(this, CallScreenActivity.class); callScreen.putExtra(SinchService.CALL_ID, callId); startActivity(callScreen); } private OnClickListener buttonClickListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.callButton: callButtonClicked(); break; case R.id.stopButton: stopButtonClicked(); break; } } }; } |
In the above code, the following things have been taken care of:-
- Setting up the connection to the Sinch Service.
- Configuring controls to place the call
The CallScreen
Now, we need to set up the activity in which the actual call would be placed. To do so, create a new layout resource file named callscreen.xml and add the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffffff" android:orientation="vertical" android:weightSum="5"> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#fffafafa" android:orientation="vertical"> <TextView android:id="@+id/remoteUser" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="10dp" android:text="" android:textSize="28sp" /> <TextView android:id="@+id/callState" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Ringing" android:textAllCaps="true" android:textSize="16sp" /> <TextView android:id="@+id/callDuration" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="8dp" android:layout_marginTop="4dp" android:text="00:00" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="16sp" /> </LinearLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="0dp" android:layout_weight="3" android:background="@android:color/darker_gray" android:orientation="vertical" android:padding="0dip"> <LinearLayout android:id="@+id/remoteVideo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:orientation="horizontal" /> <RelativeLayout android:id="@+id/localVideo" android:layout_width="150dp" android:layout_height="200dp" android:layout_alignParentRight="true" android:layout_alignParentTop="true" /> </RelativeLayout> <RelativeLayout android:id="@+id/bottomPanel" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/greyish"> <Button android:id="@+id/hangupButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:paddingBottom="20dp" android:paddingTop="20dp" android:text="END call" android:textSize="20sp" /> </RelativeLayout> </LinearLayout> |
The above code would generate the following layout
Java Part- Create a class named CallScreenActivity.java and add the following code:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
package techheromanish.example.com.videochatapp; import com.sinch.android.rtc.AudioController; import com.sinch.android.rtc.PushPair; import com.sinch.android.rtc.calling.Call; import com.sinch.android.rtc.calling.CallEndCause; import com.sinch.android.rtc.calling.CallState; import com.sinch.android.rtc.video.VideoCallListener; import com.sinch.android.rtc.video.VideoController; import android.media.AudioManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import java.util.List; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; public class CallScreenActivity extends BaseActivity { static final String TAG = CallScreenActivity.class.getSimpleName(); static final String CALL_START_TIME = "callStartTime"; static final String ADDED_LISTENER = "addedListener"; private AudioPlayer mAudioPlayer; private Timer mTimer; private UpdateCallDurationTask mDurationTask; private String mCallId; private long mCallStart = 0; private boolean mAddedListener = false; private boolean mVideoViewsAdded = false; private TextView mCallDuration; private TextView mCallState; private TextView mCallerName; private class UpdateCallDurationTask extends TimerTask { @Override public void run() { CallScreenActivity.this.runOnUiThread(new Runnable() { @Override public void run() { updateCallDuration(); } }); } } @Override protected void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putLong(CALL_START_TIME, mCallStart); savedInstanceState.putBoolean(ADDED_LISTENER, mAddedListener); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { mCallStart = savedInstanceState.getLong(CALL_START_TIME); mAddedListener = savedInstanceState.getBoolean(ADDED_LISTENER); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.callscreen); mAudioPlayer = new AudioPlayer(this); mCallDuration = (TextView) findViewById(R.id.callDuration); mCallerName = (TextView) findViewById(R.id.remoteUser); mCallState = (TextView) findViewById(R.id.callState); Button endCallButton = (Button) findViewById(R.id.hangupButton); endCallButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { endCall(); } }); mCallId = getIntent().getStringExtra(SinchService.CALL_ID); if (savedInstanceState == null) { mCallStart = System.currentTimeMillis(); } } @Override public void onServiceConnected() { Call call = getSinchServiceInterface().getCall(mCallId); if (call != null) { if (!mAddedListener) { call.addCallListener(new SinchCallListener()); mAddedListener = true; } } else { Log.e(TAG, "Started with invalid callId, aborting."); finish(); } updateUI(); } //method to update video feeds in the UI private void updateUI() { if (getSinchServiceInterface() == null) { return; // early } Call call = getSinchServiceInterface().getCall(mCallId); if (call != null) { mCallerName.setText(call.getRemoteUserId()); mCallState.setText(call.getState().toString()); if (call.getState() == CallState.ESTABLISHED) { //when the call is established, addVideoViews configures the video to be shown addVideoViews(); } } } //stop the timer when call is ended @Override public void onStop() { super.onStop(); mDurationTask.cancel(); mTimer.cancel(); removeVideoViews(); } //start the timer for the call duration here @Override public void onStart() { super.onStart(); mTimer = new Timer(); mDurationTask = new UpdateCallDurationTask(); mTimer.schedule(mDurationTask, 0, 500); updateUI(); } @Override public void onBackPressed() { // User should exit activity by ending call, not by going back. } //method to end the call private void endCall() { mAudioPlayer.stopProgressTone(); Call call = getSinchServiceInterface().getCall(mCallId); if (call != null) { call.hangup(); } finish(); } private String formatTimespan(long timespan) { long totalSeconds = timespan / 1000; long minutes = totalSeconds / 60; long seconds = totalSeconds % 60; return String.format(Locale.US, "%02d:%02d", minutes, seconds); } //method to update live duration of the call private void updateCallDuration() { if (mCallStart > 0) { mCallDuration.setText(formatTimespan(System.currentTimeMillis() - mCallStart)); } } //method which sets up the video feeds from the server to the UI of the activity private void addVideoViews() { if (mVideoViewsAdded || getSinchServiceInterface() == null) { return; //early } final VideoController vc = getSinchServiceInterface().getVideoController(); if (vc != null) { RelativeLayout localView = (RelativeLayout) findViewById(R.id.localVideo); localView.addView(vc.getLocalView()); localView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //this toggles the front camera to rear camera and vice versa vc.toggleCaptureDevicePosition(); } }); LinearLayout view = (LinearLayout) findViewById(R.id.remoteVideo); view.addView(vc.getRemoteView()); mVideoViewsAdded = true; } } //removes video feeds from the app once the call is terminated private void removeVideoViews() { if (getSinchServiceInterface() == null) { return; // early } VideoController vc = getSinchServiceInterface().getVideoController(); if (vc != null) { LinearLayout view = (LinearLayout) findViewById(R.id.remoteVideo); view.removeView(vc.getRemoteView()); RelativeLayout localView = (RelativeLayout) findViewById(R.id.localVideo); localView.removeView(vc.getLocalView()); mVideoViewsAdded = false; } } private class SinchCallListener implements VideoCallListener { @Override public void onCallEnded(Call call) { CallEndCause cause = call.getDetails().getEndCause(); Log.d(TAG, "Call ended. Reason: " + cause.toString()); mAudioPlayer.stopProgressTone(); setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE); String endMsg = "Call ended: " + call.getDetails().toString(); Toast.makeText(CallScreenActivity.this, endMsg, Toast.LENGTH_LONG).show(); endCall(); } @Override public void onCallEstablished(Call call) { Log.d(TAG, "Call established"); mAudioPlayer.stopProgressTone(); mCallState.setText(call.getState().toString()); setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); AudioController audioController = getSinchServiceInterface().getAudioController(); audioController.enableSpeaker(); mCallStart = System.currentTimeMillis(); Log.d(TAG, "Call offered video: " + call.getDetails().isVideoOffered()); } @Override public void onCallProgressing(Call call) { Log.d(TAG, "Call progressing"); mAudioPlayer.playProgressTone(); } @Override public void onShouldSendPushNotification(Call call, List pushPairs) { // Send a push through your push provider here, e.g. GCM } @Override public void onVideoTrackAdded(Call call) { Log.d(TAG, "Video track added"); addVideoViews(); } } } |
The Incoming Call  Screen
Now that the CallScreenActivity has been  configured, it’s time to configure the Incoming CallScreenActivity.
To do so, create a new layout resource file named incmoing.xml and add the following code:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffffff" android:orientation="vertical" android:weightSum="5"> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#fffafafa" android:orientation="vertical"> <TextView android:id="@+id/remoteUser" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="14dp" android:text="Sample user" android:textSize="28sp" /> <TextView android:id="@+id/callState" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Incoming call" android:textAllCaps="true" android:textSize="16sp" /> </LinearLayout> <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="3" android:background="@android:color/darker_gray" android:contentDescription="Call background" android:padding="0dip"> </ImageView> <RelativeLayout android:id="@+id/bottomPanel" android:layout_width="match_parent" android:layout_height="0dp" android:layout_gravity="center" android:layout_weight="1" android:background="@color/greyish" android:gravity="center"> <Button android:id="@+id/answerButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Answer" android:textSize="20sp" /> <Button android:id="@+id/declineButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/answerButton" android:text="Cancel" android:textSize="20sp" /> </RelativeLayout> </LinearLayout> |
The above code would generate the following  layout.
Next, create a class named IncomingCallScreenActivity.java  and add the following code:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
package techheromanish.example.com.videochatapp; import com.sinch.android.rtc.PushPair; import com.sinch.android.rtc.calling.Call; import com.sinch.android.rtc.calling.CallEndCause; import com.sinch.android.rtc.video.VideoCallListener; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import java.util.List; public class IncomingCallScreenActivity extends BaseActivity { static final String TAG = IncomingCallScreenActivity.class.getSimpleName(); private String mCallId; private AudioPlayer mAudioPlayer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.incoming); Button answer = (Button) findViewById(R.id.answerButton); answer.setOnClickListener(mClickListener); Button decline = (Button) findViewById(R.id.declineButton); decline.setOnClickListener(mClickListener); mAudioPlayer = new AudioPlayer(this); mAudioPlayer.playRingtone(); mCallId = getIntent().getStringExtra(SinchService.CALL_ID); } @Override protected void onServiceConnected() { Call call = getSinchServiceInterface().getCall(mCallId); if (call != null) { call.addCallListener(new SinchCallListener()); TextView remoteUser = (TextView) findViewById(R.id.remoteUser); remoteUser.setText(call.getRemoteUserId()); } else { Log.e(TAG, "Started with invalid callId, aborting"); finish(); } } private void answerClicked() { mAudioPlayer.stopRingtone(); Call call = getSinchServiceInterface().getCall(mCallId); if (call != null) { call.answer(); Intent intent = new Intent(this, CallScreenActivity.class); intent.putExtra(SinchService.CALL_ID, mCallId); startActivity(intent); } else { finish(); } } private void declineClicked() { mAudioPlayer.stopRingtone(); Call call = getSinchServiceInterface().getCall(mCallId); if (call != null) { call.hangup(); } finish(); } private class SinchCallListener implements VideoCallListener { @Override public void onCallEnded(Call call) { CallEndCause cause = call.getDetails().getEndCause(); Log.d(TAG, "Call ended, cause: " + cause.toString()); mAudioPlayer.stopRingtone(); finish(); } @Override public void onCallEstablished(Call call) { Log.d(TAG, "Call established"); } @Override public void onCallProgressing(Call call) { Log.d(TAG, "Call progressing"); } @Override public void onShouldSendPushNotification(Call call, List pushPairs) { // Send a push through your push provider here, e.g. GCM } @Override public void onVideoTrackAdded(Call call) { // Display some kind of icon showing it's a video call } } private OnClickListener mClickListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.answerButton: answerClicked(); break; case R.id.declineButton: declineClicked(); break; } } }; } |
The above code is quite self explanatory.
Setting up resources
Now that the screens are all set. We need to set up the audios to played when call is placed and received.
To do so, in the res directory of your project, create new directory of the name raw.
Download the zip of audios from the link given below and paste these into the raw directory.
[download id=”4353″]
Finalizing project
We are almost done. Just update your AndroidManifest.xml  file as shown  below:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="techheromanish.example.com.videochatapp"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <service android:name=".SinchService"></service> <activity android:name=".LoginActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme" > <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".CallScreenActivity"/> <activity android:name=".IncomingCallScreenActivity" android:screenOrientation="portrait" android:noHistory="true"/> <activity android:name=".PlaceCallActivity" android:screenOrientation="portrait"/> </application> </manifest> |
That’s it. Your app is ready to be tested. Just login with  unique usernames on two mobile phones. and in  the recipients name, enter one of the usernames, hit the call button and you shall see the other phone ringing.
So thats all for this Video Call Android Tutorial, you can also get the source code of my project from the link given below.
[sociallocker id=1372] Video Call Android Tutorial [/sociallocker]
Feel free to clarify your doubts in the comments section if you come across any regarding this Video Call Android Tutorial. Thank You 🙂