/** * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). * * This program 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 3.0 of the License, or (at your option) any later * version. * * BigBlueButton 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. * * You should have received a copy of the GNU Lesser General Public License along * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * */ package org.bigbluebutton.red5; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.bigbluebutton.red5.client.messaging.ConnectionInvokerService; import org.bigbluebutton.red5.pubsub.MessagePublisher; import org.red5.logging.Red5LoggerFactory; import org.red5.server.adapter.IApplication; import org.red5.server.adapter.MultiThreadedApplicationAdapter; import org.red5.server.api.IClient; import org.red5.server.api.IConnection; import org.red5.server.api.Red5; import org.red5.server.api.scope.IScope; import org.slf4j.Logger; import com.google.gson.Gson; public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter { private static Logger log = Red5LoggerFactory.getLogger(BigBlueButtonApplication.class, "bigbluebutton"); private ConnectionInvokerService connInvokerService; private MessagePublisher red5InGW; private final UserConnectionMapper userConnections = new UserConnectionMapper(); private final String APP = "BBB"; private final String CONN = "RED5-"; @Override public boolean appConnect(IConnection conn, Object[] params) { return super.appConnect(conn, params); } @Override public void appDisconnect(IConnection conn) { super.appDisconnect(conn); } @Override public boolean appJoin(IClient client, IScope scope) { return super.appJoin(client, scope); } @Override public void appLeave(IClient client, IScope scope) { super.appLeave(client, scope); } @Override public boolean roomJoin(IClient client, IScope scope) { return super.roomJoin(client, scope); } @Override public void roomLeave(IClient client, IScope scope) { super.roomLeave(client, scope); } @Override public boolean appStart(IScope app) { super.appStart(app); connInvokerService.setAppScope(app); getHeapStats(); return true; } private void getHeapStats() { Runnable getHeapTask = () -> getHeapStatsHelper(); ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(getHeapTask, 0, 5, TimeUnit.SECONDS); } private void getHeapStatsHelper() { int mb = 1024*1024; // Getting the runtime reference from system Runtime runtime = Runtime.getRuntime(); long usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / mb; long freeMemory = runtime.freeMemory() / mb; long totalMemory = runtime.totalMemory() / mb; long maxMemory = runtime.maxMemory() / mb; Map<String, Object> logData = new HashMap<String, Object>(); logData.put("used", usedMemory); logData.put("free", freeMemory); logData.put("total", totalMemory); logData.put("max", maxMemory); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.info("JVM Heap [MB] data={}", logStr); } @Override public void appStop(IScope app) { super.appStop(app); } @Override public boolean roomStart(IScope room) { return super.roomStart(room); } @Override public void roomStop(IScope room) { super.roomStop(room); } @Override public boolean roomConnect(IConnection connection, Object[] params) { String username = ((String) params[0]).toString(); String role = ((String) params[1]).toString(); String room = ((String)params[2]).toString(); String voiceBridge = ((String) params[3]).toString(); boolean record = (Boolean)params[4]; String externalUserID = ((String) params[5]).toString(); String internalUserID = ((String) params[6]).toString(); Boolean muted = false; if (params.length >= 7 && ((Boolean) params[7])) { muted = true; } Map<String, Boolean> lsMap = null; if (params.length >= 8) { try { lsMap = (Map<String, Boolean> ) params[8]; } catch(Exception e){ lsMap = new HashMap<String, Boolean>(); } } String userId = internalUserID; String sessionId = Red5.getConnectionLocal().getSessionId(); String connType = getConnectionType(Red5.getConnectionLocal().getType()); /** * Find if there are any other connections owned by this user. If we find one, * that means that the connection is old and the user reconnected. Clear the * userId attribute so that messages would not be sent in the defunct connection. */ Set<IConnection> conns = Red5.getConnectionLocal().getScope().getClientConnections(); for (IConnection conn : conns) { String connUserId = (String) conn.getAttribute("INTERNAL_USER_ID"); String connSessionId = conn.getSessionId(); String clientId = conn.getClient().getId(); String remoteHost = connection.getRemoteAddress(); int remotePort = connection.getRemotePort(); if (connUserId != null && connUserId.equals(userId) && !connSessionId.equals(sessionId)) { conn.removeAttribute("INTERNAL_USER_ID"); Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", room); logData.put("userId", userId); logData.put("oldConnId", connSessionId); logData.put("newConnId", sessionId); logData.put("clientId", clientId); logData.put("remoteAddress", remoteHost + ":" + remotePort); logData.put("event", "removing_defunct_connection"); logData.put("description", "Removing defunct connection BBB Apps."); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.info("Removing defunct connection: data={}", logStr); } } BigBlueButtonSession bbbSession = new BigBlueButtonSession(room, internalUserID, username, role, voiceBridge, record, externalUserID, muted, sessionId); connection.setAttribute(Constants.SESSION, bbbSession); connection.setAttribute("INTERNAL_USER_ID", internalUserID); connection.setAttribute("USER_SESSION_ID", sessionId); connection.setAttribute("TIMESTAMP", System.currentTimeMillis()); red5InGW.initLockSettings(room, lsMap); red5InGW.initAudioSettings(room, internalUserID, muted); String meetingId = bbbSession.getRoom(); String userFullname = bbbSession.getUsername(); String connId = Red5.getConnectionLocal().getSessionId(); String remoteHost = Red5.getConnectionLocal().getRemoteAddress(); int remotePort = Red5.getConnectionLocal().getRemotePort(); String clientId = Red5.getConnectionLocal().getClient().getId(); Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", meetingId); logData.put("connType", connType); logData.put("connId", connId); logData.put("clientId", clientId); logData.put("remoteAddress", remoteHost + ":" + remotePort); logData.put("userId", userId); logData.put("externalUserId", externalUserID); logData.put("sessionId", sessionId); logData.put("username", userFullname); logData.put("event", "user_joining_bbb_apps"); logData.put("description", "User joining BBB Apps."); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.info("User joining bbb-apps: data={}", logStr); userConnections.addUserConnection(userId, connId); return super.roomConnect(connection, params); } private String getConnectionType(String connType) { if ("persistent".equals(connType.toLowerCase())) { return "RTMP"; } else if("polling".equals(connType.toLowerCase())) { return "RTMPT"; } else { return connType.toUpperCase(); } } @Override public void roomDisconnect(IConnection conn) { String remoteHost = Red5.getConnectionLocal().getRemoteAddress(); int remotePort = Red5.getConnectionLocal().getRemotePort(); BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION); String meetingId = bbbSession.getRoom(); String userId = bbbSession.getInternalUserID(); String connType = getConnectionType(Red5.getConnectionLocal().getType()); String userFullname = bbbSession.getUsername(); String connId = Red5.getConnectionLocal().getSessionId(); String clientId = Red5.getConnectionLocal().getClient().getId(); String sessionId = CONN + userId; Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", meetingId); logData.put("connType", connType); logData.put("connId", connId); logData.put("clientId", clientId); logData.put("remoteAddress", remoteHost + ":" + remotePort); logData.put("sessionId", sessionId); logData.put("userId", userId); logData.put("username", userFullname); logData.put("event", "user_leaving_bbb_apps"); logData.put("description", "User leaving BBB Apps."); Gson gson = new Gson(); String logStr = gson.toJson(logData); boolean removeUser = userConnections.userDisconnected(userId, connId); if (removeUser) { log.info("User leaving bbb-apps: data={}", logStr); red5InGW.userLeft(bbbSession.getRoom(), getBbbSession().getInternalUserID(), sessionId); } else { log.info("User not leaving bbb-apps but just disconnected: data={}", logStr); } super.roomDisconnect(conn); } public void validateToken(Map<String, String> msg) { String token = (String) msg.get("authToken"); BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION); assert bbbSession != null; String userId = bbbSession.getInternalUserID(); String meetingId = Red5.getConnectionLocal().getScope().getName(); String connId = Red5.getConnectionLocal().getSessionId(); String sessionId = CONN + connId + "-" + userId; Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", meetingId); logData.put("connId", connId); logData.put("sessionId", sessionId); logData.put("userId", userId); logData.put("token", token); logData.put("event", "user_validate_token_bbb_apps"); logData.put("description", "User validate token BBB Apps."); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.info("User validate token bbb-apps: data={}", logStr); red5InGW.validateAuthToken(meetingId, userId, token, meetingId + "/" + userId, sessionId); } public void setApplicationListeners(Set<IApplication> listeners) { Iterator<IApplication> iter = listeners.iterator(); while (iter.hasNext()) { super.addListener((IApplication) iter.next()); } } private BigBlueButtonSession getBbbSession() { return (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION); } public void setConnInvokerService(ConnectionInvokerService connInvokerService) { this.connInvokerService = connInvokerService; } public void setRed5Publisher(MessagePublisher red5InGW) { this.red5InGW = red5InGW; } }