package com.sergiopaniegoblanco.webrtcexampleapp.listeners; import android.os.Handler; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import com.neovisionaries.ws.client.ThreadType; import com.neovisionaries.ws.client.WebSocket; import com.neovisionaries.ws.client.WebSocketException; import com.neovisionaries.ws.client.WebSocketFrame; import com.neovisionaries.ws.client.WebSocketListener; import com.neovisionaries.ws.client.WebSocketState; import com.sergiopaniegoblanco.webrtcexampleapp.VideoConferenceActivity; import com.sergiopaniegoblanco.webrtcexampleapp.constants.JSONConstants; import com.sergiopaniegoblanco.webrtcexampleapp.managers.PeersManager; import com.sergiopaniegoblanco.webrtcexampleapp.R; import com.sergiopaniegoblanco.webrtcexampleapp.RemoteParticipant; import com.sergiopaniegoblanco.webrtcexampleapp.observers.CustomSdpObserver; import org.json.JSONException; import org.json.JSONObject; import org.webrtc.EglBase; import org.webrtc.IceCandidate; import org.webrtc.MediaConstraints; import org.webrtc.PeerConnection; import org.webrtc.SessionDescription; import org.webrtc.SurfaceViewRenderer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Created by sergiopaniegoblanco on 02/12/2017. */ public class CustomWebSocketListener implements WebSocketListener { private static final String TAG = "CustomWebSocketAdapter"; private static final String JSON_RPCVERSION = "2.0"; private static final int PING_MESSAGE_INTERVAL = 3; private VideoConferenceActivity videoConferenceActivity; private PeerConnection localPeer; private int id; private List<Map<String, String>> iceCandidatesParams; private Map<String, String> localOfferParams; private String userId; private String sessionName; private String participantName; private LinearLayout views_container; private Map<String, RemoteParticipant> participants; private String remoteParticipantId; private PeersManager peersManager; private String socketAddress; private String token; public CustomWebSocketListener(VideoConferenceActivity videoConferenceActivity, PeersManager peersManager, String sessionName, String participantName, LinearLayout views_container, String socketAddress, String token) { this.videoConferenceActivity = videoConferenceActivity; this.peersManager = peersManager; this.localPeer = peersManager.getLocalPeer(); this.id = 0; this.sessionName = sessionName; this.participantName = participantName; this.views_container = views_container; this.socketAddress = socketAddress; this.iceCandidatesParams = new ArrayList<>(); this.participants = new HashMap<>(); this.token = token; } public Map<String, RemoteParticipant> getParticipants() { return participants; } public String getUserId() { return userId; } public int getId() { return id; } private void updateId() { id++; } @Override public void onStateChanged(WebSocket websocket, WebSocketState newState) throws Exception { Log.i(TAG, "State changed: " + newState.name()); } @Override public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception { Log.i(TAG, "Connected"); pingMessageHandler(websocket); String regex = "(room)+"; String baseAddress = socketAddress.split(regex)[0]; Map<String, String> joinRoomParams = new HashMap<>(); joinRoomParams.put(JSONConstants.METADATA, "{\"clientData\": \"" + participantName + "\"}"); joinRoomParams.put("recorder", "false"); joinRoomParams.put("secret", "MY_SECRET"); joinRoomParams.put("session", sessionName); joinRoomParams.put("token", token); sendJson(websocket, "joinRoom", joinRoomParams); if (localOfferParams != null) { sendJson(websocket, "publishVideo", localOfferParams); } } private void pingMessageHandler(final WebSocket webSocket) { long initialDelay = 0L; ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { Map<String, String> pingParams = new HashMap<>(); if (id == 0) { pingParams.put("interval", "3000"); } sendJson(webSocket, "ping", pingParams); } }, initialDelay, PING_MESSAGE_INTERVAL, TimeUnit.SECONDS); } @Override public void onConnectError(WebSocket websocket, WebSocketException cause) throws Exception { Log.i(TAG, "Connect error: " + cause); } @Override public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { Log.i(TAG, "Disconnected " + serverCloseFrame.getCloseReason() + " " + clientCloseFrame.getCloseReason() + " " + closedByServer); } @Override public void onFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Frame"); } @Override public void onContinuationFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Continuation Frame"); } @Override public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Text Frame"); } @Override public void onBinaryFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Binary Frame"); } @Override public void onCloseFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Close Frame"); } @Override public void onPingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Ping Frame"); } @Override public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Pong Frame"); } @Override public void onTextMessage(final WebSocket websocket, String text) throws Exception { Log.i(TAG, "Text Message " + text); JSONObject json = new JSONObject(text); if (json.has(JSONConstants.RESULT)) { handleResult(websocket, json); } else { handleMethod(websocket, json); } } private void handleResult(final WebSocket webSocket, JSONObject json) throws JSONException { JSONObject result = new JSONObject(json.getString(JSONConstants.RESULT)); if (result.has(JSONConstants.SDP_ANSWER)) { saveAnswer(result); } else if (result.has(JSONConstants.SESSION_ID)) { if (result.has(JSONConstants.VALUE)) { if (result.getJSONArray(JSONConstants.VALUE).length() > 0) { addParticipantsAlreadyInRoom(result, webSocket); } this.userId = result.getString(JSONConstants.ID); for (Map<String, String> iceCandidate : iceCandidatesParams) { iceCandidate.put("endpointName", this.userId); sendJson(webSocket, "onIceCandidate", iceCandidate); } } } else if (result.has(JSONConstants.VALUE)) { Log.i(TAG, "pong"); } else { Log.e(TAG, "Unrecognized " + result); } } private void addParticipantsAlreadyInRoom(JSONObject result, final WebSocket webSocket) throws JSONException { for (int i = 0; i < result.getJSONArray(JSONConstants.VALUE).length(); i++) { remoteParticipantId = result.getJSONArray(JSONConstants.VALUE).getJSONObject(i).getString(JSONConstants.ID); final RemoteParticipant remoteParticipant = new RemoteParticipant(); remoteParticipant.setId(remoteParticipantId); participants.put(remoteParticipantId, remoteParticipant); createVideoView(remoteParticipant); setRemoteParticipantName(new JSONObject(result.getJSONArray(JSONConstants.VALUE).getJSONObject(i).getString(JSONConstants.METADATA)).getString("clientData"), remoteParticipant); peersManager.createRemotePeerConnection(remoteParticipant); remoteParticipant.getPeerConnection().createOffer(new CustomSdpObserver("remoteCreateOffer") { @Override public void onCreateSuccess(SessionDescription sessionDescription) { super.onCreateSuccess(sessionDescription); remoteParticipant.getPeerConnection().setLocalDescription(new CustomSdpObserver("remoteSetLocalDesc"), sessionDescription); Map<String, String> remoteOfferParams = new HashMap<>(); remoteOfferParams.put("sdpOffer", sessionDescription.description); remoteOfferParams.put("sender", remoteParticipantId + "_CAMERA"); sendJson(webSocket, "receiveVideoFrom", remoteOfferParams); } }, new MediaConstraints()); } } private void handleMethod(final WebSocket webSocket, JSONObject json) throws JSONException { if(!json.has(JSONConstants.PARAMS)) { Log.e(TAG, "No params"); } else { final JSONObject params = new JSONObject(json.getString(JSONConstants.PARAMS)); String method = json.getString(JSONConstants.METHOD); switch (method) { case JSONConstants.ICE_CANDIDATE: iceCandidateMethod(params); break; case JSONConstants.PARTICIPANT_JOINED: participantJoinedMethod(params); break; case JSONConstants.PARTICIPANT_PUBLISHED: participantPublishedMethod(params, webSocket); break; case JSONConstants.PARTICIPANT_LEFT: participantLeftMethod(params); break; default: throw new JSONException("Can't understand method: " + method); } } } private void iceCandidateMethod(JSONObject params) throws JSONException { if (params.getString("endpointName").equals(userId)) { saveIceCandidate(params, null); } else { saveIceCandidate(params, params.getString("endpointName")); } } private void participantJoinedMethod(JSONObject params) throws JSONException { final RemoteParticipant remoteParticipant = new RemoteParticipant(); remoteParticipant.setId(params.getString(JSONConstants.ID)); participants.put(params.getString(JSONConstants.ID), remoteParticipant); createVideoView(remoteParticipant); setRemoteParticipantName(new JSONObject(params.getString(JSONConstants.METADATA)).getString("clientData"), remoteParticipant); peersManager.createRemotePeerConnection(remoteParticipant); } private void createVideoView(final RemoteParticipant remoteParticipant) { Handler mainHandler = new Handler(videoConferenceActivity.getMainLooper()); Runnable myRunnable = new Runnable() { @Override public void run() { View rowView = videoConferenceActivity.getLayoutInflater().inflate(R.layout.peer_video, null); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); lp.setMargins(0, 0, 0, 20); rowView.setLayoutParams(lp); int rowId = View.generateViewId(); rowView.setId(rowId); views_container.addView(rowView); SurfaceViewRenderer videoView = (SurfaceViewRenderer)((ViewGroup)rowView).getChildAt(0); remoteParticipant.setVideoView(videoView); videoView.setMirror(false); EglBase rootEglBase = EglBase.create(); videoView.init(rootEglBase.getEglBaseContext(), null); videoView.setZOrderMediaOverlay(true); View textView = ((ViewGroup)rowView).getChildAt(1); remoteParticipant.setParticipantNameText((TextView) textView); remoteParticipant.setView(rowView); } }; mainHandler.post(myRunnable); } private void participantPublishedMethod(JSONObject params, final WebSocket webSocket) throws JSONException { remoteParticipantId = params.getString(JSONConstants.ID); RemoteParticipant remoteParticipantPublished = participants.get(remoteParticipantId); remoteParticipantPublished.getPeerConnection().createOffer(new CustomSdpObserver("remoteCreateOffer", remoteParticipantPublished) { @Override public void onCreateSuccess(SessionDescription sessionDescription) { super.onCreateSuccess(sessionDescription); getRemoteParticipant().getPeerConnection().setLocalDescription(new CustomSdpObserver("remoteSetLocalDesc"), sessionDescription); Map<String, String> remoteOfferParams = new HashMap<>(); remoteOfferParams.put("sdpOffer", sessionDescription.description); remoteOfferParams.put("sender", getRemoteParticipant().getId() + "_webcam"); sendJson(webSocket, "receiveVideoFrom", remoteOfferParams); } }, new MediaConstraints()); } private void participantLeftMethod(JSONObject params) throws JSONException { final String participantId = params.getString("name"); participants.get(participantId).getPeerConnection().close(); Handler mainHandler = new Handler(videoConferenceActivity.getMainLooper()); Runnable myRunnable = new Runnable() { @Override public void run() { views_container.removeView(participants.get(participantId).getView()); } }; mainHandler.post(myRunnable); RemoteParticipant remoteParticipantToDelete = participants.get(participantId); participants.remove(remoteParticipantToDelete); } private void setRemoteParticipantName(final String name, final RemoteParticipant participant) { Handler mainHandler = new Handler(videoConferenceActivity.getMainLooper()); Runnable myRunnable = new Runnable() { @Override public void run() { videoConferenceActivity.setRemoteParticipantName(name, participant); } }; mainHandler.post(myRunnable); } @Override public void onBinaryMessage(WebSocket websocket, byte[] binary) throws Exception { Log.i(TAG, "Binary Message"); } @Override public void onSendingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Sending Frame"); } @Override public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Frame sent"); } @Override public void onFrameUnsent(WebSocket websocket, WebSocketFrame frame) throws Exception { Log.i(TAG, "Frame unsent"); } @Override public void onThreadCreated(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception { Log.i(TAG, "Thread created"); } @Override public void onThreadStarted(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception { Log.i(TAG, "Thread started"); } @Override public void onThreadStopping(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception { Log.i(TAG, "Thread stopping"); } @Override public void onError(WebSocket websocket, WebSocketException cause) throws Exception { Log.i(TAG, "Error! " + cause); } @Override public void onFrameError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) throws Exception { Log.i(TAG, "Frame error"); } @Override public void onMessageError(WebSocket websocket, WebSocketException cause, List<WebSocketFrame> frames) throws Exception { Log.i(TAG, "Message error! "+ cause); } @Override public void onMessageDecompressionError(WebSocket websocket, WebSocketException cause, byte[] compressed) throws Exception { Log.i(TAG, "Message Decompression Error"); } @Override public void onTextMessageError(WebSocket websocket, WebSocketException cause, byte[] data) throws Exception { Log.i(TAG, "Text Message Error! " + cause); } @Override public void onSendError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) throws Exception { Log.i(TAG, "Send Error! " + cause); } @Override public void onUnexpectedError(WebSocket websocket, WebSocketException cause) throws Exception { Log.i(TAG, "Unexpected error! " + cause); } @Override public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception { Log.i(TAG, "Handle callback error! " + cause); } @Override public void onSendingHandshake(WebSocket websocket, String requestLine, List<String[]> headers) throws Exception { Log.i(TAG, "Sending Handshake! Hello!"); } private void saveIceCandidate(JSONObject json, String endPointName) throws JSONException { IceCandidate iceCandidate = new IceCandidate(json.getString("sdpMid"), Integer.parseInt(json.getString("sdpMLineIndex")), json.getString("candidate")); if (endPointName == null) { localPeer.addIceCandidate(iceCandidate); } else { participants.get(endPointName).getPeerConnection().addIceCandidate(iceCandidate); } } private void saveAnswer(JSONObject json) throws JSONException { SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.ANSWER, json.getString("sdpAnswer")); if (localPeer.getRemoteDescription() == null) { localPeer.setRemoteDescription(new CustomSdpObserver("localSetRemoteDesc"), sessionDescription); } else { participants.get(remoteParticipantId).getPeerConnection().setRemoteDescription(new CustomSdpObserver("remoteSetRemoteDesc"), sessionDescription); } } public void sendJson(WebSocket webSocket, String method, Map<String, String> params) { try { JSONObject paramsJson = new JSONObject(); for (Map.Entry<String, String> param : params.entrySet()) { paramsJson.put(param.getKey(), param.getValue()); } JSONObject jsonObject = new JSONObject(); if (method.equals(JSONConstants.JOIN_ROOM)) { jsonObject.put(JSONConstants.ID, 1) .put(JSONConstants.PARAMS, paramsJson); } else if (paramsJson.length() > 0) { jsonObject.put(JSONConstants.ID, getId()) .put(JSONConstants.PARAMS, paramsJson); } else { jsonObject.put(JSONConstants.ID, getId()); } jsonObject.put("jsonrpc", JSON_RPCVERSION) .put(JSONConstants.METHOD, method); String jsonString = jsonObject.toString(); updateId(); webSocket.sendText(jsonString); } catch (JSONException e) { Log.i(TAG, "JSONException raised on sendJson"); } } public void addIceCandidate(Map<String, String> iceCandidateParams) { iceCandidatesParams.add(iceCandidateParams); } public void setLocalOfferParams(Map<String, String> offerParams) { this.localOfferParams = offerParams; } }