com.viettel.ipcclib.WebSocketRTCClient.java Source code

Java tutorial

Introduction

Here is the source code for com.viettel.ipcclib.WebSocketRTCClient.java

Source

/*
 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package com.viettel.ipcclib;

import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;

import com.viettel.ipcclib.constants.ConversationId;
import com.viettel.ipcclib.constants.GsonWrapper;
import com.viettel.ipcclib.constants.MessageType;
import com.viettel.ipcclib.model.AppConfig;
import com.viettel.ipcclib.model.CandidateJ;
import com.viettel.ipcclib.model.Message;
import com.viettel.ipcclib.model.MessageData;
import com.viettel.ipcclib.model.Service;
import com.viettel.ipcclib.util.LooperExecutor;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;

import android.text.TextUtils;
import android.util.Log;

import java.util.LinkedList;
import java.util.List;

import static com.viettel.ipcclib.constants.ConversationId.endVideoCall;
import static com.viettel.ipcclib.constants.ConversationId.existingParticipants;
import static com.viettel.ipcclib.constants.ConversationId.iceCandidate;
import static com.viettel.ipcclib.constants.ConversationId.joinRoom;
import static com.viettel.ipcclib.constants.ConversationId.newParticipantArrived;
import static com.viettel.ipcclib.constants.ConversationId.receiveVideoAnswer;
import static com.viettel.ipcclib.constants.ConversationId.receiveVideoFrom;
import static com.viettel.ipcclib.constants.ConversationId.requestVideoCall;
import static com.viettel.ipcclib.constants.GsonWrapper.getGson;
import static com.viettel.ipcclib.constants.MessageType.CUSTOMER_LOGIN;
import static com.viettel.ipcclib.constants.MessageType.CUSTOMER_RESUMING_REQUEST;
import static com.viettel.ipcclib.constants.MessageType.LEAVE_CONVERSATION;
import static com.viettel.ipcclib.constants.MessageType.MESSAGE;
import static com.viettel.ipcclib.constants.MessageType.PING;
import static com.viettel.ipcclib.constants.MessageType.SEND_IS_CHAT_TYPING;
import static com.viettel.ipcclib.constants.MessageType.VIDEO_CALL;

/**
 * Negotiates signaling for chatting with apprtc.appspot.com "rooms".
 *
 * <p>To use: create an instance of this object (registering a message handler) and
 * call connectToWebsocket().  Once room connection is established
 * onConnectedToRoom() callback with room parameters is invoked.
 * Messages to other party (with local Ice candidates and answer SDP) can
 * be sent after WebSocket connection is established.
 */
public class WebSocketRTCClient implements AppRTCClient, WebSocketChannelClient.WebSocketChannelEvents {

    private static final String TAG = "WebSocketRTCClient";

    public WebSocketRTCClient setDomain(String domain) {
        this.domain = domain;
        return this;
    }

    public enum VideoState {
        WAITING, JOINED, LIVE, ERROR, END
    }

    ;

    //  private enum MessageType {
    //    MESSAGE, LEAVE
    //  }

    ;

    private final LooperExecutor executor;
    private boolean initiator;
    private ConversationId mVideoState = endVideoCall;
    private WebSocketChannelClient wsClient;
    private WebSocketChannelClient.WebSocketConnectionState socketState;
    private RoomConnectionParameters connectionParameters;

    private SignalingEvents signalingEvents;
    private int userId;
    private int userId2;
    private String userName;
    private int conversationId = -1;
    private String domain;
    private int serviceId;

    private MessageData agentData;

    public WebSocketRTCClient(SignalingEvents events, LooperExecutor executor) {
        this.executor = executor;
        this.socketState = WebSocketChannelClient.WebSocketConnectionState.NEW;
        this.signalingEvents = events;

        executor.requestStart();
    }

