In this post, I will create simple android app for audio calling and video call (app to app) using Sinch SDK. Before going forward, I want to tell you something important that is logic, I will be using for video call services for both audio and video call the difference is that i will not allow to video streaming for audio call initially, later user can allow video streaming. The benefit of using this logic is that you can easily switch from audio to video call during a call. If you use ordinary way, like for audio call you use audio services and for video call you use video service, so the problem of doing that is no switching from audio to video call during a call, Sinch SDK not allowing you that.
Advantage of using Sinch SDK for Audio and Video calling
Sinch is basically a product which is used for video, audio calling and instant messaging over 150 countries and it is supported in android, IOS and web. The good thing about it, is that it saves a lot of time in writing a code for audio management, handling complexity of signals, video management and networking, it contain build in functions to use loud speaker as on or off and mute the your voice or unmute, also you can handle your camera in video call, you can do all these complicated things in just 2 line of code. The other good thing about it is that it give 2500+ minutes free per month, which is very good for testing and also use.
How to Install Sinch SDK in your Android Studio (Sep 2019 method)
1. First Download (Sinch ARR File for android) this file and put this file lib directory of your android studio project.
2.Open build gradle and add this line of code in dependency ( "implementation(name: 'sinch-android-rtc',version: '+', ext: 'aar')")
audio and video in android part2
3.Open build gradle as project and add this line of code in repositories ( " flatDir { dirs 'libs'} ").
audio and video in android part3
4. Now Sync your android project to install Sinch sdk successfully.
Importants things you must know about Sinch SDK.
First, in order to use Sinch services, you must bounded background services otherwise it will not work properly, background service will help you to run app functionality when your app is sleeping or destroyed. Other important thing is that sinch have its own database of user id. Suppose if your app have 5 users then you have initialize 5 different user id during creating sinch service, the best way is that when user login to app then initialize unique id for that user. You can call to other user this unique id, it is identifier for different users.
Practical implementation
Click on Tablayout, to view a code, First you have to create Sinch Service class and BaseActivity class. Sinch Service class is for using sinch services in background services and BaseActivity class is basically a interface which is used in other classes like Main Activity, Video call.
View Code
Below, you will see different buttons, these are basically different classes, click on button to view a code.
SinchService
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.saifyproduction.sunupayApp.view.Incoming_voice_call;
import com.saifyproduction.sunupayApp.view.incoming_video_call;
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.calling.Call;
import com.sinch.android.rtc.calling.CallClient;
import com.sinch.android.rtc.calling.CallClientListener;
import com.sinch.android.rtc.video.VideoController;
import androidx.annotation.NonNull;
public class SinchService extends Service {
private static final String APP_KEY = "";
private static final String APP_SECRET = "";
private static final String ENVIRONMENT = "";
static int state ;
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, int i ) {
state = i;
return mSinchClient.getCallClient().callUserVideo(userId);
}
public Call callUseraudio(String userId) {
return mSinchClient.getCallClient().callUser(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, final Call call) {
FirebaseAuth auth = FirebaseAuth.getInstance();
DatabaseReference cal = FirebaseDatabase.getInstance().getReference().child("call_detail").child(auth.getCurrentUser().getUid()).child(call.getCallId());
cal.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()){
String cl = dataSnapshot.child("calltype").getValue(String.class);
if( cl.equalsIgnoreCase("audio") ){
Log.d(TAG, "Incoming call");
Intent intent = new Intent(SinchService.this, Incoming_voice_call.class);
intent.putExtra(CALL_ID, call.getCallId());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
SinchService.this.startActivity(intent);
}
else{
Log.d(TAG, "Incoming call");
Intent intent = new Intent(SinchService.this, incoming_video_call.class);
intent.putExtra(CALL_ID, call.getCallId());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
SinchService.this.startActivity(intent);
}
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
}
}
}
import android.media.AudioManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import com.saifyproduction.sunupayApp.R;
import com.saifyproduction.sunupayApp.controller.AudioPlayer;
import com.saifyproduction.sunupayApp.controller.SinchService;
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 com.squareup.picasso.Picasso;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
public class VideoCall extends BaseActivity {
static final String TAG = VideoCall.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;
ImageView img, loudspeaker, camera , mic;
int loud_flag =0 , cam_flag =0 , mic_flag =0;
ImageView camera_change, video_request_accept, video_request_reject;
TextView connection;
int state =0;
int recieved_flag = 0;
boolean localview = false ;
boolean remoteview = false ;
DatabaseReference call_ref = FirebaseDatabase.getInstance().getReference().child("Call");
FirebaseAuth auth = FirebaseAuth.getInstance();
private class UpdateCallDurationTask extends TimerTask {
@Override
public void run() {
VideoCall.this.runOnUiThread(new Runnable() {
@Override
public void run() {
updateCallDuration();
}
});
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(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.activity_voice_call);
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.hangup);
img = (ImageView)findViewById(R.id.person_img);
camera_change = (ImageView)findViewById(R.id.camera_chnage);
video_request_accept = (ImageView)findViewById(R.id.acceptvideoreject);
video_request_reject = (ImageView)findViewById(R.id.rejectvideorequest);
connection = (TextView) findViewById(R.id.connection);
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
endCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
endCall();
}
});
mCallId = getIntent().getStringExtra(SinchService.CALL_ID);
if (savedInstanceState == null) {
mCallStart = System.currentTimeMillis();
}
mCallState.setText("Calling");
loudspeaker = (ImageView) findViewById(R.id.loudspeaker);
camera = (ImageView) findViewById(R.id.camera);
mic = (ImageView) findViewById(R.id.mic);
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
loudspeaker.setBackground(getResources().getDrawable(R.drawable.phonecircle));
video_request_accept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Call call = getSinchServiceInterface().getCall(mCallId);
cam_flag = 1;
img.setVisibility(View.GONE);
addlocalView();
addRemoteView();
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(1);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(1);
call_ref.child(mCallId).child("active").setValue(1);
}
});
video_request_reject.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
removelocalview();
removeremoteview();
img.setVisibility(View.VISIBLE);
cam_flag = 0;
camera.setBackground(getResources().getDrawable(R.drawable.trans));
Call call = getSinchServiceInterface().getCall(mCallId);
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(0);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(0);
call_ref.child(mCallId).child("active").setValue(0);
}
});
Query query = FirebaseDatabase.getInstance()
.getReference()
.child("Call").child(mCallId);
ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
// ...
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
// ...
System.out.println("On child changed");
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference().child("Call").child(mCallId);
connectedRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
System.out.println("On Enter");
if(snapshot.exists()){
System.out.println("On Data Exist");
Call call = getSinchServiceInterface().getCall(mCallId);
int me = snapshot.child(auth.getCurrentUser().getUid()).getValue(Integer.class);
int other = snapshot.child(call.getRemoteUserId()).getValue(Integer.class);
int active = snapshot.child("active").getValue(Integer.class);
System.out.println("me == "+me +"\n"+ " other == "+other +"\n active == "+active);
if (active == 0) {
if (me == 0 && other == 0) {
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
camera.setBackground(getResources().getDrawable(R.drawable.trans));
removelocalview();
removeremoteview();
cam_flag = 0;
img.setVisibility(View.VISIBLE);
} else if (me == 0 && other == 1) {
img.setVisibility(View.GONE);
video_request_accept.setVisibility(View.VISIBLE);
video_request_reject.setVisibility(View.VISIBLE);
connection.setText("Video Call Request");
}
else if (me == 1 && other == 1) {
img.setVisibility(View.GONE);
addlocalView();
addRemoteView();
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
loudspeaker.setBackground(getResources().getDrawable(R.drawable.phonecircle));
getSinchServiceInterface().getAudioController().enableSpeaker();
cam_flag = 1;
}
} else {
if (me == 0 && other == 0) {
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
call_ref.child(mCallId).child("active").setValue(0);
camera.setBackground(getResources().getDrawable(R.drawable.trans));
removelocalview();
removeremoteview();
cam_flag = 0;
img.setVisibility(View.VISIBLE);
}
else if(other == 0){
removeremoteview();
}
else if(other == 1){
addRemoteView();
}
}
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
// ...
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
// ...
}
@Override
public void onCancelled(DatabaseError databaseError) {
// ...
}
};
query.addChildEventListener(childEventListener);
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
cam_flag = 1;
img.setVisibility(View.GONE);
addlocalView();
}
public void addlocalView(){
if (localview || 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 View.OnClickListener() {
@Override
public void onClick(View v) {
//this toggles the front camera to rear camera and vice versa
vc.toggleCaptureDevicePosition();
}
});
localview = true;
}
}
public void addRemoteView(){
if (remoteview || getSinchServiceInterface() == null) {
return; //early
}
final VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
ConstraintLayout view = ( ConstraintLayout ) findViewById(R.id.remoteVideo);
view.addView(vc.getRemoteView());
remoteview = true;
}
}
public void removelocalview(){
if (getSinchServiceInterface() == null) {
return; // early
}
VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
RelativeLayout localView = (RelativeLayout) findViewById(R.id.localVideo);
localView.removeView(vc.getLocalView());
localview = false;
}
}
public void removeremoteview(){
if (getSinchServiceInterface() == null) {
return; // early
}
VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
ConstraintLayout view = (ConstraintLayout) findViewById(R.id.remoteVideo);
view.removeView(vc.getRemoteView());
remoteview = false;
}
}
@Override
public void onServiceConnected() {
Call call = getSinchServiceInterface().getCall(mCallId);
if (call != null) {
if (!mAddedListener) {
call.addCallListener(new SinchCallListener());
mAddedListener = true;
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
cam_flag = 1;
img.setVisibility(View.GONE);
addlocalView();
}
} 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("");
// online data
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(0);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(0);
call_ref.child(mCallId).child("active").setValue(0);
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference().child("user").child(call.getRemoteUserId());
connectedRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
String s = snapshot.child("name").getValue(String.class);
String u = snapshot.child("url").getValue(String.class);
mCallerName.setText(s);
Picasso.get().load(u).placeholder(R.drawable.profile_image).into(img);
mCallState.setText("Ringing");
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
mCallState.setText(call.getState().toString());
if (call.getState() == CallState.ESTABLISHED) {
//when the call is established, addVideoViews configures the video to be shown
}
}
}
//stop the timer when call is ended
@Override
public void onStop() {
super.onStop();
removelocalview();
removeremoteview();
}
//start the timer for the call duration here
@Override
public void onStart() {
super.onStart();
mTimer = new Timer();
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));
}
}
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(VideoCall.this, endMsg, Toast.LENGTH_LONG).show();
if(recieved_flag == 1){
mDurationTask.cancel();
mTimer.cancel();
}
endCall();
}
@Override
public void onCallEstablished(Call call) {
recieved_flag = 1;
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());
loud_flag = 1;
loudspeaker.setBackground(getResources().getDrawable(R.drawable.phonecircle));
// camera
addRemoteView();
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(1);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(1);
call_ref.child(mCallId).child("active").setValue(1);
mDurationTask = new UpdateCallDurationTask();
mTimer.schedule(mDurationTask, 0, 500);
loudspeaker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(loud_flag == 0){
loud_flag = 1;
loudspeaker.setBackground(getResources().getDrawable(R.drawable.phonecircle));
getSinchServiceInterface().getAudioController().enableSpeaker();
/*AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
System.out.println("speaker on= "+audioManager.isSpeakerphoneOn());
if(!audioManager.isSpeakerphoneOn()) {
audioManager.setSpeakerphoneOn(true);
}
*/
}
else {
loudspeaker.setBackground(getResources().getDrawable(R.drawable.trans));
loud_flag = 0;
getSinchServiceInterface().getAudioController().disableSpeaker();
/* AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
System.out.println("speaker off= "+ (!audioManager.isSpeakerphoneOn()));
if(audioManager.isSpeakerphoneOn()) {
audioManager.setSpeakerphoneOn(false);
}
*/
}
}
});
camera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(cam_flag == 0){
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
cam_flag = 1;
addlocalView();
img.setVisibility(View.GONE);
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(1);
}
else {
camera.setBackground(getResources().getDrawable(R.drawable.trans));
cam_flag = 0;
removelocalview();
// img.setVisibility(View.VISIBLE);
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(0);
}
}
});
mic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mic_flag == 0){
mic.setBackground(getResources().getDrawable(R.drawable.phonecircle));
getSinchServiceInterface().getAudioController().mute();
mic_flag = 1;
}
else {
mic.setBackground(getResources().getDrawable(R.drawable.trans));
getSinchServiceInterface().getAudioController().unmute();
mic_flag = 0;
}
}
});
camera_change.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final VideoController vc = getSinchServiceInterface().getVideoController();
vc.toggleCaptureDevicePosition();
}
});
}
@Override
public void onCallProgressing(Call call) {
Log.d(TAG, "Call progressing");
mAudioPlayer.playProgressTone();
}
@Override
public void onShouldSendPushNotification(Call call, List<PushPair> pushPairs) {
// Send a push through your push provider here, e.g. GCM
}
@Override
public void onVideoTrackAdded(Call call) {
}
@Override
public void onVideoTrackPaused(Call call) {
}
@Override
public void onVideoTrackResumed(Call call) {
}
}
}
VoiceCall
import android.media.AudioManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import com.saifyproduction.sunupayApp.R;
import com.saifyproduction.sunupayApp.controller.AudioPlayer;
import com.saifyproduction.sunupayApp.controller.SinchService;
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 com.squareup.picasso.Picasso;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
public class VoiceCall extends BaseActivity {
static final String TAG = VoiceCall.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;
ImageView img, loudspeaker, camera , mic;
int loud_flag =0 , cam_flag =0 , mic_flag =0;
ImageView camera_change, video_request_accept, video_request_reject;
TextView connection;
int state =0;
int recieved_flag = 0;
boolean localview = false ;
boolean remoteview = false ;
DatabaseReference call_ref = FirebaseDatabase.getInstance().getReference().child("Call");
FirebaseAuth auth = FirebaseAuth.getInstance();
private class UpdateCallDurationTask extends TimerTask {
@Override
public void run() {
VoiceCall.this.runOnUiThread(new Runnable() {
@Override
public void run() {
updateCallDuration();
}
});
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(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.activity_voice_call);
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.hangup);
img = (ImageView)findViewById(R.id.person_img);
camera_change = (ImageView)findViewById(R.id.camera_chnage);
video_request_accept = (ImageView)findViewById(R.id.acceptvideoreject);
video_request_reject = (ImageView)findViewById(R.id.rejectvideorequest);
connection = (TextView) findViewById(R.id.connection);
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
endCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
endCall();
}
});
mCallId = getIntent().getStringExtra(SinchService.CALL_ID);
if (savedInstanceState == null) {
mCallStart = System.currentTimeMillis();
}
mCallState.setText("Calling");
loudspeaker = (ImageView) findViewById(R.id.loudspeaker);
camera = (ImageView) findViewById(R.id.camera);
mic = (ImageView) findViewById(R.id.mic);
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
video_request_accept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Call call = getSinchServiceInterface().getCall(mCallId);
cam_flag = 1;
img.setVisibility(View.GONE);
addlocalView();
addRemoteView();
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(1);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(1);
call_ref.child(mCallId).child("active").setValue(1);
}
});
video_request_reject.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
removelocalview();
removeremoteview();
img.setVisibility(View.VISIBLE);
cam_flag = 0;
camera.setBackground(getResources().getDrawable(R.drawable.trans));
Call call = getSinchServiceInterface().getCall(mCallId);
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(0);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(0);
call_ref.child(mCallId).child("active").setValue(0);
}
});
Query query = FirebaseDatabase.getInstance()
.getReference()
.child("Call").child(mCallId);
ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
// ...
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
// ...
System.out.println("On child changed");
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference().child("Call").child(mCallId);
connectedRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
System.out.println("On Enter");
if(snapshot.exists()){
System.out.println("On Data Exist");
Call call = getSinchServiceInterface().getCall(mCallId);
int me = snapshot.child(auth.getCurrentUser().getUid()).getValue(Integer.class);
int other = snapshot.child(call.getRemoteUserId()).getValue(Integer.class);
int active = snapshot.child("active").getValue(Integer.class);
System.out.println("me == "+me +"\n"+ " other == "+other +"\n active == "+active);
if (active == 0) {
if (me == 0 && other == 0) {
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
camera.setBackground(getResources().getDrawable(R.drawable.trans));
removelocalview();
removeremoteview();
cam_flag = 0;
img.setVisibility(View.VISIBLE);
} else if (me == 0 && other == 1) {
img.setVisibility(View.GONE);
video_request_accept.setVisibility(View.VISIBLE);
video_request_reject.setVisibility(View.VISIBLE);
connection.setText("Video Call Request");
}
else if (me == 1 && other == 1) {
img.setVisibility(View.GONE);
addlocalView();
addRemoteView();
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
loudspeaker.setBackground(getResources().getDrawable(R.drawable.phonecircle));
getSinchServiceInterface().getAudioController().enableSpeaker();
cam_flag = 1;
}
} else {
if (me == 0 && other == 0) {
video_request_accept.setVisibility(View.GONE);
video_request_reject.setVisibility(View.GONE);
connection.setText("");
call_ref.child(mCallId).child("active").setValue(0);
camera.setBackground(getResources().getDrawable(R.drawable.trans));
removelocalview();
removeremoteview();
cam_flag = 0;
img.setVisibility(View.VISIBLE);
}
else if(other == 0){
removeremoteview();
}
else if(other == 1){
addRemoteView();
}
}
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
// ...
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
// ...
}
@Override
public void onCancelled(DatabaseError databaseError) {
// ...
}
};
query.addChildEventListener(childEventListener);
}
public void addlocalView(){
if (localview || 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 View.OnClickListener() {
@Override
public void onClick(View v) {
//this toggles the front camera to rear camera and vice versa
vc.toggleCaptureDevicePosition();
}
});
localview = true;
}
}
public void addRemoteView(){
if (remoteview || getSinchServiceInterface() == null) {
return; //early
}
final VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
ConstraintLayout view = ( ConstraintLayout ) findViewById(R.id.remoteVideo);
view.addView(vc.getRemoteView());
remoteview = true;
}
}
public void removelocalview(){
if (getSinchServiceInterface() == null) {
return; // early
}
VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
RelativeLayout localView = (RelativeLayout) findViewById(R.id.localVideo);
localView.removeView(vc.getLocalView());
localview = false;
}
}
public void removeremoteview(){
if (getSinchServiceInterface() == null) {
return; // early
}
VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
ConstraintLayout view = (ConstraintLayout) findViewById(R.id.remoteVideo);
view.removeView(vc.getRemoteView());
remoteview = false;
}
}
@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("");
// online data
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(0);
call_ref.child(mCallId).child(call.getRemoteUserId()).setValue(0);
call_ref.child(mCallId).child("active").setValue(0);
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference().child("user").child(call.getRemoteUserId());
connectedRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
String s = snapshot.child("name").getValue(String.class);
String u = snapshot.child("url").getValue(String.class);
mCallerName.setText(s);
Picasso.get().load(u).placeholder(R.drawable.profile_image).into(img);
mCallState.setText("Ringing");
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
mCallState.setText(call.getState().toString());
if (call.getState() == CallState.ESTABLISHED) {
//when the call is established, addVideoViews configures the video to be shown
}
}
}
//stop the timer when call is ended
@Override
public void onStop() {
super.onStop();
removelocalview();
removeremoteview();
}
//start the timer for the call duration here
@Override
public void onStart() {
super.onStart();
mTimer = new Timer();
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));
}
}
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(VoiceCall.this, endMsg, Toast.LENGTH_LONG).show();
if(recieved_flag == 1){
mDurationTask.cancel();
mTimer.cancel();
}
endCall();
}
@Override
public void onCallEstablished(Call call) {
recieved_flag = 1;
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());
mDurationTask = new UpdateCallDurationTask();
mTimer.schedule(mDurationTask, 0, 500);
loudspeaker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(loud_flag == 0){
loud_flag = 1;
loudspeaker.setBackground(getResources().getDrawable(R.drawable.phonecircle));
getSinchServiceInterface().getAudioController().enableSpeaker();
/*AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
System.out.println("speaker on= "+audioManager.isSpeakerphoneOn());
if(!audioManager.isSpeakerphoneOn()) {
audioManager.setSpeakerphoneOn(true);
}
*/
}
else {
loudspeaker.setBackground(getResources().getDrawable(R.drawable.trans));
loud_flag = 0;
getSinchServiceInterface().getAudioController().disableSpeaker();
/* AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
System.out.println("speaker off= "+ (!audioManager.isSpeakerphoneOn()));
if(audioManager.isSpeakerphoneOn()) {
audioManager.setSpeakerphoneOn(false);
}
*/
}
}
});
camera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(cam_flag == 0){
camera.setBackground(getResources().getDrawable(R.drawable.phonecircle));
cam_flag = 1;
addlocalView();
img.setVisibility(View.GONE);
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(1);
}
else {
camera.setBackground(getResources().getDrawable(R.drawable.trans));
cam_flag = 0;
removelocalview();
// img.setVisibility(View.VISIBLE);
call_ref.child(mCallId).child(auth.getCurrentUser().getUid()).setValue(0);
}
}
});
mic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mic_flag == 0){
mic.setBackground(getResources().getDrawable(R.drawable.phonecircle));
getSinchServiceInterface().getAudioController().mute();
mic_flag = 1;
}
else {
mic.setBackground(getResources().getDrawable(R.drawable.trans));
getSinchServiceInterface().getAudioController().unmute();
mic_flag = 0;
}
}
});
camera_change.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final VideoController vc = getSinchServiceInterface().getVideoController();
vc.toggleCaptureDevicePosition();
}
});
}
@Override
public void onCallProgressing(Call call) {
Log.d(TAG, "Call progressing");
mAudioPlayer.playProgressTone();
}
@Override
public void onShouldSendPushNotification(Call call, List<PushPair> pushPairs) {
// Send a push through your push provider here, e.g. GCM
}
@Override
public void onVideoTrackAdded(Call call) {
}
@Override
public void onVideoTrackPaused(Call call) {
}
@Override
public void onVideoTrackResumed(Call call) {
}
}
}
MainActivity
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 MainActivity 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.activity_main);
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();
}
}
Important Points
Create account in Sinch, and get three detail from there and put in your code in Sinch Service class
private static final String APP_KEY = "";
private static final String APP_SECRET = "";
private static final String ENVIRONMENT = "";
Thats it, any question and issue you can ask in comment section. Thank you.