0% found this document useful (0 votes)
12 views

Webrtc

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views

Webrtc

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 10

package com.idev.entalk.ui.

activity;

import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.bumptech.glide.Glide;
import com.google.gson.Gson;
import com.idev.entalk.R;
import com.idev.entalk.base.BaseActivity;
import com.idev.entalk.model.CoLearner;
import com.idev.entalk.model.ConnectionStatus;
import com.idev.entalk.utils.Extra;
import com.idev.entalk.utils.Global;
import com.idev.entalk.utils.SimpleObserver;
import com.idev.entalk.utils.SimpleSdpObserver;
import com.idev.entalk.utils.SocketEvent;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import io.socket.client.Socket;

public class CallingActivity extends BaseActivity {

private Socket socket;


private CoLearner coLearner;

private TextView statusView, userNameView, ratingView, timestampView;


private ImageView profilePhotoView;

private ImageView speakerBtn, muteBtn, endBtn;

/// for peer connection


private EglBase rootEglBase;

private VideoTrack localVideoTrack;


private AudioTrack localAudioTrack;

private VideoTrack remoteVideoTrack;


private AudioTrack remoteAudioTrack;

MediaStream localMediaStream;
MediaStream remoteMediaStream;

private PeerConnectionFactory peerConnectionFactory;

private SurfaceViewRenderer localVideoView;


private SurfaceViewRenderer remoteVideoView;

private TextView hangup; // Call End Button


private PeerConnection peerConnection;

private Boolean gotUserMedia = false;


private List<PeerConnection.IceServer> peerIceServers;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calling);
setStatusBarLight();

try{
coLearner = (CoLearner)
getIntent().getExtras().get(Extra.EXTRA_CO_LEARNER);
if (coLearner == null) finish();
}
catch (Exception e){
finish();
}

if (Global.socket != null) {
this.socket = Global.socket;
Global.socket = null;
}
else finish();

statusView = findViewById(R.id.status_view);
profilePhotoView = findViewById(R.id.profile_photo_view);
userNameView = findViewById(R.id.user_name_view);
ratingView = findViewById(R.id.rating_count_view);
timestampView = findViewById(R.id.timestamp_view);

speakerBtn = findViewById(R.id.speaker_btn);
muteBtn = findViewById(R.id.mute_btn);
endBtn = findViewById(R.id.end_btn);

// peer connection
localVideoView = findViewById(R.id.local_rand);
remoteVideoView = findViewById(R.id.remote_rand);

rootEglBase = EglBase.create();
initVideos();
peerIceServers = getIceServers();
peerConnectionFactory = getPeerConnectionFactory();
startPeerConnectionCalling();

speakerBtn.setSelected(true);
muteBtn.setSelected(true);

muteBtn.setOnClickListener(view -> {
muteBtn.setSelected(!muteBtn.isSelected());
onVoiceSettingChange();
});
speakerBtn.setOnClickListener(view -> {
speakerBtn.setSelected(!speakerBtn.isSelected());
onVoiceSettingChange();
});
AtomicBoolean isClicked = new AtomicBoolean(false);
endBtn.setOnClickListener(view -> {
// if (isClicked.get()) return;
// isClicked.set(true);
// closeVoiceConversion(2000);

doCall();
});

updateUI();
startTimestampIncrease();
startVoiceConversion();
}

private void updateUI() {


Glide.with(this).load(coLearner.getProfilePhoto()).into(profilePhotoView);
userNameView.setText(coLearner.getUserName());
ratingView.setText(String.valueOf(coLearner.getRating()));
}

int timestamp = 0;
private void startTimestampIncrease() {
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
timestamp++;
int min = timestamp < 60 ? 0 : timestamp/60;
int sec = timestamp < 60 ? timestamp : timestamp - (min*60);
onTimestampChanged(min, sec);
handler.postDelayed((Runnable) this, 1000);
}
};
handler.postDelayed(runnable, 1000);
}

private void onTimestampChanged(int min, int sec) {


String stampMin = String.valueOf(min).length() == 1 ? "0" + min :
String.valueOf(min);
String stampSec = (String.valueOf(sec).length() == 1 ? "0" + sec :
String.valueOf(sec));
timestampView.setText(stampMin + ":" + stampSec);
}