    // --------------------------------------------------------------------
    // WebSocketChannelEvents interface implementation.
    // All events are called by WebSocketChannelClient on a local looper thread
    // (passed to WebSocket client constructor).
    @Override
    public void onWebSocketMessage(final String msg) {
        //    try {
        Message message = getGson().fromJson(msg, Message.class);
        //      JSONObject json = new JSONObject(msg);

        AppConfig params = message.getParams();
        if (params != null) {
            Log.i(TAG, "Got appConfig" + msg + " parsing into roomParameters");
            //this.roomParametersFetcher.parseAppConfig(msg);

            Log.i(TAG, "app config: " + msg);
            try {
                //          JSONObject appConfig = new JSONObject(msg);

                String result = message.getResult();
                Log.i(TAG, "client debug ");
                if (!result.equals("SUCCESS")) {
                    return;
                }

                //          String params = appConfig.getString("params");
                //          appConfig = new JSONObject(params);
                LinkedList<PeerConnection.IceServer> iceServers = iceServersFromPCConfigJSON(params.getPcConfig());

                AppRTCClient.SignalingParameters signalingParameters = new SignalingParameters(iceServers);

                wsClient.register(connectionParameters.from);
            } catch (JSONException e) {
                signalingEvents.onChannelError("app config JSON parsing error: " + e.toString());
            }
            return;
        }

        //            if (socketState != WebSocketConnectionState.REGISTERED && socketState != WebSocketConnectionState.CONNECTED){
        //                Log.e(TAG, "websocket still in non registered state.");
        //                return;
        //            }
        JsonElement typeJson = message.getType();

        if (typeJson == null || message.getData() == null || !TextUtils.isDigitsOnly(typeJson.getAsString())) {
            return;
        }

        String id = "";
        String response = "";

        MessageData data = message.getData();
        MessageType type = MessageType.getValue(typeJson.getAsInt());
        switch (type) {
        case CUSTOMER_LOGIN:
            //user register
            //            JSONObject userT = json.getJSONObject("data");
            userId = data.getUserId();
            //            userId = userT.getInt("userId");
            //            userName = userT.getString("name");
            userName = data.getName().getAsString();
            //make call;
            socketState = WebSocketChannelClient.WebSocketConnectionState.REGISTERED;
            signalingEvents.onConnected();
            // todo use video
            //        makeCall();
            break;

        case AGENT_INFO_CUSTOMER:
            agentData = message.getData();
            signalingEvents.onAgentJoinConversation(agentData.getFullName());
            break;

        case HAVE_MESSAGE:
            signalingEvents.onMessageCome(data);
            break;

        case MESSAGE:
            conversationId = message.getData().getConversationId();
            signalingEvents.onConversationReady();
            break;

        case SEND_IS_CHAT_TYPING:
            signalingEvents.onAgentTyping(data.getFullName(), data.isTyping());
            break;

        case RESPONSE_AGENT_MISS_CHAT_TO_CUSTOMER:
            signalingEvents.onAgentMissedChat();
            break;

        case VIDEO_CALL: //video call processing
            //            JSONObject dataO = json.getJSONObject("data");
            processVideoCall(message);
            break;

        case AGENT_END_CONVERSATION_CUSTOMER:
            conversationId = -1;
            signalingEvents.onAgentEndConversation(data.getFullName());
            break;

        case RESPONSE_SERVICE_LIST:
            if (data.getServices().isJsonArray()) {

                List<Service> services = GsonWrapper.getGson().fromJson(data.getServices(),
                        new TypeToken<List<Service>>() {
                        }.getType());
                signalingEvents.onServiceListResponse(services);
            }
            break;
        }
        /*
        if(json.has("id")) id = json.getString("id");
            
        if(id.equals("registerResponse")){
            
            response = json.getString("response"); //TODO if not accepted what todo?
            String message = json.getString("message");
            
            if(response.equals("accepted"))      {
                socketState = WebSocketConnectionState.REGISTERED;
            }
            
            else if(response.equals("rejected"))      {
                signalingEvents.onChannelError("register rejected: " + message);
            }
            
            else if(response.equals("skipped")) {
                signalingEvents.onChannelError("register rejected: " + message);                                                                       // Log.e(TAG, "registration was skipped because: "+message);
            }
        }
            
        if(id.equals("registeredUsers")){
            response = json.getString("response");
            signalingEvents.onUserListUpdate(response);
        }
            
        if(id.equals("callResponse")){
            response = json.getString("response");
            
            if(response.startsWith("rejected")) {
                Log.d(TAG, "call got rejected: "+response);
                signalingEvents.onChannelClose();
            }else{
                Log.d(TAG, "sending sdpAnswer: "+response);
                SessionDescription sdp = new SessionDescription(
                        SessionDescription.Type.ANSWER,json.getString("sdpAnswer"));
            
                signalingEvents.onRemoteDescription(sdp);
            }
        }
        if(id.equals("callScreenResponse")){
            response = json.getString("response");
            
            if(response.startsWith("rejected")) {
                Log.d(TAG, "call got rejected: "+response);
                signalingEvents.onChannelScreenClose();
            }else{
                Log.d(TAG, "sending sdpAnswer: "+response);
                SessionDescription sdp = new SessionDescription(
                        SessionDescription.Type.ANSWER,json.getString("sdpAnswer"));
            
                signalingEvents.onRemoteScreenDescription(sdp);
            }
        }
            
        if(id.equals("incomingCall")){
            Log.d(TAG, "incomingCall "+json.toString());
            signalingEvents.onIncomingCall(json.getString("from"));
        }
            
        if(id.equals("incomingScreenCall")){
            Log.d(TAG, "incomingScreenCall "+json.toString());
            signalingEvents.onIncomingScreenCall(json);
        }
            
        if(id.equals("startCommunication")){
            Log.d(TAG, "startCommunication "+json.toString());
            SessionDescription sdp = new SessionDescription(SessionDescription.Type.ANSWER,json.getString("sdpAnswer"));
            signalingEvents.onStartCommunication(sdp);
        }
        if(id.equals("startScreenCommunication")){
            Log.d(TAG, "startScreenCommunication "+json.toString());
            SessionDescription sdp = new SessionDescription(SessionDescription.Type.ANSWER,json.getString("sdpAnswer"));
               // signalingEvents.onStartScreenCommunication(sdp); //remove if not needed!
            signalingEvents.onStartScreenCommunication(sdp);
        }
        if(id.equals("stopCommunication")){
            Log.d(TAG, "stopCommunication "+json.toString());
            signalingEvents.onChannelClose();
        }
        if(id.equals("stopScreenCommunication")){
            Log.d(TAG, "stopCommunication "+json.toString());
            signalingEvents.onChannelScreenClose();
        }
        if(id.equals("iceCandidateScreen")){
            
            JSONObject candidateJson = json.getJSONObject("candidate");
            
            IceCandidate candidate = new IceCandidate(
                    candidateJson.getString("sdpMid"),
                    candidateJson.getInt("sdpMLineIndex"),
                    candidateJson.getString("candidate"));
            
            signalingEvents.onRemoteScreenIceCandidate(candidate);
            
        }
        if(id.equals("iceCandidate")){
            Log.d(TAG, "iceCandidate "+json.toString());
            
            JSONObject candidateJson = json.getJSONObject("candidate");
            
            IceCandidate candidate = new IceCandidate(
                    candidateJson.getString("sdpMid"),
                    candidateJson.getInt("sdpMLineIndex"),
                    candidateJson.getString("candidate"));
            
            signalingEvents.onRemoteIceCandidate(candidate);
        }
            
        if (id.equals("stop")) {
            signalingEvents.onChannelClose();
        }
        if (id.equals("stopScreen")) {
            signalingEvents.onChannelScreenClose();
        }
        */
        //    } catch (JSONException e) {
        //      reportError("WebSocket message JSON parsing error: " + e.toString());
        //    }
    }

