You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2017/08/31 02:30:42 UTC

[2/2] openmeetings git commit: [OPENMEETINGS-1638] initial attempt to implement recording in the room

[OPENMEETINGS-1638] initial attempt to implement recording in the room


Project: http://git-wip-us.apache.org/repos/asf/openmeetings/repo
Commit: http://git-wip-us.apache.org/repos/asf/openmeetings/commit/5fd782fc
Tree: http://git-wip-us.apache.org/repos/asf/openmeetings/tree/5fd782fc
Diff: http://git-wip-us.apache.org/repos/asf/openmeetings/diff/5fd782fc

Branch: refs/heads/master
Commit: 5fd782fcbf365dbaf54b09a26dd978ba403662c2
Parents: 3ede291
Author: Maxim Solodovnik <so...@gmail.com>
Authored: Thu Aug 31 00:13:12 2017 +0700
Committer: Maxim Solodovnik <so...@gmail.com>
Committed: Thu Aug 31 00:13:12 2017 +0700

----------------------------------------------------------------------
 .../openmeetings/core/remote/MobileService.java |   6 +-
 .../core/remote/RecordingService.java           | 185 ++++----
 .../core/remote/ScopeApplicationAdapter.java    | 204 ++-------
 .../core/session/SessionManager.java            |  34 +-
 .../test/rtmp/LoadTestRtmpClient.java           |   2 +-
 .../org/apache/openmeetings/IApplication.java   |  16 +-
 .../db/dao/server/ISessionManager.java          |  13 +-
 .../openmeetings/db/entity/basic/Client.java    | 120 +++--
 .../openmeetings/db/entity/basic/IClient.java   |  10 +
 .../db/entity/room/StreamClient.java            |  44 +-
 .../web/admin/connection/ConnectionsPanel.java  |   2 +-
 .../openmeetings/web/admin/rooms/RoomForm.java  |   2 +-
 .../openmeetings/web/app/Application.java       |  18 +-
 .../apache/openmeetings/web/room/RoomPanel.java |  38 +-
 .../web/room/wb/AbstractWbPanel.java            |  72 ++-
 .../web/room/wb/InterviewWbPanel.java           |  28 +-
 .../openmeetings/web/room/wb/WbAction.java      |   2 +
 .../openmeetings/web/room/wb/WbPanel.java       | 437 +++++++++----------
 .../openmeetings/web/room/wb/interviewwb.js     |  28 +-
 .../WEB-INF/classes/applicationContext.xml      |  12 +-
 20 files changed, 657 insertions(+), 616 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/MobileService.java