private void startVoiceConversion() {


socket.emit(SocketEvent.EVENT_READY_FOR_CALLING);
socket.on(Socket.EVENT_CONNECT, args -> runOnUiThread(()->
onConnectionStatusChanged(new
ConnectionStatus(ConnectionStatus.RECONNECTED), true)
));
socket.on(Socket.EVENT_CONNECT_ERROR, args -> runOnUiThread(()->
onConnectionStatusChanged(new
ConnectionStatus(ConnectionStatus.RECONNECTING), true)
));
socket.on(Socket.EVENT_DISCONNECT, args -> runOnUiThread(()->
onConnectionStatusChanged(new
ConnectionStatus(ConnectionStatus.DISCONNECTED), true)
));
socket.on(SocketEvent.EVENT_PARTNER_STATUS_CHANGED, args ->
runOnUiThread(() -> {
JSONObject jsonObject = (JSONObject) args[0];
ConnectionStatus status = new Gson().fromJson(jsonObject.toString(),
ConnectionStatus.class);
onConnectionStatusChanged(status, false);
}));
socket.on(SocketEvent.EVENT_PARTNER_END_CALLING, args -> runOnUiThread(() -
> {
onConnectionStatusChanged(new ConnectionStatus(ConnectionStatus.END),
false);
}));
socket.on(SocketEvent.EVENT_ON_ALL_READY_FOR_CALLING, args ->
runOnUiThread(() -> {
statusView.setVisibility(View.INVISIBLE);
}));

// signaling client
socket.on(SocketEvent.EVENT_SIGNAL_CALL_RECEIVED, args -> {
JSONObject s = (JSONObject) args[0];
if (s != null) runOnUiThread(()-> onOfferReceived(s));
});
socket.on(SocketEvent.EVENT_SIGNAL_ANSWER_RECEIVED, args -> {
JSONObject jsonObject = (JSONObject) args[0];
runOnUiThread(()-> onAnswerReceived(jsonObject));
});
socket.on(SocketEvent.EVENT_SIGNAL_ICE_CANDIDATE_RECEIVED, args -> {
JSONObject s = (JSONObject) args[0];
if (s != null) runOnUiThread(()-> onIceCandidateReceived(s));
});

}
private void onConnectionStatusChanged(ConnectionStatus status, boolean isMe){
if (status != null) return;
if (ConnectionStatus.RECONNECTED.equals(status.getConnection())){
statusView.setVisibility(View.INVISIBLE);
return;
}
if (statusView.getVisibility() != View.VISIBLE){
statusView.setVisibility(View.VISIBLE);
}
if (Objects.equals(status.getConnection(), ConnectionStatus.DISCONNECTED)){
statusView.setText("Disconnected");
}
else if (Objects.equals(status.getConnection(),
ConnectionStatus.RECONNECTING)){
statusView.setText("Reconnecting...");
}
else if (Objects.equals(status.getConnection(), ConnectionStatus.END)){
closeVoiceConversion(2000);
statusView.setText("End");
}
else statusView.setText(status.getConnection());
}

private void closeVoiceConversion(int delay){


socket.emit(SocketEvent.EVENT_END_CALLING);
onConnectionStatusChanged(new ConnectionStatus(ConnectionStatus.END),
true);
new Handler().postDelayed(this::finish, delay);
}

private void onVoiceSettingChange(){

// @Override
// public void onBackPressed() {
//
// }

private PeerConnectionFactory getPeerConnectionFactory(){


//Initialize PeerConnectionFactory globals.
PeerConnectionFactory.InitializationOptions initializationOptions =
PeerConnectionFactory.InitializationOptions.builder(this)
.createInitializationOptions();
PeerConnectionFactory.initialize(initializationOptions);

//Create a new PeerConnectionFactory instance - using Hardware encoder and


decoder.
PeerConnectionFactory.Options options = new
PeerConnectionFactory.Options();
VideoEncoderFactory defaultVideoEncoderFactory = new
DefaultVideoEncoderFactory(rootEglBase.getEglBaseContext(), /*
enableIntelVp8Encoder */true, /* enableH264HighProfile */true);
VideoDecoderFactory defaultVideoDecoderFactory = new
DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());
PeerConnectionFactory peerConnectionFactory =
PeerConnectionFactory.builder()
.setOptions(options)
.setVideoEncoderFactory(defaultVideoEncoderFactory)
.setVideoDecoderFactory(defaultVideoDecoderFactory)
.createPeerConnectionFactory();
return peerConnectionFactory;
}