    private void processVideoCall(Message message) {
        MessageData dataO = message.getData();
        if (dataO == null) {
            return;
        }

        ConversationId idM = dataO.getId();
        if (idM == null) {
            return;
        }
        switch (idM) {
        case endVideoCall:
            mVideoState = endVideoCall;
            if (socketState == WebSocketChannelClient.WebSocketConnectionState.NEW) {
                signalingEvents.onNoAgentResponse();
            } else {
                signalingEvents.endVideoCall();
            }
            break;

        case joinRoom:
            mVideoState = joinRoom;
            conversationId = dataO.getConversationId();
            joinRoom();
            break;

        case existingParticipants:
            mVideoState = existingParticipants;
            signalingEvents.onReciveCall();
            break;

        case iceCandidate:
            mVideoState = iceCandidate;
            //Log.d(TAG, "iceCandidate "+dataO.toString());
            int name = dataO.getName().getAsInt();
            //              int nameX = dataO.getInt("name");
            //              String candidateJ = dataO.getString("candidate");
            //              String candidateJ = dataO.getCandidate();
            String candidateString = dataO.getCandidate();
            CandidateJ candidateJ = GsonWrapper.getGson().fromJson(candidateString, CandidateJ.class);
            //              JSONObject candidateJson = new JSONObject(candidateJ);
            String sdpMid = candidateJ.getSdpMid();
            int sdpMLineIndex = candidateJ.getSdpMLineIndex();
            String candidateStr = candidateJ.getCandidate();
            //              String sdpMid = candidateJson.getString("sdpMid");
            //              int sdpMLineIndex = candidateJson.getInt("sdpMLineIndex");
            //              String candidateStr = candidateJson.getString("candidate");
            IceCandidate candidate = new IceCandidate(sdpMid, sdpMLineIndex, candidateStr);
            if (name == userId) {
                signalingEvents.onRemoteIceCandidate(candidate);
            } else {
                signalingEvents.onRemoteScreenIceCandidate(candidate);
            }
            break;

        case receiveVideoAnswer:
            mVideoState = receiveVideoAnswer;
            int nameX = dataO.getName().getAsInt();
            //              Log.d(TAG, "sending sdpAnswer: " + dataO.getString("sdpAnswer"));
            SessionDescription sdp = new SessionDescription(
                    //                  SessionDescription.Type.ANSWER, dataO.getString("sdpAnswer"));
                    SessionDescription.Type.ANSWER, dataO.getSdpAnswer());

            if (nameX == userId) {
                signalingEvents.onRemoteDescription(sdp);
            } else {
                signalingEvents.onRemoteScreenDescription(sdp);
            }
            break;

        case newParticipantArrived:
            mVideoState = newParticipantArrived;
            userId2 = dataO.getName().getAsInt();
            signalingEvents.onIncomingScreenCall(message);
            //              userId2 = dataO.getInt("name");
            //              signalingEvents.onIncomingScreenCall(json);
            break;
        }
    }

