blob: bb57c1956f3fec86e30518c20ecf4125823cd09f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License") + you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openmeetings.core.remote.red5;
import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.openmeetings.IApplication;
import org.apache.openmeetings.core.data.conference.RoomManager;
import org.apache.openmeetings.core.data.whiteboard.WhiteboardManager;
import org.apache.openmeetings.core.remote.RecordingService;
import org.apache.openmeetings.core.remote.WhiteBoardService;
import org.apache.openmeetings.core.remote.util.SessionVariablesUtil;
import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
import org.apache.openmeetings.db.dao.label.LabelDao;
import org.apache.openmeetings.db.dao.log.ConferenceLogDao;
import org.apache.openmeetings.db.dao.record.RecordingDao;
import org.apache.openmeetings.db.dao.room.RoomDao;
import org.apache.openmeetings.db.dao.server.ISessionManager;
import org.apache.openmeetings.db.dao.server.ServerDao;
import org.apache.openmeetings.db.dao.server.SessiondataDao;
import org.apache.openmeetings.db.dao.user.UserDao;
import org.apache.openmeetings.db.dto.room.BrowserStatus;
import org.apache.openmeetings.db.dto.room.RoomStatus;
import org.apache.openmeetings.db.entity.file.FileItem;
import org.apache.openmeetings.db.entity.log.ConferenceLog;
import org.apache.openmeetings.db.entity.room.Client;
import org.apache.openmeetings.db.entity.room.Room;
import org.apache.openmeetings.db.entity.room.Room.RoomElement;
import org.apache.openmeetings.db.entity.server.Server;
import org.apache.openmeetings.db.entity.user.User;
import org.apache.openmeetings.db.util.AuthLevelUtil;
import org.apache.openmeetings.util.CalendarPatterns;
import org.apache.openmeetings.util.InitializationContainer;
import org.apache.openmeetings.util.OmFileHelper;
import org.apache.openmeetings.util.OpenmeetingsVariables;
import org.apache.openmeetings.util.Version;
import org.apache.openmeetings.util.message.RoomMessage;
import org.apache.openmeetings.util.message.TextRoomMessage;
import org.apache.wicket.Application;
import org.apache.wicket.protocol.ws.WebSocketSettings;
import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionRegistry;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.string.Strings;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.scope.IBasicScope;
import org.red5.server.api.scope.IBroadcastScope;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.ScopeType;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.stream.IBroadcastStream;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
public class ScopeApplicationAdapter extends ApplicationAdapter implements IPendingServiceCallback {
private static final Logger log = Red5LoggerFactory.getLogger(ScopeApplicationAdapter.class, webAppRootKey);
private static final String SECURITY_CODE_PARAM = "securityCode";
@Autowired
private ISessionManager sessionManager;
@Autowired
private WhiteBoardService whiteBoardService;
@Autowired
private WhiteboardManager whiteboardManagement;
@Autowired
private RecordingService recordingService;
@Autowired
private ConfigurationDao configurationDao;
@Autowired
private AppointmentDao appointmentDao;
@Autowired
private SessiondataDao sessiondataDao;
@Autowired
private RoomManager roomManager;
@Autowired
private ConferenceLogDao conferenceLogDao;
@Autowired
private UserDao userDao;
@Autowired
private RoomDao roomDao;
@Autowired
private RecordingDao recordingDao;
@Autowired
private ServerDao serverDao;
private static AtomicLong broadCastCounter = new AtomicLong(0);
@Override
public void resultReceived(IPendingServiceCall arg0) {
if (log.isTraceEnabled()) {
log.trace("resultReceived:: {}", arg0);
}
}
@Override
public boolean appStart(IScope scope) {
try {
OmFileHelper.setOmHome(scope.getResource("/").getFile());
LabelDao.initLanguageMap();
log.debug("webAppPath : " + OmFileHelper.getOmHome());
// Only load this Class one time Initially this value might by empty, because the DB is empty yet
getCryptKey();
// init your handler here
for (String scopeName : scope.getScopeNames()) {
log.debug("scopeName :: " + scopeName);
}
InitializationContainer.initComplete = true;
Version.logOMStarted();
recordingDao.resetProcessingStatus(); //we are starting so all processing recordings are now errors
sessionManager.clearCache(); // 'sticky' clients should be cleaned up from DB
} catch (Exception err) {
log.error("[appStart]", err);
}
return true;
}
@SuppressWarnings("unchecked")
private static Map<String, Object> getConnParams(Object[] params) {
if (params != null && params.length > 0) {
return (Map<String, Object>)params[0];
}
return new HashMap<>();
}
@Override
public boolean roomConnect(IConnection conn, Object[] params) {
log.debug("roomConnect : ");
IServiceCapableConnection service = (IServiceCapableConnection) conn;
String streamId = conn.getClient().getId();
log.debug("### Client connected to OpenMeetings, register Client StreamId: " + streamId + " scope " + conn.getScope().getName());
// Set StreamId in Client
service.invoke("setId", new Object[] { streamId }, this);
Map<String, Object> map = conn.getConnectParams();
String swfURL = map.containsKey("swfUrl") ? (String)map.get("swfUrl") : "";
String tcUrl = map.containsKey("tcUrl") ? (String)map.get("tcUrl") : "";
Map<String, Object> connParams = getConnParams(params);
String uid = (String)connParams.get("uid");
String securityCode = (String)connParams.get(SECURITY_CODE_PARAM);
if (!Strings.isEmpty(securityCode)) {
//FIXME TODO add better mechanism
Client parent = sessionManager.getClientByPublicSID(securityCode, null);
if (parent == null || !parent.getScope().equals(conn.getScope().getName())) {
return rejectClient();
}
}
if (Strings.isEmpty(uid) && Strings.isEmpty(securityCode)) {
return rejectClient();
}
if ("networktest".equals(uid)) {
return true;
}
Client parentClient = null;
//TODO add similar code for other connections
if (map.containsKey("screenClient")) {
String parentSid = (String)map.get("parentSid");
parentClient = sessionManager.getClientByPublicSID(parentSid, null);
if (parentClient == null) {
return rejectClient();
}
}
Client rcm = new Client();
rcm.setStreamid(conn.getClient().getId());
StringValue scn = StringValue.valueOf(conn.getScope().getName());
rcm.setScope(scn.toString());
long roomId = scn.toLong(Long.MIN_VALUE);
if (Long.MIN_VALUE != roomId) {
rcm.setRoomId(roomId);
} else if (!"hibernate".equals(scn.toString())) {
return rejectClient();
}
rcm.setUserport(conn.getRemotePort());
rcm.setUserip(conn.getRemoteAddress());
rcm.setSwfurl(swfURL);
rcm.setTcUrl(tcUrl);
rcm.setPublicSID(uid);
rcm.setSecurityCode(securityCode);
rcm = sessionManager.add(((IApplication)Application.get(OpenmeetingsVariables.wicketApplicationName)).updateClient(rcm), null);
if (rcm == null) {
log.warn("Failed to create Client on room connect");
return false;
}
SessionVariablesUtil.initClient(conn.getClient(), rcm.getPublicSID());
//TODO add similar code for other connections, merge with above block
if (map.containsKey("screenClient")) {
//TODO add check for room rights
String parentSid = parentClient.getPublicSID();
rcm.setRoomId(Long.valueOf(conn.getScope().getName()));
rcm.setScreenClient(true);
SessionVariablesUtil.setIsScreenClient(conn.getClient());
rcm.setUserId(parentClient.getUserId());
Long userId = rcm.getUserId();
SessionVariablesUtil.setUserId(conn.getClient(), userId);
rcm.setStreamPublishName(parentSid);
User u = null;
if (userId != null) {
long _uid = userId.longValue();
u = userDao.get(_uid < 0 ? -_uid : _uid);
}
if (u != null) {
rcm.setUsername(u.getLogin());
rcm.setFirstname(u.getFirstname());
rcm.setLastname(u.getLastname());
}
log.debug("publishName :: " + rcm.getStreamPublishName());
sessionManager.updateClientByStreamId(streamId, rcm, false, null);
}
// Log the User
conferenceLogDao.add(ConferenceLog.Type.clientConnect,
rcm.getUserId(), streamId, null, rcm.getUserip(),
rcm.getScope());
return true;
}
public Map<String, String> screenSharerAction(Map<String, Object> map) {
Map<String, String> returnMap = new HashMap<String, String>();
try {
log.debug("----------- screenSharerAction ENTER");
IConnection current = Red5.getConnectionLocal();
Client client = sessionManager.getClientByStreamId(current.getClient().getId(), null);
if (client != null) {
boolean changed = false;
if (Boolean.parseBoolean("" + map.get("stopStreaming")) && client.isStartStreaming()) {
changed = true;
client.setStartStreaming(false);
//Send message to all users
sendMessageToCurrentScope("stopScreenSharingMessage", client, false);
broadcastRoom(new TextRoomMessage(client.getRoomId(), client.getUserId(), RoomMessage.Type.sharingStoped, client.getStreamPublishName()));
returnMap.put("result", "stopSharingOnly");
}
if (Boolean.parseBoolean("" + map.get("stopRecording")) && client.getIsRecording()) {
changed = true;
client.setStartRecording(false);
client.setIsRecording(false);
returnMap.put("result", "stopRecordingOnly");
recordingService.stopRecordAndSave(current.getScope(), client, null);
}
if (Boolean.parseBoolean("" + map.get("stopPublishing")) && client.isScreenPublishStarted()) {
changed = true;
client.setScreenPublishStarted(false);
returnMap.put("result", "stopPublishingOnly");
//Send message to all users
sendMessageToCurrentScope("stopPublishingMessage", client, false);
}
if (changed) {
sessionManager.updateClientByStreamId(client.getStreamid(), client, false, null);
if (!client.isStartStreaming() && !client.isStartRecording() && !client.isStreamPublishStarted()) {
returnMap.put("result", "stopAll");
}
}
}
log.debug("----------- screenSharerAction, return: " + returnMap);
} catch (Exception err) {
log.error("[screenSharerAction]", err);
}
return returnMap;
}
public List<Client> checkScreenSharing() {
try {
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
log.debug("checkScreenSharing -2- " + streamid);
List<Client> screenSharerList = new LinkedList<Client>();
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
for (Client rcl : sessionManager.getClientListByRoomAll(currentClient.getRoomId())) {
if (rcl.isStartStreaming()) {
screenSharerList.add(rcl);
}
}
return screenSharerList;
} catch (Exception err) {
log.error("[checkScreenSharing]", err);
}
return null;
}
/**
*
* @param map
* @return returns key,value Map with multiple return values or null in case of exception
*
*/
public Map<String, Object> setConnectionAsSharingClient(Map<String, Object> map) {
try {
log.debug("----------- setConnectionAsSharingClient");
IConnection current = Red5.getConnectionLocal();
Client client = sessionManager.getClientByStreamId(current.getClient().getId(), null);
if (client != null) {
boolean startRecording = Boolean.parseBoolean("" + map.get("startRecording"));
boolean startStreaming = Boolean.parseBoolean("" + map.get("startStreaming"));
boolean startPublishing = Boolean.parseBoolean("" + map.get("startPublishing")) && (0 == sessionManager.getPublishingCount(client.getRoomId()));
boolean alreadyStreaming = client.isStartStreaming();
if (startStreaming) {
client.setStartStreaming(true);
}
boolean alreadyRecording = client.isStartRecording();
if (startRecording) {
client.setStartRecording(true);
}
if (startPublishing) {
client.setStreamPublishStarted(true);
}
client.setVX(Double.valueOf("" + map.get("screenX")).intValue());
client.setVY(Double.valueOf("" + map.get("screenY")).intValue());
client.setVWidth(Double.valueOf("" + map.get("screenWidth")).intValue());
client.setVHeight(Double.valueOf("" + map.get("screenHeight")).intValue());
client.setStreamPublishName("" + map.get("publishName"));
sessionManager.updateClientByStreamId(current.getClient().getId(), client, false, null);
Map<String, Object> returnMap = new HashMap<String, Object>();
returnMap.put("alreadyPublished", false);
// if is already started screen sharing, then there is no need
// to start it again
if (client.isScreenPublishStarted()) {
returnMap.put("alreadyPublished", true);
}
log.debug(String.format("screen x,y,width,height %s,%s,%s,%s", client.getVX(), client.getVY(), client.getVWidth(), client.getVHeight()));
if (startStreaming) {
if (!alreadyStreaming) {
returnMap.put("modus", "startStreaming");
log.debug("start streamPublishStart Is Screen Sharing ");
//Send message to all users
sendMessageToCurrentScope("newScreenSharing", client, false);
broadcastRoom(new TextRoomMessage(client.getRoomId(), client.getUserId(), RoomMessage.Type.sharingStarted, client.getStreamPublishName()));
} else {
log.warn("Streaming is already started for the client id=" + client.getId() + ". Second request is ignored.");
}
}
if (startRecording) {
if (!alreadyRecording) {
returnMap.put("modus", "startRecording");
String recordingName = "Recording " + CalendarPatterns.getDateWithTimeByMiliSeconds(new Date());
recordingService.recordMeetingStream(current, client, recordingName, "", false);
} else {
log.warn("Recording is already started for the client id=" + client.getId() + ". Second request is ignored.");
}
}
if (startPublishing) {
sendMessageToCurrentScope("startedPublishing", new Object[]{client, "rtmp://" + map.get("publishingHost") + ":1935/"
+ map.get("publishingApp") + "/" + map.get("publishingId")}, false, true);
returnMap.put("modus", "startPublishing");
}
return returnMap;
} else {
log.error("[setConnectionAsSharingClient] Could not find Screen Sharing Client " + current.getClient().getId());
}
} catch (Exception err) {
log.error("[setConnectionAsSharingClient]", err);
}
return null;
}
public List<Long> listRoomBroadcast() {
Set<Long> broadcastList = new HashSet<>();
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
for (IConnection conn : current.getScope().getClientConnections()) {
if (conn != null) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl == null) {
// continue;
} else if (rcl.isScreenClient()) {
// continue;
} else {
if (!streamid.equals(rcl.getStreamid())) {
// It is not needed to send back
// that event to the actuall
// Moderator
// as it will be already triggered
// in the result of this Function
// in the Client
Long id = Long.valueOf(rcl.getBroadCastID());
if (!broadcastList.contains(id)) {
broadcastList.add(id);
}
}
}
}
}
return new ArrayList<Long>(broadcastList);
}
/**
* this function is invoked directly after initial connecting
*
* @return publicSID of current client
*/
public String getPublicSID() {
log.debug("----------- getPublicSID");
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
sessionManager.updateClientByStreamId(current.getClient().getId(), currentClient, false, null);
return currentClient.getPublicSID();
}
/**
* this function is invoked after a reconnect
*
* @param newPublicSID
*/
public boolean overwritePublicSID(String newPublicSID) {
try {
log.debug("----------- overwritePublicSID");
IConnection current = Red5.getConnectionLocal();
IClient c = current.getClient();
Client currentClient = sessionManager.getClientByStreamId(c.getId(), null);
if (currentClient == null) {
return false;
}
SessionVariablesUtil.initClient(c, newPublicSID);
currentClient.setPublicSID(newPublicSID);
sessionManager.updateClientByStreamId(c.getId(), currentClient, false, null);
return true;
} catch (Exception err) {
log.error("[overwritePublicSID]", err);
}
return false;
}
/**
* Logic must be before roomDisconnect cause otherwise you cannot throw a
* message to each one
*
*/
@Override
public void roomLeave(IClient client, IScope room) {
try {
log.debug(String.format("roomLeave %s %s %s %s", client.getId(), room.getClients().size(), room.getContextPath(), room.getName()));
Client currentClient = sessionManager.getClientByStreamId(client.getId(), null);
// The Room Client can be null if the Client left the room by using
// logicalRoomLeave
if (currentClient != null) {
log.debug("currentClient IS NOT NULL");
roomLeaveByScope(currentClient, room, true);
}
} catch (Exception err) {
log.error("[roomLeave]", err);
}
}
/**
* this means a user has left a room but only logically, he didn't leave the
* app he just left the room
*
* FIXME: Is this really needed anymore if you re-connect to another scope?
*
* Exit Room by Application
*
*/
public void logicalRoomLeave() {
log.debug("logicalRoomLeave ");
try {
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
log.debug(streamid + " is leaving");
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
roomLeaveByScope(currentClient, current.getScope(), true);
} catch (Exception err) {
log.error("[logicalRoomLeave]", err);
}
}
/**
* Removes the Client from the List, stops recording, adds the Room-Leave
* event to running recordings, clear Polls and removes Client from any list
*
* This function is kind of private/protected as the client won't be able
* to call it with proper values.
*
* @param client
* @param scope
*/
public void roomLeaveByScope(Client client, IScope scope, boolean removeUserFromSessionList) {
try {
log.debug("currentClient " + client);
Long roomId = client.getRoomId();
if (client.isScreenClient() && client.isStartStreaming()) {
//TODO check others/find better way
broadcastRoom(new TextRoomMessage(client.getRoomId(), client.getUserId(), RoomMessage.Type.sharingStoped, client.getStreamPublishName()));
}
// Log the User
conferenceLogDao.add(ConferenceLog.Type.roomLeave,
client.getUserId(), client.getStreamid(),
roomId, client.getUserip(), "");
// Remove User from Sync List's
if (roomId != null) {
whiteBoardService.removeUserFromAllLists(scope, client);
}
log.debug("removing Username " + client.getUsername() + " "
+ client.getConnectedSince() + " streamid: "
+ client.getStreamid());
// stop and save any recordings
if (client.getIsRecording()) {
log.debug("*** roomLeave Current Client is Recording - stop that");
if (client.getInterviewPodId() != null) {
//interview, TODO need better check
_stopInterviewRecording(client, scope);
} else {
recordingService.stopRecordAndSave(scope, client, null);
// set to true and overwrite the default one cause otherwise no
// notification is send
client.setIsRecording(true);
}
}
// Notify all clients of the same currentScope (room) with domain
// and room except the current disconnected cause it could throw an exception
log.debug("currentScope " + scope);
if (scope != null && scope.getClientConnections() != null) {
// Notify Users of the current Scope
for (IConnection cons : scope.getClientConnections()) {
if (cons != null && cons instanceof IServiceCapableConnection) {
log.debug("sending roomDisconnect to {} client id {}", cons, cons.getClient().getId());
Client rcl = sessionManager.getClientByStreamId(cons.getClient().getId(), null);
// Check if the Client does still exist on the list
if (rcl == null) {
log.debug("For this StreamId: " + cons.getClient().getId() + " There is no Client in the List anymore");
continue;
}
//Do not send back to sender, but actually all other clients should receive this message swagner 01.10.2009
if (!client.getStreamid().equals(rcl.getStreamid())) {
// add Notification if another user isrecording
log.debug("###########[roomLeaveByScope]");
if (rcl.getIsRecording()) {
log.debug("*** roomLeave Any Client is Recording - stop that");
recordingService.stopRecordingShowForClient(cons, client);
}
boolean isScreen = rcl.isScreenClient();
if (isScreen && client.getPublicSID().equals(rcl.getStreamPublishName())) {
//going to terminate screen sharing started by this client
((IServiceCapableConnection) cons).invoke("stopStream", new Object[] { },this);
continue;
} else if (isScreen) {
// screen sharing clients do not receive events
continue;
}
// Send to all connected users
((IServiceCapableConnection) cons).invoke("roomDisconnect", new Object[] { client },this);
log.debug("sending roomDisconnect to " + cons);
}
}
}
}
if (removeUserFromSessionList) {
sessionManager.removeClient(client.getStreamid(), null);
}
} catch (Exception err) {
log.error("[roomLeaveByScope]", err);
}
}
/**
* This method handles the Event after a stream has been added all connected
* Clients in the same room will get a notification
*
*/
/* (non-Javadoc)
* @see org.red5.server.adapter.MultiThreadedApplicationAdapter#streamPublishStart(org.red5.server.api.stream.IBroadcastStream)
*/
@Override
public void streamPublishStart(IBroadcastStream stream) {
try {
log.debug("----------- streamPublishStart");
IConnection current = Red5.getConnectionLocal();
final String streamid = current.getClient().getId();
final Client currentClient = sessionManager.getClientByStreamId(streamid, null);
//We make a second object the has the reference to the object
//that we will use to send to all participents
Client clientObjectSendToSync = currentClient;
// Notify all the clients that the stream had been started
log.debug("start streamPublishStart broadcast start: " + stream.getPublishedName() + " CONN " + current);
// In case its a screen sharing we start a new Video for that
if (currentClient.isScreenClient()) {
currentClient.setScreenPublishStarted(true);
sessionManager.updateClientByStreamId(streamid, currentClient, false, null);
}
if (!Strings.isEmpty(currentClient.getSecurityCode())) {
currentClient.setBroadCastID(Long.parseLong(stream.getPublishedName()));
currentClient.setIsBroadcasting(true);
currentClient.setVWidth(320);
currentClient.setVHeight(240);
sessionManager.updateClientByStreamId(streamid, currentClient, false, null);
}
log.debug("newStream SEND: " + currentClient);
// Notify all users of the same Scope
// We need to iterate through the streams to catch if anybody is recording
new MessageSender(current, "newStream", clientObjectSendToSync) {
@Override
public boolean filter(IConnection conn) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl == null) {
log.debug("RCL IS NULL newStream SEND");
return true;
}
log.debug("check send to "+rcl);
if (rcl.getPublicSID() == "") {
log.debug("publicSID IS NULL newStream SEND");
return true;
}
if (rcl.getIsRecording()) {
log.debug("RCL getIsRecording newStream SEND");
recordingService.addRecordingByStreamId(current, streamid, currentClient, rcl.getRecordingId());
}
if (rcl.isScreenClient()) {
log.debug("RCL getIsScreenClient newStream SEND");
return true;
}
if (rcl.getPublicSID().equals(currentClient.getPublicSID())) {
log.debug("RCL publicSID is equal newStream SEND");
return true;
}
log.debug("RCL SEND is equal newStream SEND "+rcl.getPublicSID()+" || "+rcl.getUserport());
return false;
}
}.start();
} catch (Exception err) {
log.error("[streamPublishStart]", err);
}
}
public IBroadcastScope getBroadcastScope(IScope scope, String name) {
IBasicScope basicScope = scope.getBasicScope(ScopeType.BROADCAST, name);
if (!(basicScope instanceof IBroadcastScope)) {
return null;
} else {
return (IBroadcastScope) basicScope;
}
}
/**
* This method handles the Event after a stream has been removed all
* connected Clients in the same room will get a notification
*
*/
/* (non-Javadoc)
* @see org.red5.server.adapter.MultiThreadedApplicationAdapter#streamBroadcastClose(org.red5.server.api.stream.IBroadcastStream)
*/
@Override
public void streamBroadcastClose(IBroadcastStream stream) {
// Notify all the clients that the stream had been closed
log.debug("start streamBroadcastClose broadcast close: " + stream.getPublishedName());
try {
IConnection current = Red5.getConnectionLocal();
Client rcl = sessionManager.getClientByStreamId(current.getClient().getId(), null);
sendClientBroadcastNotifications(stream, "closeStream", rcl);
} catch (Exception e) {
log.error("[streamBroadcastClose]", e);
}
}
/**
* This method handles the notification room-based
*
* @return void
*
*/
private void sendClientBroadcastNotifications(IBroadcastStream stream, String clientFunction, Client rc) {
try {
// Store the local so that we do not send notification to ourself back
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
if (currentClient == null) {
// In case the client has already left(kicked) this message
// might be thrown later then the RoomLeave
// event and the currentClient is already gone
// The second Use-Case where the currentClient is maybe null is
// if we remove the client because its a Zombie/Ghost
return;
}
// Notify all the clients that the stream had been started
log.debug("sendClientBroadcastNotifications: " + stream.getPublishedName());
log.debug("sendClientBroadcastNotifications : " + currentClient + " " + currentClient.getStreamid());
// Notify all clients of the same scope (room)
for (IConnection conn : current.getScope().getClientConnections()) {
if (conn != null) {
if (conn instanceof IServiceCapableConnection) {
if (conn.equals(current)) {
// there is a Bug in the current implementation
// of the appDisconnect
if (clientFunction.equals("closeStream")) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (clientFunction.equals("closeStream") && rcl.getIsRecording()) {
log.debug("*** stopRecordingShowForClient Any Client is Recording - stop that");
// StreamService.stopRecordingShowForClient(conn,
// currentClient,
// rcl.getRoomRecordingName(), false);
recordingService.stopRecordingShowForClient(conn, currentClient);
}
// Don't notify current client
current.ping();
}
continue;
} else {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl != null) {
if (rcl.isScreenClient()) {
// continue;
} else {
log.debug("is this users still alive? :" + rcl);
IServiceCapableConnection iStream = (IServiceCapableConnection) conn;
iStream.invoke(clientFunction, new Object[] { rc }, this);
}
log.debug("sending notification to " + conn + " ID: ");
// if this close stream event then stop the
// recording of this stream
if (clientFunction.equals("closeStream") && rcl.getIsRecording()) {
log.debug("*** +++++++ ######## sendClientBroadcastNotifications Any Client is Recording - stop that");
recordingService.stopRecordingShowForClient(conn, currentClient);
}
}
}
}
}
}
} catch (Exception err) {
log.error("[sendClientBroadcastNotifications]", err);
}
}
@SuppressWarnings("unchecked")
public void setNewCursorPosition(Object item) {
try {
IConnection current = Red5.getConnectionLocal();
Client c = sessionManager.getClientByStreamId(current.getClient().getId(), null);
@SuppressWarnings("rawtypes")
Map cursor = (Map) item;
cursor.put("streamPublishName", c.getStreamPublishName());
sendMessageToCurrentScope("newRed5ScreenCursor", cursor, true, false);
} catch (Exception err) {
log.error("[setNewCursorPosition]", err);
}
}
public long removeModerator(String publicSID) {
try {
log.debug("----------- removeModerator: " + publicSID);
Client currentClient = sessionManager.getClientByPublicSID(publicSID, null);
if (currentClient == null) {
return -1L;
}
Long roomId = currentClient.getRoomId();
currentClient.setIsMod(false);
// Put the mod-flag to true for this client
sessionManager.updateClientByStreamId(currentClient.getStreamid(), currentClient, false, null);
List<Client> currentMods = sessionManager.getCurrentModeratorByRoom(roomId);
sendMessageToCurrentScope("setNewModeratorByList", currentMods, true);
} catch (Exception err) {
log.error("[removeModerator]", err);
}
return -1L;
}
public long giveExclusiveAudio(String publicSID) {
try {
log.debug("----------- giveExclusiveAudio: " + publicSID);
IConnection current = Red5.getConnectionLocal();
// String streamid = current.getClient().getId();
final Client currentClient = sessionManager.getClientByPublicSID(publicSID, null);
if (currentClient == null) {
return -1L;
}
// Put the mod-flag to true for this client
currentClient.setMicMuted(false);
sessionManager.updateClientByStreamId(currentClient.getStreamid(), currentClient, false, null);
// Notify all clients of the same scope (room)
new MessageSender(current, "receiveExclusiveAudioFlag", currentClient) {
@Override
public boolean filter(IConnection conn) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl == null) {
} else if (rcl.isScreenClient()) {
} else {
if (rcl != currentClient) {
rcl.setMicMuted(true);
sessionManager.updateClientByStreamId(rcl.getStreamid(), rcl, false, null);
}
return false;
}
return true;
}
}.start();
} catch (Exception err) {
log.error("[giveExclusiveAudio]", err);
}
return -1L;
}
public long switchMicMuted(String publicSID, boolean mute) {
try {
log.debug("----------- switchMicMuted: " + publicSID);
Client currentClient = sessionManager.getClientByPublicSID(publicSID, null);
if (currentClient == null) {
return -1L;
}
currentClient.setMicMuted(mute);
sessionManager.updateClientByStreamId(currentClient.getStreamid(), currentClient, false, null);
HashMap<Integer, Object> newMessage = new HashMap<Integer, Object>();
newMessage.put(0, "updateMuteStatus");
newMessage.put(1, currentClient);
sendMessageWithClient(newMessage);
} catch (Exception err) {
log.error("[switchMicMuted]", err);
}
return 0L;
}
public boolean getMicMutedByPublicSID(String publicSID) {
try {
Client currentClient = sessionManager.getClientByPublicSID(publicSID, null);
if (currentClient == null) {
return true;
}
//Put the mod-flag to true for this client
return currentClient.getMicMuted();
} catch (Exception err) {
log.error("[getMicMutedByPublicSID]",err);
}
return true;
}
/**
* Invoked by a User whenever he want to become moderator this is needed,
* cause if the room has no moderator yet there is no-one he can ask to get
* the moderation, in case its a Non-Moderated Room he should then get the
* Moderation without any confirmation needed
*
* @return Long 1 => means get Moderation, 2 => ask Moderator for
* Moderation, 3 => wait for Moderator
*/
public long applyForModeration(String publicSID) {
try {
Client currentClient = sessionManager.getClientByPublicSID(publicSID, null);
if (currentClient == null) {
log.warn("Unable to find client by publicSID: {}", publicSID);
return -1L;
}
List<Client> currentModList = sessionManager.getCurrentModeratorByRoom(currentClient.getRoomId());
if (currentModList.size() > 0) {
return 2L;
} else {
// No moderator in this room at the moment
Room room = roomDao.get(currentClient.getRoomId());
return room.isModerated() ? 3L : 1L;
}
} catch (Exception err) {
log.error("[applyForModeration]", err);
}
return -1L;
}
/**
* there will be set an attribute called "broadCastCounter" this is the name
* this user will publish his stream
*
* @return long broadCastId
*/
public long getBroadCastId() {
try {
log.debug("----------- getBroadCastId");
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
Client client = sessionManager.getClientByStreamId(streamid, null);
client.setBroadCastID(broadCastCounter.getAndIncrement());
sessionManager.updateClientByStreamId(streamid, client, false, null);
return client.getBroadCastID();
} catch (Exception err) {
log.error("[getBroadCastId]", err);
}
return -1;
}
/**
* this must be set _after_ the Video/Audio-Settings have been chosen (see
* editrecordstream.lzx) but _before_ anything else happens, it cannot be
* applied _after_ the stream has started! avsettings can be: av - video and
* audio a - audio only v - video only n - no a/v only static image
* furthermore
*
* @param avsettings
* @param newMessage
* @param vWidth
* @param vHeight
* @param roomId
* @param publicSID
* @param interviewPodId
* @return RoomClient being updated in case of no errors, null otherwise
*/
public Client setUserAVSettings(String avsettings,
Object newMessage, Integer vWidth, Integer vHeight,
long roomId, String publicSID, Integer interviewPodId) {
try {
IConnection current = Red5.getConnectionLocal();
IClient c = current.getClient();
String streamid = c.getId();
log.debug("----------- setUserAVSettings {} {} {}", new Object[] {streamid, publicSID, avsettings, newMessage});
Client parentClient = sessionManager.getClientByPublicSID(publicSID, null);
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
if (parentClient == null || currentClient == null) {
log.warn("Failed to find appropriate clients");
return null;
}
currentClient.setAvsettings(avsettings);
currentClient.setRoomId(roomId);
currentClient.setPublicSID(publicSID);
currentClient.setVWidth(vWidth);
currentClient.setVHeight(vHeight);
currentClient.setInterviewPodId(interviewPodId);
currentClient.setUserId(parentClient.getUserId());
currentClient.setLastname(parentClient.getLastname());
currentClient.setFirstname(parentClient.getFirstname());
currentClient.setPicture_uri(parentClient.getPicture_uri());
sessionManager.updateAVClientByStreamId(streamid, currentClient, null);
SessionVariablesUtil.initClient(c, publicSID);
HashMap<String, Object> hsm = new HashMap<String, Object>();
hsm.put("client", currentClient);
hsm.put("message", newMessage);
sendMessageToCurrentScope("sendVarsToMessageWithClient", hsm, true);
return currentClient;
} catch (Exception err) {
log.error("[setUserAVSettings]", err);
}
return null;
}
/*
* checks if the user is allowed to apply for Moderation
*/
public boolean checkRoomValues(Long roomId) {
try {
// appointed meeting or moderated Room?
Room room = roomDao.get(roomId);
// not really - default logic
if (!room.isAppointment() && room.isModerated()) {
// if this is a Moderated Room then the Room can be only
// locked off by the Moderator Bit
List<Client> clientModeratorListRoom = sessionManager.getCurrentModeratorByRoom(roomId);
// If there is no Moderator yet and we are asking for it
// then deny it
// cause at this moment, the user should wait untill a
// Moderator enters the Room
return clientModeratorListRoom.size() != 0;
} else {
// FIXME: TODO: For Rooms that are created as Appointment we
// have to check that too
// but I don't know yet the Logic behind it - swagner 19.06.2009
return true;
}
} catch (Exception err) {
log.error("[checkRoomValues]", err);
}
return false;
}
/**
* This function is called once a User enters a Room
*
* It contains several different mechanism depending on what roomtype and
* what options are available for the room to find out if the current user
* will be a moderator of that room or not<br/>
* <br/>
* Some rules:<br/>
* <ul>
* <li>If it is a room that was created through the calendar, the user that
* organized the room will be moderator, the param Boolean becomeModerator
* will be ignored then</li>
* <li>In regular rooms you can use the param Boolean becomeModerator to set
* any user to become a moderator of the room</li>
* </ul>
* <br/>
* If a new moderator is detected a Push Call to all current users of the
* room is invoked "setNewModeratorByList" to notify them of the new
* moderator<br/>
* <br/>
* At the end of the mechanism a push call with the new client-object
* and all the informations about the new user is send to every user of the
* current conference room<br/>
* <br/>
*
* @param roomId - id of the room
* @param becomeModerator - is user will become moderator
* @param isSuperModerator - is user super moderator
* @param groupId - group id of the user
* @param colorObj - some color
* @return RoomStatus object
*/
public RoomStatus setRoomValues(Long roomId, boolean becomeModerator, boolean isSuperModerator, String colorObj) {
try {
log.debug("----------- setRoomValues");
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
currentClient.setRoomId(roomId);
currentClient.setRoomEnter(new Date());
currentClient.setUsercolor(colorObj);
User u = userDao.get(currentClient.getUserId());
// Inject externalUserId if nothing is set yet
if (currentClient.getExternalUserId() == null && u != null) {
currentClient.setExternalUserId(u.getExternalId());
currentClient.setExternalUserType(u.getExternalType());
}
Room r = roomDao.get(roomId);
if (!r.isHidden(RoomElement.MicrophoneStatus)) {
currentClient.setCanGiveAudio(true);
}
sessionManager.updateClientByStreamId(streamid, currentClient, true, null); // first save to get valid room count
// Log the User
conferenceLogDao.add(ConferenceLog.Type.roomEnter,
currentClient.getUserId(), streamid, roomId,
currentClient.getUserip(), "");
// Check for Moderation LogicalRoom ENTER
List<Client> roomClients = sessionManager.getClientListByRoom(roomId);
// Return Object
RoomStatus roomStatus = new RoomStatus();
// appointed meeting or moderated Room? => Check Max Users first
if (isSuperModerator) {
// This can be set without checking for Moderation Flag
currentClient.setIsSuperModerator(isSuperModerator);
currentClient.setIsMod(isSuperModerator);
} else {
Room.Right rr = AuthLevelUtil.getRoomRight(u, r, r.isAppointment() ? appointmentDao.getByRoom(r.getId()) : null, roomClients.size());
currentClient.setIsSuperModerator(rr == Room.Right.superModerator);
currentClient.setIsMod(becomeModerator || rr == Room.Right.moderator);
}
if (currentClient.getIsMod()) {
// Update the Client List
sessionManager.updateClientByStreamId(streamid, currentClient, false, null);
List<Client> modRoomList = sessionManager.getCurrentModeratorByRoom(currentClient.getRoomId());
//Sync message to everybody
sendMessageToCurrentScope("setNewModeratorByList", modRoomList, false);
}
//Sync message to everybody
sendMessageToCurrentScope("addNewUser", currentClient, false);
//Status object for Shared Browsing
BrowserStatus browserStatus = (BrowserStatus)current.getScope().getAttribute("browserStatus");
if (browserStatus == null) {
browserStatus = new BrowserStatus();
}
// RoomStatus roomStatus = new RoomStatus();
// FIXME: Rework Client Object to DTOs
roomStatus.setClientList(roomClients);
roomStatus.setBrowserStatus(browserStatus);
return roomStatus;
} catch (Exception err) {
log.error("[setRoomValues]", err);
}
return null;
}
/**
* This method is invoked when the user has disconnected and reconnects to
* the Gateway with the new scope
*
* @param SID
* @param userId
* @param username
* @param firstname
* @param lastname
* @param picture_uri
* @return client being updated in case of success, null otherwise
*/
public Client setUsernameReconnect(String SID, Long userId, String username, String firstname, String lastname, String picture_uri) {
try {
log.debug("----------- setUsernameReconnect");
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
SessionVariablesUtil.setUserId(current.getClient(), userId);
currentClient.setPicture_uri(picture_uri);
currentClient.setUserObject(userId, username, firstname, lastname);
// Update Session Data
sessiondataDao.updateUserWithoutSession(SID, userId);
// only fill this value from User-Record
// cause invited users have no associated User, so
// you cannot set the firstname,lastname from the UserRecord
if (userId != null) {
User us = userDao.get(userId);
if (us != null) {
currentClient.setExternalUserId(us.getExternalId());
currentClient.setExternalUserType(us.getExternalType());
if (us.getPictureuri() != null) {
// set Picture-URI
currentClient.setPicture_uri(us.getPictureuri());
}
}
}
sessionManager.updateClientByStreamId(streamid, currentClient, false, null);
return currentClient;
} catch (Exception err) {
log.error("[setUsername]", err);
}
return null;
}
/**
* this is set initial directly after login/loading language
*
* @param SID - id of the session
* @param userId - id of the user being set
* @param username - username of the user
* @param firstname - firstname of the user
* @param lastname - lastname of the user
* @return RoomClient in case of everything is OK, null otherwise
*/
public Client setUsernameAndSession(String SID, Long userId, String username, String firstname, String lastname) {
try {
log.debug("----------- setUsernameAndSession");
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
currentClient.setUsername(username);
currentClient.setUserId(userId);
SessionVariablesUtil.setUserId(current.getClient(), userId);
currentClient.setUserObject(userId, username, firstname, lastname);
// Update Session Data
log.debug("UDPATE SESSION " + SID + ", " + userId);
sessiondataDao.updateUserWithoutSession(SID, userId);
User user = userDao.get(userId);
if (user != null) {
currentClient.setExternalUserId(user.getExternalId());
currentClient.setExternalUserType(user.getExternalType());
}
// only fill this value from User-Record
// cause invited users have non
// you cannot set the firstname,lastname from the UserRecord
User us = userDao.get(userId);
if (us != null && us.getPictureuri() != null) {
// set Picture-URI
currentClient.setPicture_uri(us.getPictureuri());
}
sessionManager.updateClientByStreamId(streamid, currentClient, false, null);
return currentClient;
} catch (Exception err) {
log.error("[setUsername]", err);
}
return null;
}
/**
* used by the Screen-Sharing Servlet to trigger events
*
* @param roomId
* @param message
* @return the list of room clients
*/
public HashMap<String, Client> sendMessageByRoomAndDomain(Long roomId, Object message) {
HashMap<String, Client> roomClientList = new HashMap<String, Client>();
try {
log.debug("sendMessageByRoomAndDomain " + roomId);
IScope scope = getRoomScope(roomId.toString());
new MessageSender(scope, "newMessageByRoomAndDomain", message) {
@Override
public boolean filter(IConnection conn) {
IClient client = conn.getClient();
return SessionVariablesUtil.isScreenClient(client);
}
}.start();
} catch (Exception err) {
log.error("[getClientListBYRoomAndDomain]", err);
}
return roomClientList;
}
public List<Client> getCurrentModeratorList() {
try {
IConnection current = Red5.getConnectionLocal();
Client client = sessionManager.getClientByStreamId(current.getClient().getId(), null);
Long roomId = client.getRoomId();
Room r = roomDao.get(roomId);
if (r != null) {
if (Room.Type.conference == r.getType()) {
client.setCanVideo(!r.isAudioOnly());
client.setIsBroadcasting(true);
sessionManager.updateClientByStreamId(client.getStreamid(), client, false, null);
sendToScope(roomId, "clientUpdated", client);
}
return sessionManager.getCurrentModeratorByRoom(roomId);
}
} catch (Exception err) {
log.error("[getCurrentModerator]", err);
}
return null;
}
/**
* This Function is triggered from the Whiteboard
*
* @param whiteboardObjParam - array of parameters being sended to whiteboard
* @param whiteboardId - id of whiteboard parameters will be send to
* @return 1 in case of no errors, -1 otherwise
*/
public int sendVarsByWhiteboardId(List<?> whiteboardObjParam, Long whiteboardId) {
try {
IConnection current = Red5.getConnectionLocal();
Client client = sessionManager.getClientByStreamId(current.getClient().getId(), null);
return sendToWhiteboard(client, whiteboardObjParam, whiteboardId);
} catch (Exception err) {
log.error("[sendVarsByWhiteboardId]", err);
return -1;
}
}
public void sendToWhiteboard(String uid, Long wbId, FileItem fi, String url) {
int width = 0, height = 0;
if (fi.getFlvWidth() != null && fi.getFlvHeight() != null) {
width = fi.getFlvWidth();
height = fi.getFlvHeight();
}
String fuid = UUID.randomUUID().toString();
Client client = sessionManager.getClientByPublicSIDAnyServer(uid).getRcl();
String type = "n/a";
switch (fi.getType()) {
case Image:
type = "image";
break;
case Presentation:
type = "swf";
break;
default:
}
sendToWhiteboard(client, Arrays.asList(
"whiteboard"
, new Date()
, "draw"
, Arrays.asList(
type // 0
, url // urlname
, "--dummy--" // baseurl
, fi.getHash() // fileName //3
, "--dummy--" // moduleName //4
, "--dummy--" // parentPath //5
, "--dummy--" // room //6
, "--dummy--" // domain //7
, 1 // slideNumber //8
, 0 // innerx //9
, 0 // innery //10
, width // innerwidth //11
, height // innerheight //12
, 20 // zoomlevel //13
, width // initwidth //14
, height // initheight //15
, 100 // currentzoom //16 FIXME TODO
, fuid // uniquObjectSyncName //17
, fi.getName() // standardFileName //18
, true // fullFit //19 FIXME TODO
, 1 // zIndex //-8 FIXME TODO
, null //-7
, 0 // this.counter //-6 FIXME TODO
, 0 // posx //-5
, 0 // posy //-4
, width // width //-3
, height // height //-2
, fuid // this.currentlayer.name //-1
)), wbId);
}
private int sendToWhiteboard(Client client, List<?> wbObj, Long wbId) {
try {
// Check if this User is the Mod:
if (client == null) {
return -1;
}
Map<Integer, Object> whiteboardObj = new HashMap<>();
int i = 0;
for (Object obj : wbObj) {
whiteboardObj.put(i++, obj);
}
Long roomId = client.getRoomId();
// log.debug("***** sendVars: " + whiteboardObj);
// Store event in list
String action = whiteboardObj.get(2).toString();
if (action.equals("deleteMindMapNodes")) {
// Simulate Single Delete Events for z-Index
List<?> actionObject = (List<?>) whiteboardObj.get(3);
@SuppressWarnings("unchecked")
List<List<?>> itemObjects = (List<List<?>>) actionObject.get(3);
Map<Integer, Object> whiteboardTempObj = new HashMap<>();
whiteboardTempObj.put(2, "delete");
for (List<?> itemObject : itemObjects) {
List<Object> tempActionObject = new ArrayList<>();
tempActionObject.add("mindmapnode");
tempActionObject.add(itemObject.get(0)); // z-Index -8
tempActionObject.add(null); // simulate -7
tempActionObject.add(null); // simulate -6
tempActionObject.add(null); // simulate -5
tempActionObject.add(null); // simulate -4
tempActionObject.add(null); // simulate -3
tempActionObject.add(null); // simulate -2
tempActionObject.add(itemObject.get(1)); // Object-Name -1
whiteboardTempObj.put(3, tempActionObject);
whiteboardManagement.addWhiteBoardObjectById(roomId, whiteboardTempObj, wbId);
}
} else {
whiteboardManagement.addWhiteBoardObjectById(roomId, whiteboardObj, wbId);
}
Map<String, Object> sendObject = new HashMap<String, Object>();
sendObject.put("id", wbId);
sendObject.put("param", wbObj);
boolean showDrawStatus = getWhiteboardDrawStatus();
sendToScope(roomId, "sendVarsToWhiteboardById", new Object[] { showDrawStatus ? client : null, sendObject });
} catch (Exception err) {
log.error("[sendToWhiteboard]", err);
return -1;
}
return 1;
}
public int sendVarsModeratorGeneral(Object vars) {
log.debug("*..*sendVars: " + vars);
try {
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
log.debug("***** id: " + currentClient.getStreamid());
boolean ismod = currentClient.getIsMod();
if (ismod) {
log.debug("CurrentScope :" + current.getScope().getName());
// Send to all Clients of the same Scope
sendMessageToCurrentScope("sendVarsToModeratorGeneral", vars, false);
return 1;
} else {
// log.debug("*..*you are not allowed to send: "+ismod);
return -1;
}
} catch (Exception err) {
log.error("[sendVarsModeratorGeneral]", err);
}
return -1;
}
public int sendMessage(Object newMessage) {
sendMessageToCurrentScope("sendVarsToMessage", newMessage, false);
return 1;
}
public int sendMessageAll(Object newMessage) {
sendMessageToCurrentScope("sendVarsToMessage", newMessage, true);
return 1;
}
/**
* send status for shared browsing to all members except self
* @param newMessage
* @return 1
*/
@SuppressWarnings({ "rawtypes" })
public synchronized int sendBrowserMessageToMembers(Object newMessage) {
try {
IConnection current = Red5.getConnectionLocal();
List newMessageList = (List) newMessage;
String action = newMessageList.get(0).toString();
BrowserStatus browserStatus = (BrowserStatus) current.getScope().getAttribute("browserStatus");
if (browserStatus == null) {
browserStatus = new BrowserStatus();
}
if (action.equals("initBrowser") || action.equals("newBrowserURL")) {
browserStatus.setBrowserInited(true);
browserStatus.setCurrentURL(newMessageList.get(1).toString());
} else if (action.equals("closeBrowserURL")) {
browserStatus.setBrowserInited(false);
}
current.getScope().setAttribute("browserStatus", browserStatus);
sendMessageToCurrentScope("sendVarsToMessage", newMessage, false);
} catch (Exception err) {
log.error("[sendMessage]", err);
}
return 1;
}
/**
* wrapper method
* @param newMessage
*/
public void sendMessageToMembers(List<?> newMessage) {
//Sync to all users of current scope
sendMessageToCurrentScope("sendVarsToMessage", newMessage, false);
}
/**
* General sync mechanism for all messages that are send from within the
* scope of the current client, but:
* <ul>
* <li>optionally do not send to self (see param: sendSelf)</li>
* <li>do not send to clients that are screen sharing clients</li>
* <li>do not send to clients that are audio/video clients (or potentially ones)</li>
* <li>do not send to connections where no RoomClient is registered</li>
* </ul>
*
* @param remoteMethodName The method to be called
* @param newMessage parameters
* @param sendSelf send to the current client as well
*/
public void sendMessageToCurrentScope(String remoteMethodName, Object newMessage, boolean sendSelf) {
sendMessageToCurrentScope(remoteMethodName, newMessage, sendSelf, false);
}
public void sendToScope(final Long roomId, String method, Object obj) {
new MessageSender(getRoomScope("" + roomId), method, obj) {
@Override
public boolean filter(IConnection conn) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
return rcl.isScreenClient()
|| rcl.getRoomId() == null || !rcl.getRoomId().equals(roomId) || userDao.get(rcl.getUserId()) == null;
}
}.start();
}
/**
* Only temporary for load test, with return argument for the client to have a result
*
* @param remoteMethodName
* @param newMessage
* @param sendSelf
* @return true
*/
@Deprecated
public boolean loadTestSyncMessage(String remoteMethodName, Object newMessage, boolean sendSelf) {
sendMessageToCurrentScope(remoteMethodName, newMessage, sendSelf, false);
return true;
}
/**
* General sync mechanism for all messages that are send from within the
* scope of the current client, but:
* <ul>
* <li>optionally do not send to self (see param: sendSelf)</li>
* <li>send to clients that are screen sharing clients based on parameter</li>
* <li>do not send to clients that are audio/video clients (or potentially ones)</li>
* <li>do not send to connections where no RoomClient is registered</li>
* </ul>
*
* @param remoteMethodName The method to be called
* @param newMessage parameters
* @param sendSelf send to the current client as well
* @param sendScreen send to the current client as well
*/
public void sendMessageToCurrentScope(final String remoteMethodName, final Object newMessage, final boolean sendSelf, final boolean sendScreen) {
new MessageSender(remoteMethodName, newMessage) {
@Override
public boolean filter(IConnection conn) {
IClient client = conn.getClient();
return (!sendScreen && SessionVariablesUtil.isScreenClient(client))
|| (!sendSelf && client.getId().equals(current.getClient().getId()));
}
}.start();
}
public abstract class MessageSender extends Thread {
final IScope scope;
final IConnection current;
final String remoteMethodName;
final Object newMessage;
public MessageSender(final String remoteMethodName, final Object newMessage) {
this((IScope)null, remoteMethodName, newMessage);
}
public MessageSender(IScope _scope, String remoteMethodName, Object newMessage) {
this(Red5.getConnectionLocal(), _scope, remoteMethodName, newMessage);
}
public MessageSender(IConnection current, String remoteMethodName, Object newMessage) {
this(current, null, remoteMethodName, newMessage);
}
public MessageSender(IConnection current, IScope _scope, String remoteMethodName, Object newMessage) {
this.current = current;
scope = _scope == null ? current.getScope() : _scope;
this.remoteMethodName = remoteMethodName;
this.newMessage = newMessage;
}
public abstract boolean filter(IConnection conn);
@Override
public void run() {
try {
if (scope == null) {
log.debug(String.format("[MessageSender] -> 'Unable to send message to NULL scope' %s, %s", remoteMethodName, newMessage));
} else {
if (log.isTraceEnabled()) {
log.trace(String.format("[MessageSender] -> 'sending message' %s, %s", remoteMethodName, newMessage));
}
// Send to all Clients of that Scope(Room)
int count = 0;
for (IConnection conn : scope.getClientConnections()) {
if (conn != null && conn instanceof IServiceCapableConnection) {
if (filter(conn)) {
continue;
}
((IServiceCapableConnection) conn).invoke(remoteMethodName, new Object[] { newMessage }, ScopeApplicationAdapter.this);
count++;
}
}
if (log.isTraceEnabled()) {
log.trace(String.format("[MessageSender] -> 'sending message to %s clients, DONE' %s", count, remoteMethodName));
}
}
} catch (Exception err) {
log.error(String.format("[MessageSender -> %s, %s]", remoteMethodName, newMessage), err);
}
}
}
/**
* wrapper method
* @param newMessage
* @return 1 in case of success, -1 otherwise
*/
public int sendMessageWithClient(Object newMessage) {
try {
sendMessageWithClientWithSyncObject(newMessage, true);
} catch (Exception err) {
log.error("[sendMessageWithClient] ", err);
return -1;
}
return 1;
}
/**
* wrapper method
* @param newMessage
* @param sync
* @return 1 in case of success, -1 otherwise
*/
public int sendMessageWithClientWithSyncObject(Object newMessage, boolean sync) {
try {
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
HashMap<String, Object> hsm = new HashMap<String, Object>();
hsm.put("client", currentClient);
hsm.put("message", newMessage);
//Sync to all users of current scope
sendMessageToCurrentScope("sendVarsToMessageWithClient", hsm, sync);
} catch (Exception err) {
log.error("[sendMessageWithClient] ", err);
return -1;
}
return 1;
}
/**
* Function is used to send the kick Trigger at the moment,
* it sends a general message to a specific clientId
*
* @param newMessage
* @param clientId
* @return 1 in case of success, -1 otherwise
*/
public int sendMessageById(Object newMessage, String clientId, IScope scope) {
try {
log.debug("### sendMessageById ###" + clientId);
HashMap<String, Object> hsm = new HashMap<String, Object>();
hsm.put("message", newMessage);
// broadcast Message to specific user with id inside the same Scope
for (IConnection conn : scope.getClientConnections()) {
if (conn != null) {
if (conn instanceof IServiceCapableConnection) {
if (conn.getClient().getId().equals(clientId)) {
((IServiceCapableConnection) conn).invoke("sendVarsToMessageWithClient", new Object[] { hsm }, this);
}
}
}
}
} catch (Exception err) {
log.error("[sendMessageWithClient] ", err);
return -1;
}
return 1;
}
/**
* Sends a message to a user in the same room by its clientId
*
* @param newMessage
* @param clientId
* @return 1 in case of no exceptions, -1 otherwise
*/
public int sendMessageWithClientById(Object newMessage, String clientId) {
try {
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
HashMap<String, Object> hsm = new HashMap<String, Object>();
hsm.put("client", currentClient);
hsm.put("message", newMessage);
// broadcast Message to specific user with id inside the same Scope
for (IConnection conn : current.getScope().getClientConnections()) {
if (conn.getClient().getId().equals(clientId)) {
((IServiceCapableConnection) conn).invoke("sendVarsToMessageWithClient", new Object[] { hsm }, this);
}
}
} catch (Exception err) {
log.error("[sendMessageWithClient] ", err);
return -1;
}
return 1;
}
public void sendMessageWithClientByPublicSID(Object message, String publicSID) {
try {
if (publicSID == null) {
log.warn("'null' publicSID was passed to sendMessageWithClientByPublicSID");
return;
}
// Get Room Id to send it to the correct Scope
Client currentClient = sessionManager.getClientByPublicSID(publicSID, null);
if (currentClient == null) {
throw new Exception("Could not Find RoomClient on List publicSID: " + publicSID);
}
IScope scope = getRoomScope("" + currentClient.getRoomId());
// log.debug("scopeHibernate "+scopeHibernate);
if (scope != null) {
// Notify the clients of the same scope (room) with userId
for (IConnection conn : scope.getClientConnections()) {
IClient client = conn.getClient();
if (SessionVariablesUtil.isScreenClient(client)) {
// screen sharing clients do not receive events
continue;
}
if (publicSID.equals(SessionVariablesUtil.getPublicSID(client))) {
((IServiceCapableConnection) conn).invoke("newMessageByRoomAndDomain", new Object[] { message }, this);
}
}
} else {
// Scope not yet started
}
} catch (Exception err) {
log.error("[sendMessageWithClientByPublicSID] ", err);
}
}
/**
* @deprecated this method should be reworked to use a single SQL query in
* the cache to get any client in the current room that is
* recording instead of iterating through connections!
* @return true in case there is recording session, false otherwise, null if any exception happend
*/
public boolean getInterviewRecordingStatus() {
try {
IConnection current = Red5.getConnectionLocal();
for (IConnection conn : current.getScope().getClientConnections()) {
if (conn != null) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl.getIsRecording()) {
return true;
}
}
}
} catch (Exception err) {
log.error("[getInterviewRecordingStatus]", err);
}
return false;
}
/**
* @deprecated @see {@link ScopeApplicationAdapter#getInterviewRecordingStatus()}
* @return - false if there were existing recording, true if recording was started successfully, null if any exception happens
*/
public boolean startInterviewRecording() {
try {
log.debug("----------- startInterviewRecording");
IConnection current = Red5.getConnectionLocal();
for (IConnection conn : current.getScope().getClientConnections()) {
if (conn != null) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl != null && rcl.getIsRecording()) {
return false;
}
}
}
Client current_rcl = sessionManager.getClientByStreamId(current.getClient().getId(), null);
// Also set the Recording Flag to Record all Participants that enter
// later
current_rcl.setIsRecording(true);
sessionManager.updateClientByStreamId(current.getClient().getId(), current_rcl, false, null);
Map<String, String> interviewStatus = new HashMap<String, String>();
interviewStatus.put("action", "start");
for (IConnection conn : current.getScope().getClientConnections()) {
if (conn != null) {
IClient client = conn.getClient();
if (SessionVariablesUtil.isScreenClient(client)) {
// screen sharing clients do not receive events
continue;
}
((IServiceCapableConnection) conn).invoke("interviewStatus", new Object[] { interviewStatus }, this);
log.debug("-- startInterviewRecording " + interviewStatus);
}
}
String recordingName = "Interview " + CalendarPatterns.getDateWithTimeByMiliSeconds(new Date());
recordingService.recordMeetingStream(current, current_rcl, recordingName, "", true);
return true;
} catch (Exception err) {
log.debug("[startInterviewRecording]", err);
}
return false;
}
@SuppressWarnings({ "rawtypes" })
public boolean sendRemoteCursorEvent(final String streamid, Map messageObj) {
new MessageSender("sendRemoteCursorEvent", messageObj) {
@Override
public boolean filter(IConnection conn) {
IClient client = conn.getClient();
return !SessionVariablesUtil.isScreenClient(client) || !conn.getClient().getId().equals(streamid);
}
}.start();
return true;
}
private Long checkRecordingClient(IConnection conn) {
Long recordingId = null;
if (conn != null) {
Client rcl = sessionManager.getClientByStreamId(conn.getClient().getId(), null);
if (rcl != null && rcl.getIsRecording()) {
rcl.setIsRecording(false);
recordingId = rcl.getRecordingId();
rcl.setRecordingId(null);
// Reset the Recording Flag to Record all
// Participants that enter later
sessionManager.updateClientByStreamId(conn.getClient().getId(), rcl, false, null);
}
}
return recordingId;
}
/**
* Stop the recording of the streams and send event to connected users of scope
*
* @return true if interview was found
*/
public boolean stopInterviewRecording() {
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
return _stopInterviewRecording(currentClient, current.getScope());
}
/**
* Stop the recording of the streams and send event to connected users of scope
*
* @return true if interview was found
*/
private boolean _stopInterviewRecording(Client currentClient, IScope currentScope) {
try {
log.debug("----------- stopInterviewRecording");
Long clientRecordingId = currentClient.getRecordingId();
for (IConnection conn : currentScope.getClientConnections()) {
Long recordingId = checkRecordingClient(conn);
if (recordingId != null) {
clientRecordingId = recordingId;
}
}
if (clientRecordingId == null) {
log.debug("stopInterviewRecording:: unable to find recording client");
return false;
}
recordingService.stopRecordAndSave(scope, currentClient, clientRecordingId);
Map<String, String> interviewStatus = new HashMap<String, String>();
interviewStatus.put("action", "stop");
sendMessageToCurrentScope("interviewStatus", interviewStatus, true);
return true;
} catch (Exception err) {
log.debug("[stopInterviewRecording]", err);
}
return false;
}
/**
* Get all ClientList Objects of that room and domain Used in
* lz.applyForModeration.lzx
*
* @return all ClientList Objects of that room
*/
public List<Client> getClientListScope() {
try {
IConnection current = Red5.getConnectionLocal();
Client currentClient = sessionManager.getClientByStreamId(current.getClient().getId(), null);
return sessionManager.getClientListByRoom(currentClient.getRoomId());
} catch (Exception err) {
log.debug("[getClientListScope]", err);
}
return new ArrayList<Client>();
}
private boolean getWhiteboardDrawStatus() {
return configurationDao.getWhiteboardDrawStatus();
}
public String getCryptKey() {
return configurationDao.getCryptKey();
}
public IScope getRoomScope(String room) {
if (Strings.isEmpty(room)) {
return null;
} else {
IScope globalScope = getContext().getGlobalScope();
IScope webAppKeyScope = globalScope.getScope(OpenmeetingsVariables.webAppRootKey);
return webAppKeyScope.getScope(room);
}
}
/*
* SIP transport methods
*/
private List<Long> getVerifiedActiveRoomIds(Server s) {
List<Long> result = new ArrayList<Long>(sessionManager.getActiveRoomIdsByServer(s));
//verify
for (Iterator<Long> i = result.iterator(); i.hasNext();) {
Long id = i.next();
List<Client> rcs = sessionManager.getClientListByRoom(id);
if (rcs.size() == 0 || (rcs.size() == 1 && rcs.get(0).isSipTransport())) {
i.remove();
}
}
return result.isEmpty() ? result : roomDao.getSipRooms(result);
}
public List<Long> getActiveRoomIds() {
List<Long> result = getVerifiedActiveRoomIds(null);
for (Server s : serverDao.getActiveServers()) {
result.addAll(getVerifiedActiveRoomIds(s));
}
return result.isEmpty() ? result : roomDao.getSipRooms(result);
}
private String getSipTransportLastname(Long roomId) {
return getSipTransportLastname(roomManager.getSipConferenceMembersNumber(roomId));
}
private static String getSipTransportLastname(Integer c) {
return (c != null && c > 0) ? "(" + (c - 1) + ")" : "";
}
public synchronized int updateSipTransport() {
log.debug("----------- updateSipTransport");
IConnection current = Red5.getConnectionLocal();
String streamid = current.getClient().getId();
Client client = sessionManager.getClientByStreamId(streamid, null);
Long roomId = client.getRoomId();
Integer count = roomManager.getSipConferenceMembersNumber(roomId);
String newNumber = getSipTransportLastname(count);
log.debug("getSipConferenceMembersNumber: " + newNumber);
if (!newNumber.equals(client.getLastname())) {
client.setLastname(newNumber);
sessionManager.updateClientByStreamId(streamid, client, false, null);
log.debug("updateSipTransport: {}, {}, {}, {}, {}", new Object[] { client.getPublicSID(), client.getRoomId(),
client.getFirstname(), client.getLastname(), client.getAvsettings() });
sendMessageWithClient(new String[] { "personal", client.getFirstname(), client.getLastname() });
}
return count != null && count > 0 ? count - 1 : 0;
}
public void setSipTransport(Long roomId, String publicSID, String broadCastId) {
log.debug("----------- setSipTransport");
IConnection current = Red5.getConnectionLocal();
IClient c = current.getClient();
String streamid = c.getId();
// Notify all clients of the same scope (room)
Client currentClient = sessionManager.getClientByStreamId(streamid, null);
currentClient.setSipTransport(true);
currentClient.setRoomId(roomId);
currentClient.setRoomEnter(new Date());
currentClient.setFirstname("SIP Transport");
currentClient.setLastname(getSipTransportLastname(roomId));
currentClient.setBroadCastID(Long.parseLong(broadCastId));
currentClient.setIsBroadcasting(true);
currentClient.setPublicSID(publicSID);
currentClient.setVWidth(120);
currentClient.setVHeight(90);
currentClient.setPicture_uri("phone.png");
sessionManager.updateClientByStreamId(streamid, currentClient, false, null);
SessionVariablesUtil.initClient(c, publicSID);
sendMessageToCurrentScope("addNewUser", currentClient, false);
}
public void broadcastRoom(RoomMessage msg) {
log.debug("Sending WebSocket message: {} {}", msg.getType(), msg instanceof TextRoomMessage ? ((TextRoomMessage)msg).getText() : "");
Application app = Application.get(OpenmeetingsVariables.wicketApplicationName);
WebSocketSettings settings = WebSocketSettings.Holder.get(app);
IWebSocketConnectionRegistry registry = settings.getConnectionRegistry();
for (IWebSocketConnection wc : registry.getConnections(app)) {
if (wc != null && wc.isOpen()) {
wc.sendMessage(msg);
}
}
}
}