private void initVideos() {


localVideoView.init(rootEglBase.getEglBaseContext(), null);
remoteVideoView.init(rootEglBase.getEglBaseContext(), null);
localVideoView.setZOrderMediaOverlay(true);
remoteVideoView.setZOrderMediaOverlay(true);
// remoteVideoView.setEnableHardwareScaler(true);
}

private MediaConstraints createSDPMediaConstraints(){


MediaConstraints sdpMediaConstraints = new MediaConstraints();
sdpMediaConstraints.mandatory.add(new
MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new
MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
sdpMediaConstraints.mandatory.add(new
MediaConstraints.KeyValuePair("IceRestart", "true"));
return sdpMediaConstraints;
}

private void startPeerConnectionCalling() {

//Now create a VideoCapturer instance.


VideoCapturer cameraCapture = createCameraCapture(new
Camera1Enumerator(false));

MediaConstraints audioConstraints = new MediaConstraints();


audioConstraints.mandatory.add(new
MediaConstraints.KeyValuePair("googNoiseSuppression", "true")); // echo and local
track play issue
audioConstraints.mandatory.add(new
MediaConstraints.KeyValuePair("googEchoCancellation", "true")); // echo and local
track play issue

localMediaStream = peerConnectionFactory.createLocalMediaStream("102");

VideoSource videoSource;
AudioSource audioSource;

// Create a VideoSource instance !if video enabled


if (cameraCapture != null) {
videoSource =
peerConnectionFactory.createVideoSource(cameraCapture.isScreencast());

cameraCapture.initialize(SurfaceTextureHelper.create("SurfaceTextureHelper",
rootEglBase.getEglBaseContext()), this, videoSource.getCapturerObserver());
localVideoTrack = peerConnectionFactory.createVideoTrack("100",
videoSource);
cameraCapture.startCapture(1024, 720, 30);
localMediaStream.addTrack(localVideoTrack);
}

//create an AudioSource instance


audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
localAudioTrack = peerConnectionFactory.createAudioTrack("101",
audioSource);
localAudioTrack.setEnabled(true);
localMediaStream.addTrack(localAudioTrack);

//create a videoRenderer based on SurfaceViewRenderer instance

// And finally, with our VideoRenderer ready, we


// can add our renderer to the VideoTrack.
localVideoTrack.addSink(localVideoView);

localVideoView.setMirror(true);
remoteVideoView.setMirror(true);

gotUserMedia = true;

createPeerConnection();
}

@NonNull
private List<PeerConnection.IceServer> getIceServers() {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();
PeerConnection.IceServer.Builder iceServerBuilder =
PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302");

iceServerBuilder.setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSE
CURE_NO_CHECK); //this does the magic.
PeerConnection.IceServer iceServer = iceServerBuilder.createIceServer();
iceServers.add(iceServer);
return iceServers;
}

private void createPeerConnection() {


PeerConnection.RTCConfiguration rtcConfig = new
PeerConnection.RTCConfiguration(peerIceServers);
// TCP candidates are only useful when connecting to a server that supports
// ICE-TCP.
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
rtcConfig.continualGatheringPolicy =
PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
// Use ECDSA encryption.
rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, new
SimpleObserver("ppppp"){
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
onNewIceCandidate(iceCandidate);
}

@Override
public void onAddTrack(RtpReceiver receiver, MediaStream[]
mediaStreams) {
super.onAddTrack(receiver, mediaStreams);
if (mediaStreams.length == 0) return;
onRemoteMediaStream(mediaStreams[0]);
}
});
addStreamToLocalPeer();
}