    // --------------------------------------------------------------------
    // AppRTCClient interface implementation.
    // Asynchronously connect to an AppRTC room URL using supplied connection
    // parameters, retrieves room parameters and connect to WebSocket server.
    @Override
    public void connectToWebsocket(RoomConnectionParameters connectionParameters) {
        this.connectionParameters = connectionParameters;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    connectToWebsocketInternal();
                } catch (Exception e) {
                    reportError("WebSocketerror: " + e.toString());
                }
            }
        });
    }

    public void setServiceId(int serviceId) {
        this.serviceId = serviceId;
    }

    public void sendStopToPeer() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //          JSONObject jsonMessage = new JSONObject();
                    //          jsonPut(jsonMessage, "id", "stop");
                    Message message = new Message().setId("stop");

                    wsClient.send(message);
                } catch (Exception e) {
                    reportError("WebSocketerror: " + e.toString());
                }
            }
        });
    }

    @Override
    public void sendDisconnectToPeer() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                disconnectFromRoomInternal();
            }
        });
    }

    @Override
    public void reconnect() {
        executor.execute(new Runnable() {
            @Override
            public void run() {

                disconnectFromRoomInternal();

                try {
                    connectToWebsocketInternal();
                } catch (Exception e) {
                    reportError("WebSocketerror: " + e.toString());
                }
            }
        });
    }

    @Override
    public void register() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                MessageData data = new MessageData().setVisitorName("").setServiceId("").setConversationId(-1)
                        .setDomain(domain);

                Message message = new Message().setData(data).setService(CUSTOMER_RESUMING_REQUEST);

                wsClient.send(message);
            }
        });
    }

    @Override
    public void login() {
        //    executor.execute(new Runnable() {
        //      @Override
        //      public void run() {
        //        MessageData data = new MessageData()
        //            .setVisitorName("")
        //            .setServiceId("-1")
        //            .setHost("10.61.138.224:8939")
        //            .setConversationId(-1)
        //            .setDomain(domain);
        //
        //        Message message = new Message()
        //            .setData(data)
        //            .setService(CUSTOMER_LOGIN);
        //
        //        wsClient.send(message);
        //      }
        //    });
    }

    @Override
    public void initUser() {
        //get info
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //        JSONObject data = new JSONObject();
                //        WebSocketRTCClient.jsonPut(data, "visitorId", -1);
                //        WebSocketRTCClient.jsonPut(data, "visitorName", "");
                //        WebSocketRTCClient.jsonPut(data, "ip_address", "192.168.0.132:8939");
                //        WebSocketRTCClient.jsonPut(data, "host", "192.168.0.132:8939");
                //        WebSocketRTCClient.jsonPut(data, "os", "");
                //        WebSocketRTCClient.jsonPut(data, "browser", "");
                //        WebSocketRTCClient.jsonPut(data, "device_type", "");
                //        WebSocketRTCClient.jsonPut(data, "domain", "LANT_TEST1");
                //        WebSocketRTCClient.jsonPut(data, "country_name", "");
                //        JSONObject json = new JSONObject();
                //
                //        WebSocketRTCClient.jsonPut(json, "service", 37);
                //        WebSocketRTCClient.jsonPut(json, "data", data);

                // Todo question
                MessageData data = new MessageData().setVisitorId(-1).setVisitorName("")
                        .setIpAddress("192.168.0.132:8939").setHost("192.168.0.132:8939").setOs("").setBrowser("")
                        .setDeviceType("").setConversationId(-1).setDomain(domain).setCountryName("");

                Message message = new Message().setData(data).setService(CUSTOMER_LOGIN);

                wsClient.send(message);
            }
        });

    }

    @Override
    public void makeCall() {
        executor.execute(new Runnable() {
            @Override
            public void run() {

                MessageData data = new MessageData().setServiceId(serviceId + "").setConversationId(conversationId)
                        .setMessage("").setDomain(domain).setId(requestVideoCall);
                Message message = new Message().setData(data).setService(VIDEO_CALL);
                //        JSONObject data = new JSONObject();
                //        WebSocketRTCClient.jsonPut(data, "service_id", serviceId);
                //        WebSocketRTCClient.jsonPut(data, "conversationId", -1);
                //        WebSocketRTCClient.jsonPut(data, "message", "");
                //        WebSocketRTCClient.jsonPut(data, "domain", 14);
                //        WebSocketRTCClient.jsonPut(data, "id", "requestVideoCall");
                //        JSONObject json = new JSONObject();
                //        WebSocketRTCClient.jsonPut(json, "data", data);
                //        WebSocketRTCClient.jsonPut(json, "service", 113);
                //        wsClient.send(json.toString());
                if (wsClient != null)
                    wsClient.send(message);
            }
        });

    }

    @Override
    public void joinRoom() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //        JSONObject data = new JSONObject();
                //        WebSocketRTCClient.jsonPut(data, "service_id", serviceId);
                //        WebSocketRTCClient.jsonPut(data, "conversationId", conversationId);
                //        WebSocketRTCClient.jsonPut(data, "message", "");
                //        WebSocketRTCClient.jsonPut(data, "domain", 14);
                //        WebSocketRTCClient.jsonPut(data, "id", "joinRoom");
                //        JSONObject json = new JSONObject();
                //        WebSocketRTCClient.jsonPut(json, "data", data);
                //        WebSocketRTCClient.jsonPut(json, "service", 113);

                // todo question
                MessageData data = new MessageData().setServiceId(serviceId + "").setConversationId(conversationId)
                        .setMessage("").setDomain(domain).setId(joinRoom);

                Message message = new Message().setData(data).setService(VIDEO_CALL);

                wsClient.send(message);
            }
        });
    }

    // Connects to websocket - function runs on a local looper thread.
    private void connectToWebsocketInternal() {

        String connectionUrl = getConnectionUrl(connectionParameters);

        socketState = WebSocketChannelClient.WebSocketConnectionState.NEW;
        wsClient = new WebSocketChannelClient(executor, this);
        wsClient.connect(connectionUrl);
        socketState = WebSocketChannelClient.WebSocketConnectionState.CONNECTED;
        Log.d(TAG, "wsClient connect " + connectionUrl);

    }

    // Disconnect from room and send bye messages - runs on a local looper thread.
    private void disconnectFromRoomInternal() {
        Log.d(TAG, "Disconnect. Room state: " + socketState);
        //        if (socketState == WebSocketConnectionState.CONNECTED
        //                || socketState == WebSocketConnectionState.NEW
        //                || socketState == WebSocketConnectionState.REGISTERED)
        {
            Log.d(TAG, "Closing room.");
            //      JSONObject data = new JSONObject();
            //      WebSocketRTCClient.jsonPut(data, "service_id", serviceId);
            //      WebSocketRTCClient.jsonPut(data, "conversationId", conversationId);
            //      WebSocketRTCClient.jsonPut(data, "domain", 14);
            //      WebSocketRTCClient.jsonPut(data, "id", "endVideoCall");
            //      JSONObject json = new JSONObject();
            //      WebSocketRTCClient.jsonPut(json, "data", data);
            //      WebSocketRTCClient.jsonPut(json, "service", 113);

            // todo
            MessageData data = new MessageData().setServiceId(serviceId + "").setConversationId(conversationId)
                    .setDomain(domain).setId(endVideoCall);

            Message message = new Message().setData(data).setService(VIDEO_CALL);

            wsClient.send(message);
            //wsClient.disconnect(true);
        }
    }

    // Helper functions to get connection, sendSocketMessage message and leave message URLs
    private String getConnectionUrl(RoomConnectionParameters connectionParameters) {
        return connectionParameters.roomUrl + "/websocket";
    }

    // Return the list of ICE servers described by a WebRTCPeerConnection
    // configuration string.
    public static LinkedList<PeerConnection.IceServer> iceServersFromPCConfigJSON(String pcConfig)
            throws JSONException {
        JSONObject json = new JSONObject(pcConfig);
        Log.d(TAG, "current pcConfig: " + pcConfig);
        LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
        JSONArray iceServersArray = json.getJSONArray("iceServers");
        for (int i = 0; i < iceServersArray.length(); i++) {
            JSONObject iceJson = iceServersArray.getJSONObject(i);

            String username = iceJson.getString("username");
            String password = iceJson.getString("password");
            JSONArray iceUris = iceJson.getJSONArray("urls");

            for (int j = 0; j < iceUris.length(); j++) {
                String uri = iceUris.getString(j);
                Log.d(TAG, "adding ice server: " + uri + " username:" + username + " password:" + password);
                iceServers.add(new PeerConnection.IceServer(uri, username, password));
            }
        }
        return iceServers;
    }

    public void call(final SessionDescription sdp) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //                  if (socketState != WebSocketConnectionState.REGISTERED) {
                //                      reportError("Sending offer SDP in non registered state.");
                //                      return;
                //                  }

                //        JSONObject json = new JSONObject();
                //        JSONObject dataJ = new JSONObject();
                //        jsonPut(dataJ, "service_id", serviceId);
                //        jsonPut(dataJ, "conversationId", conversationId);
                //        jsonPut(dataJ, "domain", 14);
                //        jsonPut(dataJ, "id", "receiveVideoFrom");
                //        jsonPut(dataJ, "sender", userId);
                //        jsonPut(dataJ, "sdpOffer", sdp.description);
                //        jsonPut(json, "service", 113);
                //        jsonPut(json, "data", dataJ);

                MessageData data = new MessageData().setServiceId("" + serviceId).setConversationId(conversationId)
                        .setDomain(domain).setId(receiveVideoFrom).setSender(userId + "")
                        .setSdpOffer(sdp.description);

                // todo
                Message message = new Message().setData(data).setService(VIDEO_CALL);

                wsClient.send(message);

            }
        });
    }

    // Send local answer SDP to the other participant.
    @Override
    public void sendOfferSdp(final SessionDescription sdp, final boolean isScreenSharing) {
        //    if (mVideoState == endVideoCall || mVideoState == cancelVideoCall ) {
        //      return;
        //    }
        executor.execute(new Runnable() {
            @Override
            public void run() {

                //        JSONObject json = new JSONObject();
                //        JSONObject dataJ = new JSONObject();
                //        jsonPut(dataJ, "service_id", serviceId);
                //        jsonPut(dataJ, "conversationId", conversationId);
                //        jsonPut(dataJ, "domain", 14);
                //        jsonPut(dataJ, "id", "receiveVideoFrom");
                //        if (isScreenSharing)
                //          jsonPut(dataJ, "sender", userId2);
                //        else {
                //          jsonPut(dataJ, "sender", userId);
                //        }
                //        jsonPut(dataJ, "sdpOffer", sdp.description);
                //        jsonPut(json, "service", 113);
                //        jsonPut(json, "data", dataJ);

                MessageData data = new MessageData().setServiceId(serviceId + "").setConversationId(conversationId)
                        .setDomain(domain).setId(receiveVideoFrom).setSdpOffer(sdp.description);

                if (isScreenSharing) {
                    data.setSender(userId2 + "");
                } else {
                    data.setSender(userId + "");
                }

                Message message = new Message().setService(VIDEO_CALL).setData(data);
                wsClient.send(message);

            }
        });
    }

    // Send Ice candidate to the other participant.
    @Override
    public void sendLocalIceCandidate(final IceCandidate candidate, final boolean isScreenSharing) {
        //    if (mVideoState == endVideoCall || mVideoState == existingParticipants || mVideoState == cancelVideoCall) {
        //      return;
        //    }
        executor.execute(new Runnable() {
            @Override
            public void run() {

                JSONObject json = new JSONObject();
                JSONObject data = new JSONObject();
                JSONObject candidateJ = new JSONObject();

                jsonPut(candidateJ, "candidate", candidate.sdp);
                jsonPut(candidateJ, "sdpMid", candidate.sdpMid);
                jsonPut(candidateJ, "sdpMLineIndex", candidate.sdpMLineIndex);
                jsonPut(data, "id", "onIceCandidate");
                if (!isScreenSharing)
                    jsonPut(data, "name", userId);
                else
                    jsonPut(data, "name", userId2);
                jsonPut(data, "service_id", serviceId);
                jsonPut(data, "conversationId", conversationId);
                jsonPut(data, "domain", domain);
                jsonPut(data, "candidate", candidateJ);
                jsonPut(json, "service", 113);
                jsonPut(json, "data", data);

                //        CandidateJ candidateJ = new CandidateJ()
                //            .setCandidate(candidate.sdp)
                //            .setSdpMid(candidate.sdpMid)
                //            .setSdpMLineIndex(candidate.sdpMLineIndex);
                //
                //        JsonObject candidateJson = new JsonObject();
                //        candidateJson.addProperty("candidate", candidate.sdp);
                //        candidateJson.addProperty("sdpMid", candidate.sdpMid);
                //        candidateJson.addProperty("sdpMLineIndex", candidate.sdpMLineIndex);
                //
                //        MessageData data = new MessageData()
                //            .setId(onIceCandidate)
                //            .setServiceId(serviceId + "")
                //            .setConversationId(conversationId)
                //            .setDomain(domain)
                //            .setCandidate(getGson().toJson(candidateJson))
                //            ;
                //        if (!isScreenSharing) {
                //          data.setName(getGson().toJsonTree(userId));
                //        } else {
                //          data.setName(getGson().toJsonTree(userId2));
                //        }

                //        Message message = new Message()
                //            .setService(VIDEO_CALL)
                //            .setData(data);
                // Call receiver sends ice candidates to websocket server.
                wsClient.send(json.toString());
            }
        });
    }

    @Override
    public void onWebSocketClose() {
        signalingEvents.onChannelClose();
    }

    @Override
    public void onWebSocketError(String description) {
        reportError("WebSocket error: " + description);
    }

    @Override
    public void onWebSocketConnected() {
        connectToUser(0);
    }

    public void disconnect() {
        if (wsClient != null) {
            wsClient.disconnect();
        }
    }

    private void connectToUser(int runTimeMs) {
        initTurnServer();
        initUser();
        //    login();
        String to = "112";
        //    roomConnectionParameters.initiator = true;
        //    roomConnectionParameters.to = to;
    }

    private void initTurnServer() {
        String dataJsonTurn = "{\n" + "    \"params\": {\n" + "        \"pc_config\": {\n"
                + "            \"iceServers\": [\n" + "                           {\n"
                + "                           \"username\": \"\",\n"
                + "                           \"password\": \"\",\n" + "                           \"urls\": [\n"
                + "                                    \"stun:203.190.170.131:49352\",\n"
                + "                                    \"stun:10.60.96.56:8478?transport=tcp\",\n"
                + "                                    \"stun:stun.l.google.com:19302\",\n"
                + "                                    \"stun:stun1.l.google.com:19302\",\n"
                + "                                    \"stun:stun2.l.google.com:19302\",\n"
                + "                                    \"stun:stun3.l.google.com:19302\",\n"
                + "                                    \"stun:stun4.l.google.com:19302\",\n"
                + "                                    \"stun:stun.ekiga.net\",\n"
                + "                                    \"stun:stun.ideasip.com\",\n"
                + "                                    \"stun:stun.schlund.de\",\n"
                + "                                    \"stun:stun.voiparound.com\",\n"
                + "                                    \"stun:stun.voipbuster.com\",\n"
                + "                                    \"stun:stun.voipstunt.com\",\n"
                + "                                    \"stun:stun.voxgratia.org\",\n"
                + "                                    \"stun:stun.services.mozilla.com\"\n"
                + "                                    ]\n" + "                           },\n"
                + "                           {\n" + "                           \"username\": \"viettel\",\n"
                + "                           \"password\": \"123456aA\",\n"
                + "                           \"urls\": [\n"
                + "                                    \"turn:203.190.170.131:49352\"\n"
                + "                                    ]\n" + "                           }\n"
                + "                           ]\n" + "        }\n" + "    },\n" + "    \"result\": \"SUCCESS\"\n"
                + "}\n";
        JSONObject appConfig = null;
        try {
            appConfig = new JSONObject(dataJsonTurn);
            String result = appConfig.getString("result");
            Log.i(TAG, "client debug ");
            if (!result.equals("SUCCESS")) {
                return;
            }

            String params = appConfig.getString("params");
            appConfig = new JSONObject(params);
            LinkedList<PeerConnection.IceServer> iceServers = WebSocketRTCClient
                    .iceServersFromPCConfigJSON(appConfig.getString("pc_config"));
            AppRTCClient.SignalingParameters signalingParameters = new AppRTCClient.SignalingParameters(iceServers);
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    // --------------------------------------------------------------------
    // Helper functions.
    private void reportError(final String errorMessage) {
        Log.e(TAG, errorMessage);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                if (socketState != WebSocketChannelClient.WebSocketConnectionState.ERROR) {
                    socketState = WebSocketChannelClient.WebSocketConnectionState.ERROR;
                    signalingEvents.onChannelError(errorMessage);
                }
            }
        });
    }

    @Override
    public void sendTextMessage(final String messageText) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                MessageData data = new MessageData().setServiceId(serviceId + "").setConversationId(conversationId)
                        .setMessage(messageText).setUserId(userId).setVisitorId(userId).setDomain(domain)
                        .setHost("messageText");

                Message message = new Message().setService(MESSAGE).setData(data);
                // Call receiver sends ice candidates to websocket server.
                wsClient.send(message);
            }
        });
    }

    public void sendTypingStatus(final boolean typing) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                MessageData data = new MessageData().setServiceId(serviceId + "").setConversationId(conversationId)
                        .setUserId(userId).setUserName(userName).setTyping(typing).setType(MessageType.PING)
                        .setDomain(domain);

                Message message = new Message().setService(SEND_IS_CHAT_TYPING).setData(data);
                // Call receiver sends ice candidates to websocket server.
                wsClient.send(message);
            }
        });
    }

    @Override
    public void leaveConversation() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                MessageData data = new MessageData().setVisitorId(userId).setServiceId("-1")
                        .setConversationId(conversationId);

                Message message = new Message().setService(LEAVE_CONVERSATION).setData(data);
                // Call receiver sends ice candidates to websocket server.
                wsClient.send(message);
            }
        });
    }

    @Override
    public void updateUserInfo(final String username, String email) {

        // todo
        executor.execute(new Runnable() {
            @Override
            public void run() {
                MessageData data = new MessageData().setUserId(userId).setUserName(username).setType(PING)
                        .setTyping(false).setServiceId("" + serviceId).setConversationId(conversationId);

                Message message = new Message().setService(LEAVE_CONVERSATION).setData(data);
                // Call receiver sends ice candidates to websocket server.
                wsClient.send(message);
            }
        });
    }

    // Put a |key|->|value| mapping in |json|.
    public static void jsonPut(JSONObject json, String key, Object value) {
        try {
            json.put(key, value);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

}