----------------------------------------------------------------------
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/MobileService.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/MobileService.java
index 6ea3bee..af7b106 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/MobileService.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/MobileService.java
@@ -216,7 +216,7 @@ public class MobileService {
 	public StreamClient create(User u, Sessiondata sd) {
 		StreamClient c = new StreamClient();
 		c.setType(StreamClient.Type.mobile);
-		c.setOwnerSid(sd.getSessionId());
+		c.setSid(sd.getSessionId());
 		c.setUid(UUID.randomUUID().toString());
 		return create(c, u);
 	}
@@ -226,7 +226,7 @@ public class MobileService {
 		c.setFirstname(u.getFirstname());
 		c.setLastname(u.getLastname());
 		if (c.getUserId() != null) {
-			c.setUsername(u.getLogin());
+			c.setLogin(u.getLogin());
 			c.setFirstname(u.getFirstname());
 			c.setLastname(u.getLastname());
 			c.setEmail(u.getAddress() == null ? null : u.getAddress().getEmail());
@@ -274,7 +274,7 @@ public class MobileService {
 					add(map, "firstname", c.getFirstname());
 					add(map, "lastname", c.getLastname());
 					add(map, "publicSid", c.getUid());
-					add(map, "login", c.getUsername());
+					add(map, "login", c.getLogin());
 					add(map, "email", c.getEmail());
 					add(map, "avsettings", c.getAvsettings());
 					add(map, "interviewPodId", c.getInterviewPodId());

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/RecordingService.java
----------------------------------------------------------------------
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/RecordingService.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/RecordingService.java
index 0b086d5..81ce1fc 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/RecordingService.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/RecordingService.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.core.remote;
 
+import static org.apache.openmeetings.core.remote.ScopeApplicationAdapter.getApp;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
 
 import java.util.Date;
@@ -25,6 +26,7 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.openmeetings.IApplication;
 import org.apache.openmeetings.core.converter.BaseConverter;
 import org.apache.openmeetings.core.data.record.converter.InterviewConverterTask;
 import org.apache.openmeetings.core.data.record.converter.RecordingConverterTask;
@@ -37,6 +39,7 @@ import org.apache.openmeetings.db.dao.record.RecordingMetaDeltaDao;
 import org.apache.openmeetings.db.dao.server.ISessionManager;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.basic.Client;
+import org.apache.openmeetings.db.entity.basic.IClient;
 import org.apache.openmeetings.db.entity.file.FileItem.Type;
 import org.apache.openmeetings.db.entity.record.Recording;
 import org.apache.openmeetings.db.entity.record.RecordingMetaData;
@@ -95,7 +98,7 @@ public class RecordingService implements IPendingServiceCallback {
 		return "rec_" + recordingId + "_stream_" + streamid + "_" + dateString;
 	}
 
-	public String recordMeetingStream(IConnection current, StreamClient client, String roomRecordingName, String comment, boolean isInterview) {
+	public void startRecording(IScope scope, IClient client, boolean isInterview) {
 		try {
 			log.debug("##REC:: recordMeetingStream ::");
 
@@ -106,24 +109,23 @@ public class RecordingService implements IPendingServiceCallback {
 			Recording recording = new Recording();
 
 			recording.setHash(UUID.randomUUID().toString());
-			recording.setName(roomRecordingName);
+			recording.setName(String.format("%s %s", isInterview ? "Interview" : "Recording", CalendarPatterns.getDateWithTimeByMiliSeconds(new Date())));
 			Long ownerId = client.getUserId();
-			if (ownerId != null && ownerId < 0) {
-				User c = userDao.get(-ownerId);
-				if (c != null) {
-					ownerId = c.getOwnerId();
-				}
+			User u = userDao.get(ownerId);
+			if (u != null && User.Type.contact == u.getType()) {
+				ownerId = u.getOwnerId();
 			}
 			recording.setInsertedBy(ownerId);
 			recording.setType(Type.Recording);
-			recording.setComment(comment);
 			recording.setInterview(isInterview);
 
 			recording.setRoomId(roomId);
 			recording.setRecordStart(now);
 
-			recording.setWidth(client.getWidth());
-			recording.setHeight(client.getHeight());
+			if (!isInterview) {
+				recording.setWidth(client.getWidth());
+				recording.setHeight(client.getHeight());
+			}
 
 			recording.setOwnerId(ownerId);
 			recording.setStatus(Recording.Status.RECORDING);
@@ -135,10 +137,17 @@ public class RecordingService implements IPendingServiceCallback {
 			// Update Client and set Flag
 			client.setRecordingStarted(true);
 			client.setRecordingId(recordingId);
+			if (!(client instanceof Client)) {
+				IApplication iapp = getApp();
+				Client c = iapp.getOmClientBySid(client.getSid());
+				c.setRecordingId(recordingId);
+				c.setRecordingStarted(true);
+				iapp.update(c);
+			}
 			sessionManager.update(client);
 
 			// get all stream and start recording them
-			for (IConnection conn : current.getScope().getClientConnections()) {
+			for (IConnection conn : scope.getClientConnections()) {
 				if (conn != null) {
 					if (conn instanceof IServiceCapableConnection) {
 						StreamClient rcl = sessionManager.get(IClientUtil.getId(conn.getClient()));
@@ -192,13 +201,77 @@ public class RecordingService implements IPendingServiceCallback {
 				}
 			}
 			// Send every user a notification that the recording did start
-			WebSocketHelper.sendRoom(new TextRoomMessage(roomId, ownerId, RoomMessage.Type.recordingStarted, client.getOwnerSid()));
-			return roomRecordingName;
+			WebSocketHelper.sendRoom(new TextRoomMessage(roomId, ownerId, RoomMessage.Type.recordingStarted, client.getSid()));
+		} catch (Exception err) {
+			log.error("[startRecording]", err);
+		}
+	}
+
+	public void stopRecording(IScope scope, IClient client) {
+		try {
+			log.debug("stopRecordAndSave {}, {}", client.getLogin(), client.getRemoteAddress());
+			IApplication iapp = getApp();
+			Client recClient = null;
+			for (Client c : iapp.getOmRoomClients(client.getRoomId())) {
+				if (c.getRecordingId() != null) {
+					recClient = c;
+					break;
+				}
+			}
+			if (recClient == null) {
+				log.error("Unable to find recordingId on recording stop");
+				return;
+			}
+			WebSocketHelper.sendRoom(new TextRoomMessage(recClient.getRoomId(), recClient.getUserId(), RoomMessage.Type.recordingStoped, recClient.getSid()));
+
+			// get all stream and stop recording them
+			for (IConnection conn : scope.getClientConnections()) {
+				if (conn != null) {
+					if (conn instanceof IServiceCapableConnection) {
+						StreamClient rcl = sessionManager.get(IClientUtil.getId(conn.getClient()));
+
+						if (rcl == null) {
+							continue;
+						}
+						log.debug("is this users still alive? stop it : {}", rcl);
 
+						if (Client.Type.sharing == rcl.getType()) {
+							if (rcl.getRecordingId() != null && (rcl.isSharingStarted() || rcl.isRecordingStarted())) {
+								// Stop FLV Recording
+								stopRecordingShow(scope, rcl.getBroadCastId(), rcl.getMetaId());
+
+								// Update Meta Data
+								metaDataDao.updateEndDate(rcl.getMetaId(), new Date());
+							}
+						} else if (rcl.getAvsettings().equals("av") || rcl.getAvsettings().equals("a") || rcl.getAvsettings().equals("v")) {
+							stopRecordingShow(scope, rcl.getBroadCastId(), rcl.getMetaId());
+
+							// Update Meta Data
+							metaDataDao.updateEndDate(rcl.getMetaId(), new Date());
+						}
+					}
+				}
+			}
+			// Store to database
+			Long recordingId = recClient.getRecordingId();
+
+			recordingDao.updateEndTime(recordingId, new Date());
+
+			// Reset values
+			recClient.setRecordingId(null);
+			recClient.setRecordingStarted(false);
+			sessionManager.update(recClient);
+			log.debug("recordingConverterTask {}", recordingConverterTask);
+
+			Recording recording = recordingDao.get(recordingId);
+			if (recording.isInterview()) {
+				interviewConverterTask.startConversionThread(recordingId);
+			} else {
+				recordingConverterTask.startConversionThread(recordingId);
+			}
 		} catch (Exception err) {
-			log.error("[recordMeetingStream]", err);
+			log.error("[-- stopRecording --]", err);
 		}
-		return null;
 	}
 
 	/**
@@ -250,8 +323,8 @@ public class RecordingService implements IPendingServiceCallback {
 	 */
 	public void stopRecordingShow(IScope scope, String broadcastId, Long metaId) {
 		try {
-			log.debug("** stopRecordingShow: " + scope);
-			log.debug("### Stop recording show for broadcastId: " + broadcastId + " || " + scope.getContextPath());
+			log.debug("** stopRecordingShow: {}", scope);
+			log.debug("### Stop recording show for broadcastId: {} || {}", broadcastId, scope.getContextPath());
 
 			IBroadcastStream stream = scopeApplicationAdapter.getBroadcastStream(scope, broadcastId);
 
@@ -282,10 +355,10 @@ public class RecordingService implements IPendingServiceCallback {
 			// this would normally happen in the Listener
 			Status s = metaData.getStreamStatus();
 			if (Status.NONE == s) {
-				log.debug("Stream was not started, no need to stop :: stream with id " + metaId);
+				log.debug("Stream was not started, no need to stop :: stream with id {}", metaId);
 			} else {
 				metaData.setStreamStatus(listenerAdapter == null && s == Status.STARTED ? Status.STOPPED : Status.STOPPING);
-				log.debug("Stopping the stream :: New status == " + metaData.getStreamStatus());
+				log.debug("Stopping the stream :: New status == {}", metaData.getStreamStatus());
 			}
 			metaDataDao.update(metaData);
 			if (listenerAdapter == null) {
@@ -293,7 +366,7 @@ public class RecordingService implements IPendingServiceCallback {
 				log.debug("Available Streams :: " + streamListeners.size());
 
 				for (Long entryKey : streamListeners.keySet()) {
-					log.debug("Stored recordingMetaDataId in Map: " + entryKey);
+					log.debug("Stored recordingMetaDataId in Map: {}", entryKey);
 				}
 				throw new IllegalStateException("Could not find Listener to stop! recordingMetaDataId " + metaId);
 			}
@@ -306,78 +379,12 @@ public class RecordingService implements IPendingServiceCallback {
 		}
 	}
 
-	public void stopRecordAndSave(IScope scope, StreamClient client, Long storedRecordingId) {
-		try {
-			log.debug("stopRecordAndSave " + client.getUsername() + "," + client.getUserip());
-			WebSocketHelper.sendRoom(new TextRoomMessage(client.getRoomId(), client.getUserId(), RoomMessage.Type.recordingStoped, client.getOwnerSid()));
-
-			// get all stream and stop recording them
-			for (IConnection conn : scope.getClientConnections()) {
-				if (conn != null) {
-					if (conn instanceof IServiceCapableConnection) {
-						StreamClient rcl = sessionManager.get(IClientUtil.getId(conn.getClient()));
-
-						if (rcl == null) {
-							continue;
-						}
-						log.debug("is this users still alive? stop it :" + rcl);
-
-						if (Client.Type.sharing == rcl.getType()) {
-							if (rcl.getRecordingId() != null && (rcl.isSharingStarted() || rcl.isRecordingStarted())) {
-								// Stop FLV Recording
-								stopRecordingShow(scope, rcl.getBroadCastId(), rcl.getMetaId());
-
-								// Update Meta Data
-								metaDataDao.updateEndDate(rcl.getMetaId(), new Date());
-							}
-						} else if (rcl.getAvsettings().equals("av") || rcl.getAvsettings().equals("a") || rcl.getAvsettings().equals("v")) {
-							stopRecordingShow(scope, rcl.getBroadCastId(), rcl.getMetaId());
-
-							// Update Meta Data
-							metaDataDao.updateEndDate(rcl.getMetaId(), new Date());
-						}
-					}
-				}
-			}
-			// Store to database
-			Long recordingId = client.getRecordingId();
-
-			// In the Case of an Interview the stopping client does not mean
-			// that its actually the recording client
-			if (storedRecordingId != null) {
-				recordingId = storedRecordingId;
-			}
-
-			if (recordingId != null) {
-				recordingDao.updateEndTime(recordingId, new Date());
-
-				// Reset values
-				client.setRecordingId(null);
-				client.setRecordingStarted(false);
-
-				sessionManager.update(client);
-				log.debug("recordingConverterTask {}", recordingConverterTask);
-
-				Recording recording = recordingDao.get(recordingId);
-				if (!recording.isInterview()) {
-					recordingConverterTask.startConversionThread(recordingId);
-				} else {
-					interviewConverterTask.startConversionThread(recordingId);
-				}
-			}
-		} catch (Exception err) {
-			log.error("[-- stopRecordAndSave --]", err);
-		}
-	}
-
 	public void stopRecordingShowForClient(IScope scope, StreamClient rcl) {
 		try {
-			// this cannot be handled here, as to stop a stream and to leave a
-			// room is not
+			// this cannot be handled here, as to stop a stream and to leave a room is not
 			// the same type of event.
-			// StreamService.addRoomClientEnterEventFunc(rcl, roomrecordingName,
-			// rcl.getUserip(), false);
-			log.debug("### stopRecordingShowForClient: " + rcl);
+			// StreamService.addRoomClientEnterEventFunc(rcl, roomrecordingName, rcl.getUserip(), false);
+			log.debug("### stopRecordingShowForClient: {}", rcl);
 
 			if (Client.Type.sharing == rcl.getType()) {
 				if (rcl.getRecordingId() != null && rcl.isSharingStarted()) {

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/ScopeApplicationAdapter.java
----------------------------------------------------------------------
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/ScopeApplicationAdapter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/ScopeApplicationAdapter.java
index 5804cf9..f3b9636 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/ScopeApplicationAdapter.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/ScopeApplicationAdapter.java
@@ -39,7 +39,6 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -62,7 +61,6 @@ import org.apache.openmeetings.db.entity.basic.Client;
 import org.apache.openmeetings.db.entity.log.ConferenceLog;
 import org.apache.openmeetings.db.entity.room.Room;
 import org.apache.openmeetings.db.entity.room.StreamClient;
-import org.apache.openmeetings.util.CalendarPatterns;
 import org.apache.openmeetings.util.InitializationContainer;
 import org.apache.openmeetings.util.NullStringer;
 import org.apache.openmeetings.util.OmFileHelper;
@@ -125,6 +123,10 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	@Autowired
 	private RecordingDao recordingDao;
 
+	public static IApplication getApp() {
+		return (IApplication)Application.get(wicketApplicationName);
+	}
+
 	@Override
 	public void resultReceived(IPendingServiceCall arg0) {
 		if (_log.isTraceEnabled()) {
@@ -167,7 +169,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 
 			InitializationContainer.initComplete = true;
 			// Init properties
-			IApplication iapp = (IApplication)Application.get(wicketApplicationName);
+			IApplication iapp = getApp();
 			iapp.setXFrameOptions(cfgDao.getConfValue(CONFIG_HEADER_XFRAME, String.class, HEADER_XFRAME_SAMEORIGIN));
 			iapp.setContentSecurityPolicy(cfgDao.getConfValue(CONFIG_HEADER_CSP, String.class, HEADER_CSP_SELF));
 			iapp.updateJpaAddresses(cfgDao);
@@ -232,15 +234,15 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 			rcm.setType(Client.Type.room);
 		}
 		rcm.setUid(Strings.isEmpty(uid) ? UUID.randomUUID().toString() : uid);
-		rcm.setOwnerSid(ownerSid);
-		if (sipDao.getUid() != null && sipDao.getUid().equals(rcm.getOwnerSid())) {
+		rcm.setSid(ownerSid);
+		if (sipDao.getUid() != null && sipDao.getUid().equals(rcm.getSid())) {
 			rcm.setType(Client.Type.sip);
 		}
 		rcm.setUserport(conn.getRemotePort());
-		rcm.setUserip(conn.getRemoteAddress());
+		rcm.setRemoteAddress(conn.getRemoteAddress());
 		rcm.setSwfurl(swfURL);
 		rcm.setTcUrl(tcUrl);
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
+		IApplication iapp = getApp();
 		Number width = (Number)connParams.get(WIDTH_PARAM);
 		Number height = (Number)connParams.get(HEIGHT_PARAM);
 		if (width != null && height != null) {
@@ -260,7 +262,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 
 		// Log the User
 		conferenceLogDao.add(ConferenceLog.Type.clientConnect,
-				rcm.getUserId(), streamId, null, rcm.getUserip(),
+				rcm.getUserId(), streamId, null, rcm.getRemoteAddress(),
 				rcm.getScope());
 		return true;
 	}
@@ -289,7 +291,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 
 					returnMap.put("result", "stopRecordingOnly");
 
-					recordingService.stopRecordAndSave(current.getScope(), client, null);
+					recordingService.stopRecording(current.getScope(), client);
 				}
 				if (Boolean.parseBoolean("" + map.get("stopPublishing")) && client.isPublishStarted()) {
 					changed = true;
@@ -442,7 +444,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 				sendStreamClosed(client);
 			}
 
-			_log.debug("removing Username {} {},  streamid: {}", client.getUsername()
+			_log.debug("removing Username {} {},  streamid: {}", client.getLogin()
 					, client.getConnectedSince(), client.getId());
 
 			// stop and save any recordings
@@ -450,9 +452,9 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 				_log.debug("*** roomLeave Current Client is Recording - stop that");
 				if (client.getInterviewPodId() != null) {
 					//interview, TODO need better check
-					_stopInterviewRecording(client, scope);
+					stopInterviewRecording(client);
 				} else {
-					recordingService.stopRecordAndSave(scope, client, null);
+					recordingService.stopRecording(scope, client);
 				}
 			}
 			recordingService.stopRecordingShowForClient(scope, client);
@@ -462,8 +464,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 			_log.debug("currentScope " + scope);
 
 			if (Client.Type.mobile == client.getType() || Client.Type.sip == client.getType()) {
-				IApplication app = (IApplication)Application.get(wicketApplicationName);
-				app.exit(client.getUid());
+				getApp().exit(client.getUid());
 			}
 			sessionManager.remove(client.getUid());
 		} catch (Exception err) {
@@ -500,8 +501,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 				}
 			}
 			if (Client.Type.sip == c.getType()) {
-				IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-				Client cl = iapp.getOmClientBySid(c.getOwnerSid());
+				Client cl = getApp().getOmClientBySid(c.getSid());
 				String newNumber = getSipTransportLastname(c.getRoomId());
 				cl.getUser().setLastname(newNumber);
 				c.setLastname(newNumber);
@@ -509,8 +509,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 			}
 			sessionManager.update(c);
 			if (Client.Type.sharing == c.getType() && c.isRecordingStarted()) {
-				String recordingName = "Recording " + CalendarPatterns.getDateWithTimeByMiliSeconds(new Date());
-				recordingService.recordMeetingStream(current, c, recordingName, "", false);
+				recordingService.startRecording(current.getScope(), c, false);
 			}
 
 			_log.debug("newStream SEND: {}", c);
@@ -551,7 +550,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 				}
 			}.start();
 			JSONObject obj = new JSONObject()
-					.put("ownerSid", c.getOwnerSid())
+					.put("ownerSid", c.getSid())
 					.put("uid", c.getUid())
 					.put("type", c.getType())
 					.put("streamId", current.getClient().getId())
@@ -615,7 +614,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 
 	private static void sendSharingStoped(StreamClient rcl) {
 		JSONObject obj = new JSONObject()
-				.put("ownerSid", rcl.getOwnerSid())
+				.put("ownerSid", rcl.getSid())
 				.put("uid", rcl.getUid());
 		WebSocketHelper.sendRoom(new TextRoomMessage(rcl.getRoomId(), rcl.getUserId(), RoomMessage.Type.sharingStoped, obj.toString()));
 	}
@@ -623,7 +622,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	private static void sendStreamClosed(StreamClient rcl) {
 		JSONObject obj = new JSONObject()
 				.put("uid", rcl.getUid())
-				.put("ownerSid", rcl.getOwnerSid())
+				.put("ownerSid", rcl.getSid())
 				.put("broadcastId", rcl.getBroadCastId());
 		WebSocketHelper.sendRoom(new TextRoomMessage(rcl.getRoomId(), rcl.getUserId(), RoomMessage.Type.closeStream, obj.toString()));
 	}
@@ -718,20 +717,6 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	}
 
 	/**
-	 * 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>
@@ -927,83 +912,6 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 		return 1;
 	}
 
-	/**
-	 * @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
-	 */
-	@Deprecated
-	public boolean getInterviewRecordingStatus() {
-		try {
-			IConnection current = Red5.getConnectionLocal();
-
-			for (IConnection conn : current.getScope().getClientConnections()) {
-				if (conn != null) {
-					StreamClient rcl = sessionManager.get(IClientUtil.getId(conn.getClient()));
-
-					if (rcl != null && rcl.isRecordingStarted()) {
-						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
-	 */
-	@Deprecated
-	public boolean startInterviewRecording() {
-		try {
-			_log.debug("-----------  startInterviewRecording");
-			IConnection current = Red5.getConnectionLocal();
-
-			for (IConnection conn : current.getScope().getClientConnections()) {
-				if (conn != null) {
-					StreamClient rcl = sessionManager.get(IClientUtil.getId(conn.getClient()));
-
-					if (rcl != null && rcl.isRecordingStarted()) {
-						return false;
-					}
-				}
-			}
-			StreamClient rcl = sessionManager.get(IClientUtil.getId(current.getClient()));
-
-			// Also set the Recording Flag to Record all Participants that enter later
-			rcl.setRecordingStarted(true);
-			sessionManager.update(rcl);
-
-			Map<String, String> interviewStatus = new HashMap<>();
-			interviewStatus.put("action", "start");
-
-			for (IConnection conn : current.getScope().getClientConnections()) {
-				if (conn != null) {
-					IClient client = conn.getClient();
-					if (IClientUtil.isSharing(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, 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, this) {
@@ -1018,14 +926,18 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	}
 
 	/**
-	 * Stop the recording of the streams and send event to connected users of scope
+	 * Starts recording in interview room
 	 *
-	 * @return true if interview was found
+	 * @return - false if there were existing recording, true if recording was started successfully, null if any exception happens
 	 */
-	public boolean stopInterviewRecording() {
-		IConnection current = Red5.getConnectionLocal();
-		StreamClient currentClient = sessionManager.get(IClientUtil.getId(current.getClient()));
-		return _stopInterviewRecording(currentClient, current.getScope());
+	public void startInterviewRecording(Client c) {
+		_log.debug("-----------  startInterviewRecording");
+
+		if (c == null || sessionManager.getRecordingCount(c.getRoom().getId()) > 0) {
+			return;
+		}
+
+		recordingService.startRecording(getRoomScope("" + c.getRoom().getId()), c, true);
 	}
 
 	/**
@@ -1033,46 +945,9 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	 *
 	 * @return true if interview was found
 	 */
-	private boolean _stopInterviewRecording(StreamClient currentClient, IScope currentScope) {
-		try {
-			_log.debug("-----------  stopInterviewRecording");
-			Long clientRecordingId = currentClient.getRecordingId();
-
-			for (IConnection conn : currentScope.getClientConnections()) {
-				Long recordingId = null;
-				if (conn != null) {
-					StreamClient rcl = sessionManager.get(IClientUtil.getId(conn.getClient()));
-					if (rcl != null && rcl.isRecordingStarted()) {
-						rcl.setRecordingStarted(false);
-						recordingId = rcl.getRecordingId();
-						rcl.setRecordingId(null);
-
-						// Reset the Recording Flag to Record all
-						// Participants that enter later
-						sessionManager.update(rcl);
-					}
-				}
-				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<>();
-			interviewStatus.put("action", "stop");
-
-			sendMessageToCurrentScope("interviewStatus", interviewStatus, true);
-			return true;
-
-		} catch (Exception err) {
-			_log.debug("[stopInterviewRecording]", err);
-		}
-		return false;
+	public void stopInterviewRecording(org.apache.openmeetings.db.entity.basic.IClient c) {
+		_log.debug("-----------  stopInterviewRecording");
+		recordingService.stopRecording(getRoomScope("" + c.getRoomId()), c);
 	}
 
 	public IScope getRoomScope(String room) {
@@ -1090,7 +965,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 		IConnection current = Red5.getConnectionLocal();
 		StreamClient client = sessionManager.get(IClientUtil.getId(current.getClient()));
 		WebSocketHelper.sendRoom(new TextRoomMessage(client.getRoomId(), client.getUserId(), RoomMessage.Type.audioActivity
-				, new JSONObject().put("sid", client.getOwnerSid()).put("active", active).toString()));
+				, new JSONObject().put("sid", client.getSid()).put("active", active).toString()));
 	}
 
 	/*
@@ -1100,8 +975,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 		List<String> ids = new ArrayList<>();
 		IConnection current = Red5.getConnectionLocal();
 		StreamClient client = sessionManager.get(IClientUtil.getId(current.getClient()));
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-		for (Client c: iapp.getOmRoomClients(client.getRoomId()) ) {
+		for (Client c: getApp().getOmRoomClients(client.getRoomId()) ) {
 			for (Client.Stream s : c.getStreams()) {
 				ids.add(s.getBroadcastId());
 			}
@@ -1137,7 +1011,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	}
 
 	public List<Long> getActiveRoomIds() {
-		return new ArrayList<>(((IApplication)Application.get(wicketApplicationName)).getActiveRoomIds());
+		return new ArrayList<>(getApp().getActiveRoomIds());
 	}
 
 	public synchronized int updateSipTransport() {
@@ -1149,8 +1023,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 		String newNumber = getSipTransportLastname(count);
 		_log.debug("getSipConferenceMembersNumber: " + newNumber);
 		if (!newNumber.equals(client.getLastname())) {
-			IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-			Client cl = iapp.getOmClientBySid(client.getOwnerSid());
+			Client cl = getApp().getOmClientBySid(client.getSid());
 			cl.getUser().setLastname(newNumber);
 			client.setLastname(newNumber);
 			sessionManager.update(client);
@@ -1169,8 +1042,7 @@ public class ScopeApplicationAdapter extends MultiThreadedApplicationAdapter imp
 	public CheckDto check() {
 		IConnection current = Red5.getConnectionLocal();
 		StreamClient c = sessionManager.get(IClientUtil.getId(current.getClient()));
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-		Client cl = iapp.getOmClientBySid(c.getOwnerSid());
+		Client cl = getApp().getOmClientBySid(c.getSid());
 		return new CheckDto(cl);
 	}
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-core/src/main/java/org/apache/openmeetings/core/session/SessionManager.java
----------------------------------------------------------------------
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/session/SessionManager.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/session/SessionManager.java
index 869075a..12cf66f 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/session/SessionManager.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/session/SessionManager.java
@@ -18,8 +18,8 @@
  */
 package org.apache.openmeetings.core.session;
 
+import static org.apache.openmeetings.core.remote.ScopeApplicationAdapter.getApp;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.wicketApplicationName;
 
 import java.util.Collection;
 import java.util.Date;
@@ -32,8 +32,8 @@ import java.util.stream.Collectors;
 import org.apache.openmeetings.IApplication;
 import org.apache.openmeetings.db.dao.server.ISessionManager;
 import org.apache.openmeetings.db.entity.basic.Client;
+import org.apache.openmeetings.db.entity.basic.IClient;
 import org.apache.openmeetings.db.entity.room.StreamClient;
-import org.apache.wicket.Application;
 import org.red5.logging.Red5LoggerFactory;
 import org.red5.server.Server;
 import org.slf4j.Logger;
@@ -50,8 +50,7 @@ public class SessionManager implements ISessionManager {
 	protected static final Logger log = Red5LoggerFactory.getLogger(SessionManager.class, webAppRootKey);
 
 	private static Map<String, StreamClient> getClients() {
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-		return iapp.getStreamClients();
+		return getApp().getStreamClients();
 	}
 
 	@Override
@@ -59,11 +58,12 @@ public class SessionManager implements ISessionManager {
 		if (c == null) {
 			return null;
 		}
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
+		IApplication iapp = getApp();
 		c.setServerId(iapp.getServerId());
 		c.setConnectedSince(new Date());
 		c.setRoomEnter(new Date());
-		return iapp.update(c);
+		iapp.update(c);
+		return c;
 	}
 
 	@Override
@@ -80,9 +80,8 @@ public class SessionManager implements ISessionManager {
 	}
 
 	@Override
-	public StreamClient update(StreamClient rcm) {
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-		return iapp.update(rcm);
+	public void update(IClient rcm) {
+		getApp().update(rcm);
 	}
 
 	@Override
@@ -102,20 +101,6 @@ public class SessionManager implements ISessionManager {
 	}
 
 	@Override
-	public Collection<StreamClient> listByRoomAll(Long roomId) {
-		return list().stream()
-				.filter(c -> roomId.equals(c.getRoomId()))
-				.collect(Collectors.toList());
-	}
-
-	@Override
-	public List<StreamClient> listModeratorByRoom(Long roomId) {
-		return list().stream()
-				.filter(c -> roomId.equals(c.getRoomId()) && c.isMod())
-				.collect(Collectors.toList());
-	}
-
-	@Override
 	public long getRecordingCount(Long roomId) {
 		if (roomId == null) {
 			return 0;
@@ -137,8 +122,7 @@ public class SessionManager implements ISessionManager {
 
 	@Override
 	public Set<Long> getActiveRoomIds() {
-		IApplication iapp = (IApplication)Application.get(wicketApplicationName);
-		return iapp.getActiveRoomIds();
+		return getApp().getActiveRoomIds();
 	}
 
 	@Override

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-core/src/test/java/org/apache/openmeetings/test/rtmp/LoadTestRtmpClient.java
----------------------------------------------------------------------
diff --git a/openmeetings-core/src/test/java/org/apache/openmeetings/test/rtmp/LoadTestRtmpClient.java b/openmeetings-core/src/test/java/org/apache/openmeetings/test/rtmp/LoadTestRtmpClient.java
index 3a2a886..8d29056 100644
--- a/openmeetings-core/src/test/java/org/apache/openmeetings/test/rtmp/LoadTestRtmpClient.java
+++ b/openmeetings-core/src/test/java/org/apache/openmeetings/test/rtmp/LoadTestRtmpClient.java
@@ -86,7 +86,7 @@ public class LoadTestRtmpClient extends RTMPClient implements IPendingServiceCal
 			map.put("instanceId", instanceId);
 			map.put("count", counterCalls);
 			calls.put(counterCalls, new CallObject(new Date()));
-			invoke("loadTestSyncMessage", new Object[] {"syncMessageToCurrentScopeResult", map, true }, this);
+			invoke("sendMessageToCurrentScope", new Object[] {"syncMessageToCurrentScopeResult", map, true, false }, this);
 
 		} else {
 			System.err.println("Call running " + counterCalls);

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-db/src/main/java/org/apache/openmeetings/IApplication.java
----------------------------------------------------------------------
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/IApplication.java b/openmeetings-db/src/main/java/org/apache/openmeetings/IApplication.java
index 77f2d6f..f644172 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/IApplication.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/IApplication.java
@@ -29,6 +29,7 @@ import javax.servlet.ServletContext;
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
 import org.apache.openmeetings.db.dto.room.Whiteboards;
 import org.apache.openmeetings.db.entity.basic.Client;
+import org.apache.openmeetings.db.entity.basic.IClient;
 import org.apache.openmeetings.db.entity.room.Invitation;
 import org.apache.openmeetings.db.entity.room.StreamClient;
 import org.apache.openmeetings.util.ws.IClusterWsMessage;
@@ -45,11 +46,6 @@ public interface IApplication {
 	String getOmString(String key);
 	String getOmString(String key, long languageId);
 	String getOmString(String key, final Locale loc, String... params);
-	Client getOmClient(String uid);
-	Client getOmClientBySid(String sid);
-	Client getOmOnlineClient(String uid);
-	List<Client> getOmRoomClients(Long roomId);
-	List<Client> getOmClients(Long userId);
 	String getOmContactsLink();
 	String getOmInvitationLink(Invitation i);
 	String urlForActivatePage(PageParameters pp);
@@ -58,11 +54,19 @@ public interface IApplication {
 	void setXFrameOptions(String xFrameOptions);
 	void setContentSecurityPolicy(String contentSecurityPolicy);
 
+	IClient update(IClient c);
+
+	// web client
+	Client getOmClient(String uid);
+	Client getOmClientBySid(String sid);
+	Client getOmOnlineClient(String uid);
+	List<Client> getOmRoomClients(Long roomId);
+	List<Client> getOmClients(Long userId);
+
 	// stream client
 	StreamClient updateClient(StreamClient rcl, boolean forceSize);
 	String getServerId();
 	Map<String, StreamClient> getStreamClients();
-	StreamClient update(StreamClient c);
 	Set<Long> getActiveRoomIds();
 
 	//JPA

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/ISessionManager.java
----------------------------------------------------------------------
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/ISessionManager.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/ISessionManager.java
index 7b9b361..47fa60d 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/ISessionManager.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/ISessionManager.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.openmeetings.db.entity.basic.IClient;
 import org.apache.openmeetings.db.entity.room.StreamClient;
 
 /**
@@ -55,7 +56,7 @@ public interface ISessionManager {
 	 * @param rcm
 	 * @return updated client
 	 */
-	StreamClient update(StreamClient rcm);
+	void update(IClient rcm);
 
 	/**
 	 * Remove a client from the session store
@@ -75,16 +76,6 @@ public interface ISessionManager {
 	 */
 	List<StreamClient> listByRoom(Long roomId);
 
-	Collection<StreamClient> listByRoomAll(Long roomId);
-
-	/**
-	 * get the current Moderator in this room
-	 *
-	 * @param roomId
-	 * @return
-	 */
-	List<StreamClient> listModeratorByRoom(Long roomId);
-
 	/**
 	 * returns number of current users recording
 	 *

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/Client.java
----------------------------------------------------------------------
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/Client.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/Client.java
index 98d9251..8d4235a 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/Client.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/Client.java
@@ -66,6 +66,8 @@ public class Client implements IClient {
 		private final String uid;
 		private final String broadcastId;
 		private final Type type;
+		private Integer width;
+		private Integer height;
 
 		public Stream(String uid, String streamId, String broadcastId, Type type) {
 			this.streamId = streamId;
@@ -90,6 +92,22 @@ public class Client implements IClient {
 			return uid;
 		}
 
+		public Integer getWidth() {
+			return width;
+		}
+
+		public void setWidth(Integer width) {
+			this.width = width;
+		}
+
+		public Integer getHeight() {
+			return height;
+		}
+
+		public void setHeight(Integer height) {
+			this.height = height;
+		}
+
 		@Override
 		public int hashCode() {
 			final int prime = 31;
@@ -100,18 +118,23 @@ public class Client implements IClient {
 
 		@Override
 		public boolean equals(Object obj) {
-			if (this == obj)
+			if (this == obj) {
 				return true;
-			if (obj == null)
+			}
+			if (obj == null) {
 				return false;
-			if (getClass() != obj.getClass())
+			}
+			if (getClass() != obj.getClass()) {
 				return false;
+			}
 			Stream other = (Stream) obj;
 			if (broadcastId == null) {
-				if (other.broadcastId != null)
+				if (other.broadcastId != null) {
 					return false;
-			} else if (!broadcastId.equals(other.broadcastId))
+				}
+			} else if (!broadcastId.equals(other.broadcastId)) {
 				return false;
+			}
 			return true;
 		}
 
@@ -137,6 +160,7 @@ public class Client implements IClient {
 	private int width = 0;
 	private int height = 0;
 	private String serverId = null;
+	private Long recordingId;
 
 	public Client(String sessionId, int pageId, Long userId, UserDao dao) {
 		this.sessionId = sessionId;
@@ -153,8 +177,8 @@ public class Client implements IClient {
 		this.user = user;
 		this.connectedSince = new Date();
 		uid = rcl.getUid();
-		sid = rcl.getOwnerSid();
-		this.remoteAddress = rcl.getUserip();
+		sid = rcl.getSid();
+		this.remoteAddress = rcl.getRemoteAddress();
 	}
 
 	public String getSessionId() {
@@ -183,15 +207,22 @@ public class Client implements IClient {
 		return this;
 	}
 
+	@Override
 	public Long getUserId() {
 		return user.getId();
 	}
 
 	@Override
+	public String getLogin() {
+		return user.getLogin();
+	}
+
+	@Override
 	public String getUid() {
 		return uid;
 	}
 
+	@Override
 	public String getSid() {
 		return sid;
 	}
@@ -370,6 +401,7 @@ public class Client implements IClient {
 		return this;
 	}
 
+	@Override
 	public int getWidth() {
 		return width;
 	}
@@ -379,6 +411,7 @@ public class Client implements IClient {
 		return this;
 	}
 
+	@Override
 	public int getHeight() {
 		return height;
 	}
@@ -388,6 +421,7 @@ public class Client implements IClient {
 		return this;
 	}
 
+	@Override
 	public String getRemoteAddress() {
 		return remoteAddress;
 	}
@@ -407,33 +441,27 @@ public class Client implements IClient {
 	}
 
 	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((uid == null) ? 0 : uid.hashCode());
-		return result;
+	public void setRecordingStarted(boolean recordingStarted) {
+		if (recordingStarted) {
+			activities.add(Activity.record);
+		} else {
+			activities.remove(Activity.record);
+		}
 	}
 
 	@Override
-	public boolean equals(Object obj) {
-		if (this == obj) {
-			return true;
-		}
-		if (obj == null) {
-			return false;
-		}
-		if (!(obj instanceof Client)) {
-			return false;
-		}
-		Client other = (Client) obj;
-		if (uid == null) {
-			if (other.uid != null) {
-				return false;
-			}
-		} else if (!uid.equals(other.uid)) {
-			return false;
-		}
-		return true;
+	public Long getRecordingId() {
+		return recordingId;
+	}
+
+	@Override
+	public void setRecordingId(Long recordingId) {
+		this.recordingId = recordingId;
+	}
+
+	@Override
+	public Long getRoomId() {
+		return room == null ? null : room.getId();
 	}
 
 	public JSONObject toJson(boolean self) {
@@ -475,6 +503,36 @@ public class Client implements IClient {
 	}
 
 	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((uid == null) ? 0 : uid.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (!(obj instanceof Client)) {
+			return false;
+		}
+		Client other = (Client) obj;
+		if (uid == null) {
+			if (other.uid != null) {
+				return false;
+			}
+		} else if (!uid.equals(other.uid)) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
 	public String toString() {
 		return "Client [uid=" + uid + ", sessionId=" + sessionId + ", pageId=" + pageId + ", userId=" + user.getId() + ", room=" + (room == null ? null : room.getId())
 				+ ", rights=" + rights + ", activities=" + activities + ", connectedSince=" + connectedSince + ", pod = " + pod + "]";

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/IClient.java
----------------------------------------------------------------------
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/IClient.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/IClient.java
index 0ec52cf..f9817a5 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/IClient.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/basic/IClient.java
@@ -36,5 +36,15 @@ public interface IClient extends IDataProviderEntity {
 		, sharing
 	}
 	String getUid();
+	String getSid();
+	Long getUserId();
+	String getLogin();
+	String getRemoteAddress();
+	Long getRoomId();
+	int getWidth();
+	int getHeight();
+	void setRecordingStarted(boolean recordingStarted);
+	Long getRecordingId();
+	void setRecordingId(Long recordingId);
 	String getServerId();
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/StreamClient.java
----------------------------------------------------------------------
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/StreamClient.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/StreamClient.java
index 1b6de62..95a1cc7 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/StreamClient.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/StreamClient.java
@@ -35,17 +35,17 @@ public class StreamClient implements IClient {
 	private int width = 0;
 	private int height = 0;
 	private String uid = null;
-	private String ownerSid = null;
+	private String sid = null;
 	private boolean mod = false;
 	private boolean superMod = false;
 	private boolean canGiveAudio = false;
 	private boolean canVideo = false;
 	private Date connectedSince;
-	private String userip;
+	private String remoteAddress;
 	private int userport;
 	private Date roomEnter = null;
 	private String broadCastId = null;
-	private String username = "";
+	private String login = "";
 	private Long userId = null;
 	private String firstname = "";
 	private String lastname = "";
@@ -96,6 +96,7 @@ public class StreamClient implements IClient {
 		}
 	}
 
+	@Override
 	public int getWidth() {
 		return width;
 	}
@@ -104,6 +105,7 @@ public class StreamClient implements IClient {
 		this.width = width;
 	}
 
+	@Override
 	public int getHeight() {
 		return height;
 	}
@@ -121,12 +123,13 @@ public class StreamClient implements IClient {
 		this.uid = uid;
 	}
 
-	public String getOwnerSid() {
-		return ownerSid;
+	@Override
+	public String getSid() {
+		return sid;
 	}
 
-	public void setOwnerSid(String ownerSid) {
-		this.ownerSid = ownerSid;
+	public void setSid(String sid) {
+		this.sid = sid;
 	}
 
 	public boolean isMod() {
@@ -169,12 +172,13 @@ public class StreamClient implements IClient {
 		this.connectedSince = connectedSince;
 	}
 
-	public String getUserip() {
-		return userip;
+	@Override
+	public String getRemoteAddress() {
+		return remoteAddress;
 	}
 
-	public void setUserip(String userip) {
-		this.userip = userip;
+	public void setRemoteAddress(String remoteAddress) {
+		this.remoteAddress = remoteAddress;
 	}
 
 	public int getUserport() {
@@ -201,14 +205,16 @@ public class StreamClient implements IClient {
 		this.broadCastId = broadCastId;
 	}
 
-	public String getUsername() {
-		return username;
+	@Override
+	public String getLogin() {
+		return login;
 	}
 
-	public void setUsername(String username) {
-		this.username = username;
+	public void setLogin(String login) {
+		this.login = login;
 	}
 
+	@Override
 	public Long getUserId() {
 		return userId;
 	}
@@ -301,6 +307,7 @@ public class StreamClient implements IClient {
 		return recordingStarted;
 	}
 
+	@Override
 	public void setRecordingStarted(boolean recordingStarted) {
 		this.recordingStarted = recordingStarted;
 	}
@@ -329,10 +336,12 @@ public class StreamClient implements IClient {
 		this.broadcasting = isBroadcasting;
 	}
 
+	@Override
 	public Long getRecordingId() {
 		return recordingId;
 	}
 
+	@Override
 	public void setRecordingId(Long recordingId) {
 		this.recordingId = recordingId;
 	}
@@ -394,6 +403,7 @@ public class StreamClient implements IClient {
 		this.serverId = serverId;
 	}
 
+	@Override
 	public Long getRoomId() {
 		return roomId;
 	}
@@ -408,8 +418,8 @@ public class StreamClient implements IClient {
 
 	@Override
 	public String toString() {
-		return "StreamClient [scope=" + scope + ", uid=" + uid + ", ownerSid=" + ownerSid + ", broadCastId="
-				+ broadCastId + ", username=" + username + ", userId=" + userId + ", avsettings=" + avsettings + ", type=" + type
+		return "StreamClient [scope=" + scope + ", uid=" + uid + ", ownerSid=" + sid + ", broadCastId="
+				+ broadCastId + ", login=" + login + ", userId=" + userId + ", avsettings=" + avsettings + ", type=" + type
 				+ ", isBroadcasting=" + broadcasting + "]";
 	}
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
index 5bbf401..0c3f904 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/connection/ConnectionsPanel.java
@@ -104,7 +104,7 @@ public class ConnectionsPanel extends AdminPanel {
 				if (_c instanceof StreamClient) {
 					StreamClient c = (StreamClient)_c;
 					item.add(new Label("type", "flash"));
-					item.add(new Label("login", c.getUsername()));
+					item.add(new Label("login", c.getLogin()));
 					item.add(new Label("since", c.getConnectedSince()));
 					item.add(new Label("scope"));
 					confirm.setEnabled(Client.Type.sharing != c.getType());

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
index 65f5f81..19f7002 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomForm.java
@@ -89,7 +89,7 @@ public class RoomForm extends AdminBaseForm<Room> {
 		protected void populateItem(final ListItem<StreamClient> item) {
 			StreamClient client = item.getModelObject();
 			item.add(new Label("clientId", "" + client.getId()))
-				.add(new Label("clientLogin", "" + client.getUsername()))
+				.add(new Label("clientLogin", "" + client.getLogin()))
 				.add(new ConfirmableAjaxBorder("clientDelete", getString("80"), getString("833")) {
 					private static final long serialVersionUID = 1L;
 

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
index 445932a..71eac66 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
@@ -60,6 +60,7 @@ import org.apache.openmeetings.db.dto.room.Whiteboards;
 import org.apache.openmeetings.db.entity.basic.Client;
 import org.apache.openmeetings.db.entity.basic.Client.Activity;
 import org.apache.openmeetings.db.entity.basic.Client.Pod;
+import org.apache.openmeetings.db.entity.basic.IClient;
 import org.apache.openmeetings.db.entity.log.ConferenceLog;
 import org.apache.openmeetings.db.entity.record.Recording;
 import org.apache.openmeetings.db.entity.room.Invitation;
@@ -367,8 +368,12 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 	}
 
 	@Override
-	public StreamClient update(StreamClient c) {
-		hazelcast.getMap(STREAM_CLIENT_KEY).put(c.getUid(), c);
+	public IClient update(IClient c) {
+		if (c instanceof StreamClient) {
+			hazelcast.getMap(STREAM_CLIENT_KEY).put(c.getUid(), c);
+		} else {
+			update((Client)c);
+		}
 		return c;
 	}
 
@@ -423,10 +428,10 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 		if (rcl == null) {
 			return null;
 		}
-		Client client = getClientBySid(rcl.getOwnerSid());
+		Client client = getClientBySid(rcl.getSid());
 		if (client == null) {
 			if (Client.Type.mobile == rcl.getType()) {
-				Sessiondata sd = getBean(SessiondataDao.class).check(rcl.getOwnerSid());
+				Sessiondata sd = getBean(SessiondataDao.class).check(rcl.getSid());
 				UserDao udao = getBean(UserDao.class);
 				User u = udao.get(sd.getUserId());
 				rcl = getBean(MobileService.class).create(rcl, u);
@@ -443,7 +448,7 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 				}
 				//FIXME TODO rights
 			} else if (client == null && Client.Type.sip == rcl.getType()) {
-				rcl.setUsername(SIP_USER_NAME);
+				rcl.setLogin(SIP_USER_NAME);
 				rcl.setUserId(SIP_USER_ID);
 				//SipTransport enters the room
 				User u = new User();
@@ -469,7 +474,7 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 		}
 		User u = client.getUser();
 		rcl.setUserId(u.getId());
-		rcl.setUsername(u.getLogin());
+		rcl.setLogin(u.getLogin());
 		rcl.setFirstname(u.getFirstname());
 		rcl.setLastname(u.getLastname());
 		rcl.setEmail(u.getAddress() == null ? null : u.getAddress().getEmail());
@@ -635,6 +640,7 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 	public static List<Client> getRoomClients(Long roomId) {
 		return getRoomClients(roomId, null);
 	}
+
 	public static List<Client> getRoomClients(Long roomId, Predicate<Client> filter) {
 		List<Client> clients = new ArrayList<>();
 		if (roomId != null) {

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
index 6c5b450..f29e7d1 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
@@ -119,6 +119,7 @@ public class RoomPanel extends BasePanel {
 		, exclusive
 	}
 	private final Room r;
+	private final boolean isInterview;
 	private final WebMarkupContainer room = new WebMarkupContainer("roomContainer");
 	private final AbstractDefaultAjaxBehavior roomEnter = new AbstractDefaultAjaxBehavior() {
 		private static final long serialVersionUID = 1L;
@@ -133,7 +134,7 @@ public class RoomPanel extends BasePanel {
 					, cp.getRemoteAddress()
 					, "" + r.getId());
 			JSONObject options = VideoSettings.getInitJson(cp, "" + r.getId(), getClient().getSid());
-			options.put("interview", Room.Type.interview == r.getType());
+			options.put("interview", isInterview);
 			options.put("showMicStatus", !r.getHiddenElements().contains(RoomElement.MicrophoneStatus));
 			target.appendJavaScript(String.format("VideoManager.init(%s);", options));
 			WebSocketHelper.sendRoom(new RoomMessage(r.getId(), getUserId(), RoomMessage.Type.roomEnter));
@@ -203,20 +204,25 @@ public class RoomPanel extends BasePanel {
 	public RoomPanel(String id, Room r) {
 		super(id);
 		this.r = r;
-		this.wb = Room.Type.interview == r.getType()
-				? new InterviewWbPanel("whiteboard", this)
-				: new WbPanel("whiteboard", this);
+		this.isInterview = Room.Type.interview == r.getType();
+		this.wb = isInterview ? new InterviewWbPanel("whiteboard", this) : new WbPanel("whiteboard", this);
 	}
 
 	private void initVideos(AjaxRequestTarget target) {
 		StringBuilder sb = new StringBuilder();
+		boolean hasStreams = false;
+		Client _c = getClient();
 		for (Client c: getRoomClients(getRoom().getId()) ) {
-			boolean self = getClient().getUid().equals(c.getUid());
+			boolean self = _c.getUid().equals(c.getUid());
 			for (Client.Stream s : c.getStreams()) {
 				JSONObject jo = videoJson(c, self, c.getSid(), getBean(ISessionManager.class), s.getUid());
 				sb.append(String.format("VideoManager.play(%s);", jo));
+				hasStreams = true;
 			}
 		}
+		if (isInterview && recordingUser == null && hasStreams && _c.hasRight(Right.moderator)) {
+			sb.append("WbArea.setRecStartEnabled(true);");
+		}
 		if (!Strings.isEmpty(sb)) {
 			target.appendJavaScript(sb);
 		}
@@ -232,7 +238,7 @@ public class RoomPanel extends BasePanel {
 
 		room.add(menu = new RoomMenuPanel("menu", this));
 		room.add(AttributeModifier.append("data-room-id", r.getId()));
-		if (Room.Type.interview == r.getType()) {
+		if (isInterview) {
 			room.add(new WebMarkupContainer("wb-area").add(wb));
 		} else {
 			Droppable<FileItem> wbArea = new Droppable<FileItem>("wb-area") {
@@ -487,6 +493,9 @@ public class RoomPanel extends BasePanel {
 						if (_c.getSid().equals(c.getSid())) {
 							update(c.addStream(uid, streamId, broadcastId, type));
 						}
+						if (isInterview && recordingUser == null && _c.hasRight(Right.moderator)) {
+							handler.appendJavaScript("WbArea.setRecStartEnabled(true);");
+						}
 					}
 						break;
 					case closeStream:
@@ -497,10 +506,21 @@ public class RoomPanel extends BasePanel {
 							log.error("Not existing user in closeStream {} !!!!", obj);
 							return;
 						}
-						if (getClient().getUid().equals(c.getUid())) {
+						Client _c = getClient();
+						if (_c.getUid().equals(c.getUid())) {
 							update(c.removeStream(obj.optString("broadcastId")));
 						}
 						handler.appendJavaScript(String.format("VideoManager.close('%s');", obj.getString("uid")));
+						if (isInterview && recordingUser == null && _c.hasRight(Right.moderator)) {
+							boolean hasStreams = false;
+							for (Client cl : getRoomClients(r.getId())) {
+								if (!cl.getStreams().isEmpty()) {
+									hasStreams = true;
+									break;
+								}
+							}
+							handler.appendJavaScript(String.format("WbArea.setRecStartEnabled(%s);", hasStreams));
+						}
 					}
 						break;
 					case roomEnter:
@@ -660,7 +680,7 @@ public class RoomPanel extends BasePanel {
 		super.renderHead(response);
 		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(RoomPanel.class, "jquery.dialogextend.js"))));
 		response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(RoomPanel.class, "room.js"))));
-		if (Room.Type.interview == r.getType()) {
+		if (isInterview) {
 			response.render(JavaScriptHeaderItem.forReference(INTERVIEWWB_JS_REFERENCE));
 		} else {
 			response.render(JavaScriptHeaderItem.forReference(WB_JS_REFERENCE));
@@ -769,7 +789,7 @@ public class RoomPanel extends BasePanel {
 
 	public boolean screenShareAllowed() {
 		Room r = getRoom();
-		return Room.Type.interview != r.getType() && !r.isHidden(RoomElement.ScreenSharing)
+		return !isInterview && !r.isHidden(RoomElement.ScreenSharing)
 				&& r.isAllowRecording() && getClient().hasRight(Right.share)
 				&& sharingUser == null;
 	}

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/AbstractWbPanel.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/AbstractWbPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/AbstractWbPanel.java
index c25eaa4..bd0aba7 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/AbstractWbPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/AbstractWbPanel.java
@@ -18,17 +18,34 @@
  */
 package org.apache.openmeetings.web.room.wb;
 
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+import static org.apache.openmeetings.web.room.wb.WbWebSocketHelper.PARAM_OBJ;
+import static org.apache.openmeetings.web.util.CallbackFunctionHelper.getNamedFunction;
+import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
+
+import java.io.IOException;
+
 import org.apache.openmeetings.db.entity.file.FileItem;
 import org.apache.openmeetings.web.room.RoomPanel;
 import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
 import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.util.string.StringValue;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import com.github.openjson.JSONObject;
 
 public abstract class AbstractWbPanel extends Panel {
 	private static final long serialVersionUID = 1L;
+	private static final Logger log = Red5LoggerFactory.getLogger(AbstractWbPanel.class, webAppRootKey);
+	public static final String FUNC_ACTION = "wbAction";
+	public static final String PARAM_ACTION = "action";
 	protected static final String ROLE_NONE = "none";
 	protected final RoomPanel rp;
 	protected boolean inited = false;
@@ -43,12 +60,38 @@ public abstract class AbstractWbPanel extends Panel {
 			inited = true;
 		}
 	};
+	private final AbstractDefaultAjaxBehavior wbAction = new AbstractDefaultAjaxBehavior() {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
+			updateWbActionAttributes(attributes);
+		}
+
+		@Override
+		protected void respond(AjaxRequestTarget target) {
+			if (!inited) {
+				return;
+			}
+			if (!inited) {
+				return;
+			}
+			try {
+				WbAction a = WbAction.valueOf(getRequest().getRequestParameters().getParameterValue(PARAM_ACTION).toString());
+				StringValue sv = getRequest().getRequestParameters().getParameterValue(PARAM_OBJ);
+				JSONObject obj = sv.isEmpty() ? new JSONObject() : new JSONObject(sv.toString());
+				processWbAction(a, obj, target);
+			} catch (Exception e) {
+				log.error("Unexpected error while processing wbAction", e);
+			}
+		}
+	};
 
 	public AbstractWbPanel(String id, RoomPanel rp) {
 		super(id);
 		this.rp = rp;
 		setOutputMarkupId(true);
-		add(wbLoad);
+		add(wbLoad, wbAction);
 	}
 
 	public AbstractWbPanel update(IPartialPageRequestHandler handler) {
@@ -60,15 +103,34 @@ public abstract class AbstractWbPanel extends Panel {
 
 	protected abstract String getRole();
 
-	void internalWbLoad(StringBuilder sb) {
-	}
+	/**
+	 * Internal method to perform JS actions on WB load
+	 *
+	 * @param sb - {@link StringBuilder} to put JS calls
+	 */
+	void internalWbLoad(StringBuilder sb) {}
 
-	public void sendFileToWb(final FileItem fi, boolean clean) {
-	}
+	/**
+	 * This method being called when file is dropped to WB
+	 *
+	 * @param fi - File being dropped
+	 * @param clean - should WB be cleaned up
+	 */
+	public void sendFileToWb(final FileItem fi, boolean clean) {}
+
+	/**
+	 * This method allows to set additional attributes to wbAction
+	 *
+	 * @param attributes - attributes to set
+	 */
+	protected void updateWbActionAttributes(AjaxRequestAttributes attributes) {}
+
+	protected abstract void processWbAction(WbAction a, JSONObject obj, AjaxRequestTarget target) throws IOException;
 
 	@Override
 	public void renderHead(IHeaderResponse response) {
 		super.renderHead(response);
 		response.render(OnDomReadyHeaderItem.forScript(wbLoad.getCallbackScript()));
+		response.render(new PriorityHeaderItem(getNamedFunction(FUNC_ACTION, wbAction, explicit(PARAM_ACTION), explicit(PARAM_OBJ))));
 	}
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/InterviewWbPanel.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/InterviewWbPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/InterviewWbPanel.java
index cf6cd5c..a24fa41 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/InterviewWbPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/InterviewWbPanel.java
@@ -18,12 +18,23 @@
  */
 package org.apache.openmeetings.web.room.wb;
 
+import static org.apache.openmeetings.web.app.Application.getBean;
+
+import java.io.IOException;
+
+import org.apache.openmeetings.core.remote.ScopeApplicationAdapter;
+import org.apache.openmeetings.db.dao.server.ISessionManager;
+import org.apache.openmeetings.db.entity.basic.Client;
 import org.apache.openmeetings.db.entity.file.FileItem;
+import org.apache.openmeetings.db.entity.room.Room;
 import org.apache.openmeetings.db.entity.room.Room.Right;
 import org.apache.openmeetings.web.room.RoomPanel;
+import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.apache.wicket.request.resource.ResourceReference;
 
+import com.github.openjson.JSONObject;
+
 public class InterviewWbPanel extends AbstractWbPanel {
 	private static final long serialVersionUID = 1L;
 	public final static ResourceReference INTERVIEWWB_JS_REFERENCE = new JavaScriptResourceReference(WbPanel.class, "interviewwb.js");
@@ -38,6 +49,21 @@ public class InterviewWbPanel extends AbstractWbPanel {
 	}
 
 	@Override
-	public void sendFileToWb(final FileItem fi, boolean clean) {
+	public void sendFileToWb(final FileItem fi, boolean clean) {}
+
+	@Override
+	protected void processWbAction(WbAction a, JSONObject obj, AjaxRequestTarget target) throws IOException {
+		Client c = rp.getClient();
+		if (c.hasRight(Room.Right.moderator)) {
+			switch (a) {
+				case startRecording:
+					if (getBean(ISessionManager.class).getRecordingCount(c.getRoomId()) < 1) {
+						getBean(ScopeApplicationAdapter.class).startInterviewRecording(c);
+					}
+					break;
+				default:
+					//no-op
+			}
+		}
 	}
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbAction.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbAction.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbAction.java
index 5ef5bfd..09e238c 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbAction.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbAction.java
@@ -33,4 +33,6 @@ public enum WbAction {
 	, undo
 	, setSize
 	, downloadPdf
+	, startRecording
+	, stopRecording
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5fd782fc/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java
index 41eb7e4..17ac2ac 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java
@@ -21,12 +21,9 @@ package org.apache.openmeetings.web.room.wb;
 import static org.apache.openmeetings.db.dto.room.Whiteboard.ITEMS_KEY;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
 import static org.apache.openmeetings.web.app.Application.getBean;
-import static org.apache.openmeetings.web.room.wb.WbWebSocketHelper.PARAM_OBJ;
 import static org.apache.openmeetings.web.room.wb.WbWebSocketHelper.getObjWbJson;
 import static org.apache.openmeetings.web.room.wb.WbWebSocketHelper.getWbJson;
-import static org.apache.openmeetings.web.util.CallbackFunctionHelper.getNamedFunction;
 import static org.apache.wicket.AttributeModifier.append;
-import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
 
 import java.awt.image.BufferedImage;
 import java.io.BufferedReader;
@@ -74,19 +71,16 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
 import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
-import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method;
 import org.apache.wicket.behavior.AttributeAppender;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
-import org.apache.wicket.markup.head.PriorityHeaderItem;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.apache.wicket.request.resource.ResourceReference;
-import org.apache.wicket.util.string.StringValue;
 import org.red5.logging.Red5LoggerFactory;
 import org.slf4j.Logger;
 
@@ -102,233 +96,11 @@ public class WbPanel extends AbstractWbPanel {
 	private static final int DEFAULT_WIDTH = 640;
 	private static final int DEFAULT_HEIGHT = 480;
 	private static final int UNDO_SIZE = 20;
-	public static final String FUNC_ACTION = "wbAction";
-	public static final String PARAM_ACTION = "action";
 	public final static ResourceReference WB_JS_REFERENCE = new JavaScriptResourceReference(WbPanel.class, "wb.js");
 	private final static ResourceReference FABRIC_JS_REFERENCE = new JavaScriptResourceReference(WbPanel.class, "fabric.js");
 	private final Long roomId;
 	private long wb2save = -1;
 	private final Map<Long, Deque<UndoObject>> undoList = new HashMap<>();
-	private final AbstractDefaultAjaxBehavior wbAction = new AbstractDefaultAjaxBehavior() {
-		private static final long serialVersionUID = 1L;
-
-		@Override
-		protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
-			attributes.setMethod(Method.POST);
-		}
-
-		@Override
-		protected void respond(AjaxRequestTarget target) {
-			if (!inited) {
-				return;
-			}
-			try {
-				WbAction a = WbAction.valueOf(getRequest().getRequestParameters().getParameterValue(PARAM_ACTION).toString());
-				StringValue sv = getRequest().getRequestParameters().getParameterValue(PARAM_OBJ);
-				JSONObject obj = sv.isEmpty() ? new JSONObject() : new JSONObject(sv.toString());
-				if (WbAction.createObj == a || WbAction.modifyObj == a) {
-					JSONObject o = obj.optJSONObject("obj");
-					if (o != null && "pointer".equals(o.getString("type"))) {
-						sendWbOthers(a, obj);
-						return;
-					}
-				}
-
-				Client c = rp.getClient();
-				if (WbAction.downloadPdf == a) {
-					boolean moder = c.hasRight(Room.Right.moderator);
-					Room r = rp.getRoom();
-					if ((moder && !r.isHidden(RoomElement.ActionMenu)) || (!moder && r.isAllowUserQuestions())) {
-						try (PDDocument doc = new PDDocument()) {
-							JSONArray arr = obj.getJSONArray("slides");
-							for (int i = 0; i < arr.length(); ++i) {
-								String base64Image = arr.getString(i).split(",")[1];
-								byte[] bb = Base64.decodeBase64(base64Image);
-								BufferedImage img = ImageIO.read(new ByteArrayInputStream(bb));
-								float width = img.getWidth();
-								float height = img.getHeight();
-								PDPage page = new PDPage(new PDRectangle(width, height));
-								PDImageXObject pdImageXObject = LosslessFactory.createFromImage(doc, img);
-								try (PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.APPEND, false)) {
-									contentStream.drawImage(pdImageXObject, 0, 0, width, height);
-								}
-								doc.addPage(page);
-							}
-							ByteArrayOutputStream baos = new ByteArrayOutputStream();
-							doc.save(baos);
-							rp.startDownload(target, baos.toByteArray());
-						}
-					}
-					return;
-				}
-				//presenter-right
-				if (c.hasRight(Right.presenter)) {
-					switch (a) {
-						case createWb:
-						{
-							Whiteboard wb = WhiteboardCache.add(roomId, c.getUser().getLanguageId());
-							sendWbAll(WbAction.createWb, getAddWbJson(wb));
-						}
-							break;
-						case removeWb:
-						{
-							long _id = obj.optLong("wbId", -1);
-							Long id = _id < 0 ? null : _id;
-							WhiteboardCache.remove(roomId, id);
-							sendWbAll(WbAction.removeWb, obj);
-						}
-							break;
-						case activateWb:
-						{
-							long _id = obj.optLong("wbId", -1);
-							if (_id > -1) {
-								WhiteboardCache.activate(roomId, _id);
-								sendWbAll(WbAction.activateWb, obj);
-							}
-						}
-							break;
-						case setSlide:
-						{
-							Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
-							wb.setSlide(obj.optInt("slide", 0));
-							WhiteboardCache.update(roomId, wb);
-							sendWbOthers(WbAction.setSlide, obj);
-						}
-							break;
-						case clearAll:
-						{
-							clearAll(roomId, obj.getLong("wbId"));
-						}
-							break;
-						case setSize:
-						{
-							Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
-							wb.setZoom(obj.getDouble("zoom"));
-							wb.setZoomMode(ZoomMode.valueOf(obj.getString("zoomMode")));
-							WhiteboardCache.update(roomId, wb);
-							sendWbOthers(WbAction.setSize, getAddWbJson(wb));
-							//TODO scroll????
-						}
-							break;
-						default:
-							break;
-					}
-				}
-				//wb-right
-				if (c.hasRight(Right.presenter) || c.hasRight(Right.whiteBoard)) {
-					switch (a) {
-						case createObj:
-						{
-							Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
-							JSONObject o = obj.getJSONObject("obj");
-							wb.put(o.getString("uid"), o);
-							WhiteboardCache.update(roomId, wb);
-							addUndo(wb.getId(), new UndoObject(UndoObject.Type.add, o));
-							sendWbOthers(WbAction.createObj, obj);
-						}
-							break;
-						case modifyObj:
-						{
-							Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
-							JSONArray arr = obj.getJSONArray("obj");
-							JSONArray undo = new JSONArray();
-							for (int i = 0; i < arr.length(); ++i) {
-								JSONObject _o = arr.getJSONObject(i);
-								String uid = _o.getString("uid");
-								undo.put(wb.get(uid));
-								wb.put(uid, _o);
-							}
-							if (arr.length() != 0) {
-								WhiteboardCache.update(roomId, wb);
-								addUndo(wb.getId(), new UndoObject(UndoObject.Type.modify, undo));
-							}
-							sendWbOthers(WbAction.modifyObj, obj);
-						}
-							break;
-						case deleteObj:
-						{
-							Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
-							JSONArray arr = obj.getJSONArray("obj");
-							JSONArray undo = new JSONArray();
-							for (int i = 0; i < arr.length(); ++i) {
-								JSONObject _o = arr.getJSONObject(i);
-								JSONObject u = wb.remove(_o.getString("uid"));
-								if (u != null) {
-									undo.put(u);
-								}
-							}
-							if (undo.length() != 0) {
-								WhiteboardCache.update(roomId, wb);
-								addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, undo));
-							}
-							sendWbAll(WbAction.deleteObj, obj);
-						}
-							break;
-						case clearSlide:
-						{
-							Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
-							JSONArray arr = wb.clearSlide(obj.getInt("slide"));
-							if (arr.length() != 0) {
-								WhiteboardCache.update(roomId, wb);
-								addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, arr));
-							}
-							sendWbAll(WbAction.clearSlide, obj);
-						}
-							break;
-						case save:
-							wb2save = obj.getLong("wbId");
-							fileName.open(target);
-							break;
-						case undo:
-						{
-							Long wbId = obj.getLong("wbId");
-							UndoObject uo = getUndo(wbId);
-							if (uo != null) {
-								Whiteboard wb = WhiteboardCache.get(roomId).get(wbId);
-								switch (uo.getType()) {
-									case add:
-									{
-										JSONObject o = new JSONObject(uo.getObject());
-										wb.remove(o.getString("uid"));
-										WhiteboardCache.update(roomId, wb);
-										sendWbAll(WbAction.deleteObj, obj.put("obj", new JSONArray().put(o)));
-									}
-										break;
-									case remove:
-									{
-										JSONArray arr = new JSONArray(uo.getObject());
-										for (int i  = 0; i < arr.length(); ++i) {
-											JSONObject o = arr.getJSONObject(i);
-											wb.put(o.getString("uid"), o);
-										}
-										WhiteboardCache.update(roomId, wb);
-										sendWbAll(WbAction.createObj, obj.put("obj", new JSONArray(uo.getObject())));
-									}
-										break;
-									case modify:
-									{
-										JSONArray arr = new JSONArray(uo.getObject());
-										for (int i  = 0; i < arr.length(); ++i) {
-											JSONObject o = arr.getJSONObject(i);
-											wb.put(o.getString("uid"), o);
-										}
-										WhiteboardCache.update(roomId, wb);
-										sendWbAll(WbAction.modifyObj, obj.put("obj", arr));
-									}
-										break;
-								}
-							}
-						}
-							break;
-						default:
-							break;
-					}
-				}
-			} catch (Exception e) {
-				log.error("Unexpected error while processing wbAction", e);
-			}
-		}
-	};
 	private final NameDialog fileName = new NameDialog("filename") {
 		private static final long serialVersionUID = 1L;
 
@@ -382,7 +154,6 @@ public class WbPanel extends AbstractWbPanel {
 							, new AttributeAppender("data-image", item.getModelObject()).setSeparator(""));
 				}
 			}, fileName);
-			add(wbAction);
 		}
 	}
 
@@ -390,7 +161,6 @@ public class WbPanel extends AbstractWbPanel {
 	public void renderHead(IHeaderResponse response) {
 		super.renderHead(response);
 		response.render(JavaScriptHeaderItem.forReference(FABRIC_JS_REFERENCE));
-		response.render(new PriorityHeaderItem(getNamedFunction(FUNC_ACTION, wbAction, explicit(PARAM_ACTION), explicit(PARAM_OBJ))));
 	}
 
 	@Override
@@ -413,6 +183,213 @@ public class WbPanel extends AbstractWbPanel {
 		}
 	}
 
+	@Override
+	protected void updateWbActionAttributes(AjaxRequestAttributes attributes) {
+		attributes.setMethod(Method.POST);
+	}
+
+	@Override
+	protected void processWbAction(WbAction a, JSONObject obj, AjaxRequestTarget target) throws IOException {
+		if (WbAction.createObj == a || WbAction.modifyObj == a) {
+			JSONObject o = obj.optJSONObject("obj");
+			if (o != null && "pointer".equals(o.getString("type"))) {
+				sendWbOthers(a, obj);
+				return;
+			}
+		}
+
+		Client c = rp.getClient();
+		if (WbAction.downloadPdf == a) {
+			boolean moder = c.hasRight(Room.Right.moderator);
+			Room r = rp.getRoom();
+			if ((moder && !r.isHidden(RoomElement.ActionMenu)) || (!moder && r.isAllowUserQuestions())) {
+				try (PDDocument doc = new PDDocument()) {
+					JSONArray arr = obj.getJSONArray("slides");
+					for (int i = 0; i < arr.length(); ++i) {
+						String base64Image = arr.getString(i).split(",")[1];
+						byte[] bb = Base64.decodeBase64(base64Image);
+						BufferedImage img = ImageIO.read(new ByteArrayInputStream(bb));
+						float width = img.getWidth();
+						float height = img.getHeight();
+						PDPage page = new PDPage(new PDRectangle(width, height));
+						PDImageXObject pdImageXObject = LosslessFactory.createFromImage(doc, img);
+						try (PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.APPEND, false)) {
+							contentStream.drawImage(pdImageXObject, 0, 0, width, height);
+						}
+						doc.addPage(page);
+					}
+					ByteArrayOutputStream baos = new ByteArrayOutputStream();
+					doc.save(baos);
+					rp.startDownload(target, baos.toByteArray());
+				}
+			}
+			return;
+		}
+		//presenter-right
+		if (c.hasRight(Right.presenter)) {
+			switch (a) {
+				case createWb:
+				{
+					Whiteboard wb = WhiteboardCache.add(roomId, c.getUser().getLanguageId());
+					sendWbAll(WbAction.createWb, getAddWbJson(wb));
+				}
+					break;
+				case removeWb:
+				{
+					long _id = obj.optLong("wbId", -1);
+					Long id = _id < 0 ? null : _id;
+					WhiteboardCache.remove(roomId, id);
+					sendWbAll(WbAction.removeWb, obj);
+				}
+					break;
+				case activateWb:
+				{
+					long _id = obj.optLong("wbId", -1);
+					if (_id > -1) {
+						WhiteboardCache.activate(roomId, _id);
+						sendWbAll(WbAction.activateWb, obj);
+					}
+				}
+					break;
+				case setSlide:
+				{
+					Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
+					wb.setSlide(obj.optInt("slide", 0));
+					WhiteboardCache.update(roomId, wb);
+					sendWbOthers(WbAction.setSlide, obj);
+				}
+					break;
+				case clearAll:
+				{
+					clearAll(roomId, obj.getLong("wbId"));
+				}
+					break;
+				case setSize:
+				{
+					Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
+					wb.setZoom(obj.getDouble("zoom"));
+					wb.setZoomMode(ZoomMode.valueOf(obj.getString("zoomMode")));
+					WhiteboardCache.update(roomId, wb);
+					sendWbOthers(WbAction.setSize, getAddWbJson(wb));
+					//TODO scroll????
+				}
+					break;
+				default:
+					break;
+			}
+		}
+		//wb-right
+		if (c.hasRight(Right.presenter) || c.hasRight(Right.whiteBoard)) {
+			switch (a) {
+				case createObj:
+				{
+					Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
+					JSONObject o = obj.getJSONObject("obj");
+					wb.put(o.getString("uid"), o);
+					WhiteboardCache.update(roomId, wb);
+					addUndo(wb.getId(), new UndoObject(UndoObject.Type.add, o));
+					sendWbOthers(WbAction.createObj, obj);
+				}
+					break;
+				case modifyObj:
+				{
+					Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
+					JSONArray arr = obj.getJSONArray("obj");
+					JSONArray undo = new JSONArray();
+					for (int i = 0; i < arr.length(); ++i) {
+						JSONObject _o = arr.getJSONObject(i);
+						String uid = _o.getString("uid");
+						undo.put(wb.get(uid));
+						wb.put(uid, _o);
+					}
+					if (arr.length() != 0) {
+						WhiteboardCache.update(roomId, wb);
+						addUndo(wb.getId(), new UndoObject(UndoObject.Type.modify, undo));
+					}
+					sendWbOthers(WbAction.modifyObj, obj);
+				}
+					break;
+				case deleteObj:
+				{
+					Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
+					JSONArray arr = obj.getJSONArray("obj");
+					JSONArray undo = new JSONArray();
+					for (int i = 0; i < arr.length(); ++i) {
+						JSONObject _o = arr.getJSONObject(i);
+						JSONObject u = wb.remove(_o.getString("uid"));
+						if (u != null) {
+							undo.put(u);
+						}
+					}
+					if (undo.length() != 0) {
+						WhiteboardCache.update(roomId, wb);
+						addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, undo));
+					}
+					sendWbAll(WbAction.deleteObj, obj);
+				}
+					break;
+				case clearSlide:
+				{
+					Whiteboard wb = WhiteboardCache.get(roomId).get(obj.getLong("wbId"));
+					JSONArray arr = wb.clearSlide(obj.getInt("slide"));
+					if (arr.length() != 0) {
+						WhiteboardCache.update(roomId, wb);
+						addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, arr));
+					}
+					sendWbAll(WbAction.clearSlide, obj);
+				}
+					break;
+				case save:
+					wb2save = obj.getLong("wbId");
+					fileName.open(target);
+					break;
+				case undo:
+				{
+					Long wbId = obj.getLong("wbId");
+					UndoObject uo = getUndo(wbId);
+					if (uo != null) {
+						Whiteboard wb = WhiteboardCache.get(roomId).get(wbId);
+						switch (uo.getType()) {
+							case add:
+							{
+								JSONObject o = new JSONObject(uo.getObject());
+								wb.remove(o.getString("uid"));
+								WhiteboardCache.update(roomId, wb);
+								sendWbAll(WbAction.deleteObj, obj.put("obj", new JSONArray().put(o)));
+							}
+								break;
+							case remove:
+							{
+								JSONArray arr = new JSONArray(uo.getObject());
+								for (int i  = 0; i < arr.length(); ++i) {
+									JSONObject o = arr.getJSONObject(i);
+									wb.put(o.getString("uid"), o);
+								}
+								WhiteboardCache.update(roomId, wb);
+								sendWbAll(WbAction.createObj, obj.put("obj", new JSONArray(uo.getObject())));
+							}
+								break;
+							case modify:
+							{
+								JSONArray arr = new JSONArray(uo.getObject());
+								for (int i  = 0; i < arr.length(); ++i) {
+									JSONObject o = arr.getJSONObject(i);
+									wb.put(o.getString("uid"), o);
+								}
+								WhiteboardCache.update(roomId, wb);
+								sendWbAll(WbAction.modifyObj, obj.put("obj", arr));
+							}
+								break;
+						}
+					}
+				}
+					break;
+				default:
+					break;
+			}
+		}
+	}
+
 	private static JSONObject getAddWbJson(Whiteboard wb) {
 		return new JSONObject().put("wbId", wb.getId())
 				.put("name", wb.getName())