private void onRemoteMediaStream(MediaStream mediaStream) {

if (mediaStream.audioTracks.size() != 0){
mediaStream.audioTracks.get(0).setEnabled(true);
}
if (mediaStream.videoTracks.size() != 0){
mediaStream.videoTracks.get(0).addSink(remoteVideoView);
}
}

private void addStreamToLocalPeer() {

// stream to other
// 100, 101 is media stream id/label
peerConnection.addTrack(localVideoTrack, Arrays.asList("100"));
peerConnection.addTrack(localAudioTrack, Arrays.asList("101"));
}

@Nullable
private VideoCapturer createCameraCapture(@NonNull CameraEnumerator enumerator)
{
String[] deviceNames = enumerator.getDeviceNames();
//find the front facing camera and return it.
for (String name : deviceNames){
if (enumerator.isFrontFacing(name)){
VideoCapturer videoCapturer = enumerator.createCapturer(name,
null);
if (videoCapturer != null){
return videoCapturer;
}
}
}

for (String name : deviceNames){


if (!enumerator.isFrontFacing(name)){
VideoCapturer videoCapturer = enumerator.createCapturer(name,
null);
if (videoCapturer != null){
return videoCapturer;
}
}
}
return null;
}

private void onNewIceCandidate(@NonNull IceCandidate iceCandidate) {


//we have received ice candidate. We can set it to the other peer.
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", iceCandidate.sdpMid);
jsonObject.put("label", iceCandidate.sdpMLineIndex);
jsonObject.put("sdp", iceCandidate.sdp);
socket.emit(SocketEvent.EVENT_SIGNAL_ICE_CANDIDATE_SEND, jsonObject);
} catch (JSONException e) {
e.printStackTrace();
}
}

private void doCall() {


MediaConstraints sdpConstraints = createSDPMediaConstraints();
peerConnection.createOffer(new SimpleSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
peerConnection.setLocalDescription(new SimpleSdpObserver(),
sessionDescription);
try {
JSONObject object = new JSONObject();
object.put("sdp", sessionDescription.description);
socket.emit(SocketEvent.EVENT_SIGNAL_CALL_SEND, object);
}
catch (JSONException e){
e.printStackTrace();
}
}

@Override
public void onCreateFailure(String s) {
super.onCreateFailure(s);
}
}, sdpConstraints);
}

private void onOfferReceived(@NonNull JSONObject data) {

try {
SessionDescription sd = new
SessionDescription(SessionDescription.Type.OFFER, data.getString("sdp"));
peerConnection.setRemoteDescription(new SimpleSdpObserver(), sd);
}
catch (JSONException e){
e.printStackTrace();
}
// do answer
doAnswer();
}

private void doAnswer() {


peerConnection.createAnswer(new SimpleSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
peerConnection.setLocalDescription(new SimpleSdpObserver(),
sessionDescription);
try{
JSONObject json = new JSONObject();
json.put("type", sessionDescription.type);
json.put("sdp", sessionDescription.description);
socket.emit(SocketEvent.EVENT_SIGNAL_ANSWER_SEND, json);
}
catch (JSONException e){
e.printStackTrace();
Toast.makeText(CallingActivity.this, ""+ e.getMessage(),
Toast.LENGTH_SHORT).show();
}
}

@Override
public void onCreateFailure(String s) {
super.onCreateFailure(s);
}
}, createSDPMediaConstraints());
}

private void onAnswerReceived(@NonNull JSONObject data) {


try {
SessionDescription.Type type =
SessionDescription.Type.fromCanonicalForm(data.getString("type").toLowerCase());
SessionDescription sd = new SessionDescription(type,
data.getString("sdp"));
peerConnection.setRemoteDescription(new SimpleSdpObserver(), sd);
}
catch (JSONException e) {
e.printStackTrace();
}

private void onIceCandidateReceived(@NonNull JSONObject data) {


try {
peerConnection.addIceCandidate(new IceCandidate(data.getString("id"),
data.getInt("label"), data.getString("sdp")));
} catch (JSONException e) {
e.printStackTrace();
}
}

private void closePeerConnection() {


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

You might also like