/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.chat.video; import com.liferay.portal.kernel.json.JSONFactoryUtil; import com.liferay.portal.kernel.json.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Manages <a href="http://en.wikipedia.org/wiki/WebRTC">Web Real-Time * Communication</a> (WebRTC) clients by connecting them, checking and updating * client connection states, and managing client mail. * * @author Philippe Proulx */ public class WebRTCManager { public void answer( long sourceUserId, long destinationUserId, boolean answer) { addWebRTCClient(sourceUserId); if (!hasAvailableWebRTCClient(sourceUserId)) { return; } if (!hasAvailableWebRTCClient(destinationUserId)) { pushErrorWebRTCMail( destinationUserId, sourceUserId, "unavailableUser"); return; } WebRTCClient sourceWebRTCClient = getWebRTCClient(sourceUserId); WebRTCClient destinationWebRTCClient = getWebRTCClient( destinationUserId); if (!isValidWebRTCConnectionState( sourceWebRTCClient, destinationWebRTCClient, WebRTCConnection.State.INITIATED)) { pushErrorWebRTCMail( destinationUserId, sourceUserId, "invalidState"); return; } WebRTCConnection webRTCConnection = sourceWebRTCClient.getWebRTCConnection(destinationWebRTCClient); WebRTCClient webRTCConnectionSourceWebRTCClient = webRTCConnection.getSourceWebRTCClient(); if (webRTCConnectionSourceWebRTCClient == sourceWebRTCClient) { pushErrorWebRTCMail( destinationUserId, sourceUserId, "cannotAnswer"); return; } if (answer) { webRTCConnection.setState(WebRTCConnection.State.CONNECTED); } else { sourceWebRTCClient.removeBilateralWebRTCConnection( destinationWebRTCClient); } JSONObject messageJSONObject = JSONFactoryUtil.createJSONObject(); messageJSONObject.put("answer", answer); messageJSONObject.put("type", "answer"); pushConnectionStateWebRTCMail( sourceWebRTCClient, destinationWebRTCClient, messageJSONObject); } public void call(long sourceUserId, long destinationUserId) { addWebRTCClient(sourceUserId); if (!hasAvailableWebRTCClient(sourceUserId)) { return; } if (!hasAvailableWebRTCClient(destinationUserId)) { pushErrorWebRTCMail( destinationUserId, sourceUserId, "unavailableUser"); return; } WebRTCClient sourceWebRTCClient = getWebRTCClient(sourceUserId); WebRTCClient destinationWebRTCClient = getWebRTCClient( destinationUserId); if (sourceWebRTCClient.hasWebRTCConnection(destinationWebRTCClient) || destinationWebRTCClient.hasWebRTCConnection(sourceWebRTCClient)) { pushErrorWebRTCMail( destinationUserId, sourceUserId, "existingConnection"); return; } WebRTCConnection webRTCConnection = new WebRTCConnection( sourceWebRTCClient); webRTCConnection.setState(WebRTCConnection.State.INITIATED); destinationWebRTCClient.addWebRTCConnection( sourceWebRTCClient, webRTCConnection); sourceWebRTCClient.addWebRTCConnection( destinationWebRTCClient, webRTCConnection); JSONObject messageJSONObject = JSONFactoryUtil.createJSONObject(); messageJSONObject.put("type", "call"); pushConnectionStateWebRTCMail( sourceWebRTCClient, destinationWebRTCClient, messageJSONObject); } /** * Checks the presence of all registered WebRTC clients. This method should * be called by a message listener. */ public void checkWebRTCClients() { long time = System.currentTimeMillis(); for (long userId : _webRTCClients.keySet()) { WebRTCClient webRTCClient = getWebRTCClient(userId); long presenceDurationTime = time - webRTCClient.getPresenceTime(); if (presenceDurationTime > _PRESENCE_TIMEOUT_DURATION_TIME) { resetWebRTCClient(userId); removeWebRTCClient(userId); } } } /** * Checks the WebRTC client connection states for timeout handling. This * method should be called by a message listener. */ public void checkWebRTCConnectionsStates() { for (WebRTCClient webRTCClient : _webRTCClients.values()) { for (WebRTCClient otherWebRTCClient : webRTCClient.getWebRTCClients()) { WebRTCConnection webRTCConnection = webRTCClient.getWebRTCConnection(otherWebRTCClient); if (webRTCConnection.getState() != WebRTCConnection.State.INITIATED) { continue; } long initiatedDurationTime = webRTCConnection.getInitiatedDurationTime(); if (initiatedDurationTime <= _CONNECTION_TIMEOUT_DURATION_TIME) { continue; } webRTCClient.removeBilateralWebRTCConnection(otherWebRTCClient); pushLostConnectionStateWebRTCMail( webRTCClient, otherWebRTCClient, "timeout"); pushLostConnectionStateWebRTCMail( otherWebRTCClient, webRTCClient, "timeout"); } } } public List<Long> getAvailableWebRTCClientIds() { List<Long> availableUserIds = new ArrayList<>(); for (long userId : _webRTCClients.keySet()) { if (hasAvailableWebRTCClient(userId)) { availableUserIds.add(userId); } } return availableUserIds; } public WebRTCClient getWebRTCClient(long userId) { return _webRTCClients.get(userId); } public void hangUp(long sourceUserId, long destinationUserId) { WebRTCClient sourceWebRTCClient = getWebRTCClient(sourceUserId); if (sourceWebRTCClient == null) { return; } WebRTCClient destinationWebRTCClient = getWebRTCClient( destinationUserId); if (destinationWebRTCClient == null) { return; } if (sourceWebRTCClient.hasWebRTCConnection(destinationWebRTCClient)) { sourceWebRTCClient.removeBilateralWebRTCConnection( destinationWebRTCClient); pushLostConnectionStateWebRTCMail( sourceWebRTCClient, destinationWebRTCClient, "hangUp"); pushLostConnectionStateWebRTCMail( destinationWebRTCClient, sourceWebRTCClient, "hangUp"); } } public boolean hasAvailableWebRTCClient(long userId) { WebRTCClient webRTCClient = _webRTCClients.get(userId); if (webRTCClient == null) { return false; } return webRTCClient.isAvailable(); } public void pushDescriptionWebRTCSDPMail( long sourceUserId, long destinationUserId, String description) { JSONObject messageJSONObject = JSONFactoryUtil.createJSONObject(); messageJSONObject.put("description", description); WebRTCMail webRTCMail = new DescriptionWebRTCSDPMail( sourceUserId, messageJSONObject); pushWebRTCMail(sourceUserId, destinationUserId, webRTCMail); } public void pushICECandidateWebRTCMail( long sourceUserId, long destinationUserId, String candidate) { JSONObject messageJSONObject = JSONFactoryUtil.createJSONObject(); messageJSONObject.put("candidate", candidate); WebRTCMail webRTCMail = new ICECandidateWebRTCMail( sourceUserId, messageJSONObject); pushWebRTCMail(sourceUserId, destinationUserId, webRTCMail); } public void removeWebRTCClient(long userId) { WebRTCClient webRTCClient = getWebRTCClient(userId); if (webRTCClient == null) { return; } webRTCClient.removeBilateralWebRTCConnections(); _webRTCClients.remove(userId); } public void resetWebRTCClient(long userId) { WebRTCClient webRTCClient = getWebRTCClient(userId); if (webRTCClient == null) { return; } Set<WebRTCClient> webRTCClients = webRTCClient.getWebRTCClients(); for (WebRTCClient otherWebRTCClient : webRTCClients) { WebRTCConnection webRTCConnection = webRTCClient.getWebRTCConnection(otherWebRTCClient); if (webRTCConnection == null) { continue; } WebRTCConnection.State state = webRTCConnection.getState(); if (state != WebRTCConnection.State.DISCONNECTED) { pushLostConnectionStateWebRTCMail( webRTCClient, otherWebRTCClient, "reset"); } } webRTCClient.reset(); webRTCClient.updatePresenceTime(); } public void updateWebRTCClientAvailability(long userId, boolean available) { addWebRTCClient(userId); WebRTCClient webRTCClient = getWebRTCClient(userId); webRTCClient.setAvailable(available); } public void updateWebRTCClientPresence(long userId) { WebRTCClient webRTCClient = getWebRTCClient(userId); if (webRTCClient == null) { return; } webRTCClient.updatePresenceTime(); } protected void addWebRTCClient(long userId) { if (!_webRTCClients.containsKey(userId)) { _webRTCClients.put(userId, new WebRTCClient(userId)); } } protected boolean isValidWebRTCConnectionState( WebRTCClient webRTCClient1, WebRTCClient webRTCClient2, WebRTCConnection.State state) { WebRTCConnection webRTCClient1TowebRTCClient2WebRTCConnection = webRTCClient1.getWebRTCConnection(webRTCClient2); if (webRTCClient1TowebRTCClient2WebRTCConnection == null) { return false; } WebRTCConnection webRTCClient2TowebRTCClient1WebRTCConnection = webRTCClient2.getWebRTCConnection(webRTCClient1); if (webRTCClient2TowebRTCClient1WebRTCConnection == null) { return false; } if (webRTCClient1TowebRTCClient2WebRTCConnection != webRTCClient2TowebRTCClient1WebRTCConnection) { return false; } if (webRTCClient1TowebRTCClient2WebRTCConnection.getState() != state) { return false; } return true; } protected void pushConnectionStateWebRTCMail( WebRTCClient sourceWebRTCClient, WebRTCClient destinationWebRTCClient, JSONObject messageJSONObject) { ConnectionStateWebRTCMail connectionStateWebRTCMail = new ConnectionStateWebRTCMail( sourceWebRTCClient.getUserId(), messageJSONObject); WebRTCMailbox destinationWebRTCMailbox = destinationWebRTCClient.getOutgoingWebRTCMailbox(); destinationWebRTCMailbox.pushWebRTCMail(connectionStateWebRTCMail); } protected void pushErrorWebRTCMail( long sourceUserId, long destinationUserId, String errorId) { WebRTCClient destinationWebRTCClient = getWebRTCClient( destinationUserId); WebRTCMailbox destinationOutgoingWebRTCMailbox = destinationWebRTCClient.getOutgoingWebRTCMailbox(); JSONObject messageJSONObject = JSONFactoryUtil.createJSONObject(); messageJSONObject.put("id", errorId); WebRTCMail errorWebRTCMail = new ErrorWebRTCMail( sourceUserId, messageJSONObject); destinationOutgoingWebRTCMailbox.pushWebRTCMail(errorWebRTCMail); } protected void pushLostConnectionStateWebRTCMail( WebRTCClient sourceWebRTCClient, WebRTCClient destinationWebRTCClient, String reason) { JSONObject messageJSONObject = JSONFactoryUtil.createJSONObject(); messageJSONObject.put("reason", reason); messageJSONObject.put("status", "lost"); messageJSONObject.put("type", "status"); pushConnectionStateWebRTCMail( sourceWebRTCClient, destinationWebRTCClient, messageJSONObject); } protected void pushWebRTCMail( long sourceUserId, long destinationUserId, WebRTCMail webRTCMail) { WebRTCClient sourceWebRTCClient = getWebRTCClient(sourceUserId); WebRTCClient destinationWebRTCClient = getWebRTCClient( destinationUserId); if ((sourceWebRTCClient == null) || (destinationWebRTCClient == null)) { return; } if (!isValidWebRTCConnectionState( sourceWebRTCClient, destinationWebRTCClient, WebRTCConnection.State.CONNECTED)) { pushErrorWebRTCMail( destinationUserId, sourceUserId, "invalidState"); return; } WebRTCMailbox destinationOutgoingWebRTCMailbox = destinationWebRTCClient.getOutgoingWebRTCMailbox(); destinationOutgoingWebRTCMailbox.pushWebRTCMail(webRTCMail); } private static final long _CONNECTION_TIMEOUT_DURATION_TIME = 60000; private static final long _PRESENCE_TIMEOUT_DURATION_TIME = 30000; private Map<Long, WebRTCClient> _webRTCClients = new ConcurrentHashMap<>(); }