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 2020/10/24 04:00:23 UTC

[openmeetings] branch master updated: [OPENMEETINGS-2493] recording is fixed, code is further improved

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git


The following commit(s) were added to refs/heads/master by this push:
     new 51d4710  [OPENMEETINGS-2493] recording is fixed, code is further improved
51d4710 is described below

commit 51d471006506f7a7423eb5e8aa18c34c15e59132
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Sat Oct 24 11:00:04 2020 +0700

    [OPENMEETINGS-2493] recording is fixed, code is further improved
---
 .../openmeetings/core/remote/AbstractStream.java   |   6 +-
 .../org/apache/openmeetings/core/remote/KRoom.java |  15 +--
 .../apache/openmeetings/core/remote/KStream.java   |  58 ++++++------
 .../openmeetings/core/remote/KTestStream.java      | 101 +++++++++++++--------
 .../openmeetings/core/remote/KurentoHandler.java   |  24 +++--
 .../openmeetings/core/remote/StreamProcessor.java  |  14 ++-
 .../core/remote/TestStreamProcessor.java           |  24 +----
 .../core/remote/TestRecordingFlowMocked.java       |   4 +-
 .../core/remote/TestRoomFlowMocked.java            |   2 -
 .../apache/openmeetings/web/app/TimerService.java  |   2 +-
 .../apache/openmeetings/web/room/RoomPanel.html    |   8 --
 .../org/apache/openmeetings/web/room/raw-sharer.js |  10 +-
 .../openmeetings/web/room/raw-video-manager.js     |   8 +-
 .../org/apache/openmeetings/web/room/raw-video.js  |  17 +++-
 .../web/user/record/RecordingsPanel.java           |  11 +--
 15 files changed, 158 insertions(+), 146 deletions(-)

diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/AbstractStream.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/AbstractStream.java
index c1376d2..6de2a46 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/AbstractStream.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/AbstractStream.java
@@ -41,11 +41,11 @@ public abstract class AbstractStream {
 		return uid;
 	}
 
-	public void release(IStreamProcessor processor) {
-		release(processor, true);
+	public void release() {
+		release(true);
 	}
 
-	public abstract void release(IStreamProcessor processor, boolean remove);
+	public abstract void release(boolean remove);
 
 	public static WebRtcEndpoint createWebRtcEndpoint(MediaPipeline pipeline) {
 		return new WebRtcEndpoint.Builder(pipeline).build();
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KRoom.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KRoom.java
index 8215b37..433a74e 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KRoom.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KRoom.java
@@ -24,6 +24,7 @@ package org.apache.openmeetings.core.remote;
 import static java.util.UUID.randomUUID;
 import static org.apache.openmeetings.core.remote.KurentoHandler.PARAM_ICE;
 import static org.apache.openmeetings.core.remote.KurentoHandler.newKurentoMsg;
+import static org.apache.openmeetings.db.util.ApplicationHelper.ensureApplication;
 
 import java.util.Date;
 import java.util.Optional;
@@ -55,7 +56,6 @@ import com.github.openjson.JSONObject;
  *
  */
 public class KRoom {
-
 	private static final Logger log = LoggerFactory.getLogger(KRoom.class);
 
 	/**
@@ -63,7 +63,6 @@ public class KRoom {
 	 */
 	private final StreamProcessor processor;
 	private final RecordingChunkDao chunkDao;
-	private final IApplication app;
 	private final Long roomId;
 	private final Room.Type type;
 	private final AtomicBoolean recordingStarted = new AtomicBoolean(false);
@@ -75,7 +74,6 @@ public class KRoom {
 	public KRoom(KurentoHandler handler, Room r) {
 		this.processor = handler.getStreamProcessor();
 		this.chunkDao = handler.getChunkDao();
-		this.app = handler.getApp();
 		this.roomId = r.getId();
 		this.type = r.getType();
 		log.info("ROOM {} has been created", roomId);
@@ -125,6 +123,8 @@ public class KRoom {
 
 	public void startRecording(Client c) {
 		if (recordingStarted.compareAndSet(false, true)) {
+			IApplication app = ensureApplication(c.getUser().getLanguageId());
+
 			log.debug("##REC:: recording in room {} is starting ::", roomId);
 			Room r = c.getRoom();
 			boolean interview = Room.Type.INTERVIEW == r.getType();
@@ -164,9 +164,7 @@ public class KRoom {
 			rec = processor.getRecordingDao().update(rec);
 			// Receive recordingId
 			recordingId = rec.getId();
-			processor.getByRoom(this.getRoomId()).forEach(
-					stream -> stream.startRecord(processor)
-			);
+			processor.getByRoom(this.getRoomId()).forEach(KStream::startRecord);
 
 			// Send notification to all users that the recording has been started
 			WebSocketHelper.sendRoom(new RoomMessage(roomId, u, RoomMessage.Type.RECORDING_TOGGLED));
@@ -224,7 +222,6 @@ public class KRoom {
 		if (sharingStarted.compareAndSet(false, true)) {
 			sharingUser.put("sid", c.getSid());
 			sd = c.addStream(StreamType.SCREEN, a);
-			sd.setWidth(msg.getInt("width")).setHeight(msg.getInt("height"));
 			cm.update(c);
 			log.debug("Stream.UID {}: sharing has been started, activity: {}", sd.getUid(), a);
 			h.sendClient(sd.getSid(), newKurentoMsg()
@@ -253,9 +250,7 @@ public class KRoom {
 	}
 
 	public void close() {
-		processor.getByRoom(this.getRoomId()).forEach(
-				stream -> stream.release(processor)
-		);
+		processor.getByRoom(this.getRoomId()).forEach(KStream::release);
 		log.debug("Room {} closed", this.roomId);
 	}
 }
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java
index 930d4ea..c79fd40 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java
@@ -25,12 +25,15 @@ import static java.util.UUID.randomUUID;
 import static java.util.concurrent.CompletableFuture.delayedExecutor;
 import static org.apache.openmeetings.core.remote.KurentoHandler.PARAM_CANDIDATE;
 import static org.apache.openmeetings.core.remote.KurentoHandler.PARAM_ICE;
+import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_ROOM;
+import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_STREAM_UID;
 import static org.apache.openmeetings.core.remote.KurentoHandler.getFlowoutTimeout;
 import static org.apache.openmeetings.core.remote.KurentoHandler.newKurentoMsg;
 import static org.apache.openmeetings.util.OmFileHelper.getRecUri;
 import static org.apache.openmeetings.util.OmFileHelper.getRecordingChunk;
 
 import java.util.Date;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
@@ -53,6 +56,7 @@ import org.kurento.client.MediaPipeline;
 import org.kurento.client.MediaProfileSpecType;
 import org.kurento.client.MediaType;
 import org.kurento.client.RecorderEndpoint;
+import org.kurento.client.RtpEndpoint;
 import org.kurento.client.WebRtcEndpoint;
 import org.kurento.jsonrpc.JsonUtils;
 import org.slf4j.Logger;
@@ -89,14 +93,9 @@ public class KStream extends AbstractStream {
 		//TODO Min/Max Audio/Video RecvBandwidth
 	}
 
-	public void startBroadcast(
-			final StreamProcessor processor
-			, final StreamDesc sd
-			, final String sdpOffer
-			, Runnable then)
-	{
+	public void startBroadcast(final StreamDesc sd, final String sdpOffer, Runnable then) {
 		if (outgoingMedia != null) {
-			release(processor, false);
+			release(false);
 		}
 		hasAudio = sd.hasActivity(Activity.AUDIO);
 		hasVideo = sd.hasActivity(Activity.VIDEO);
@@ -131,10 +130,10 @@ public class KStream extends AbstractStream {
 				profile = MediaProfileSpecType.WEBM_VIDEO_ONLY;
 				break;
 		}
-		pipeline = kHandler.createPipiline(room.getRoomId(), sd.getUid(), new Continuation<Void>() {
+		pipeline = kHandler.createPipiline(Map.of(TAG_ROOM, String.valueOf(room.getRoomId()), TAG_STREAM_UID, sd.getUid()), new Continuation<Void>() {
 			@Override
 			public void onSuccess(Void result) throws Exception {
-				internalStartBroadcast(processor, sd, sdpOffer);
+				internalStartBroadcast(sd, sdpOffer);
 				then.run();
 			}
 
@@ -145,12 +144,8 @@ public class KStream extends AbstractStream {
 		});
 	}
 
-	private void internalStartBroadcast(
-			final StreamProcessor processor
-			, final StreamDesc sd
-			, final String sdpOffer)
-	{
-		outgoingMedia = createEndpoint(processor, sd.getSid(), sd.getUid());
+	private void internalStartBroadcast(final StreamDesc sd, final String sdpOffer) {
+		outgoingMedia = createEndpoint(sd.getSid(), sd.getUid());
 		outgoingMedia.addMediaSessionTerminatedListener(evt -> log.warn("Media stream terminated {}", sd));
 		outgoingMedia.addMediaFlowOutStateChangeListener(evt -> {
 			log.info("Media Flow STATE :: {}, type {}, evt {}", evt.getState(), evt.getType(), evt.getMediaType());
@@ -159,7 +154,7 @@ public class KStream extends AbstractStream {
 				flowoutFuture = Optional.of(new CompletableFuture<>().completeAsync(() -> {
 					log.warn("KStream will be dropped {}", sd);
 					if (StreamType.SCREEN == streamType) {
-						processor.doStopSharing(sid, uid);
+						kHandler.getStreamProcessor().doStopSharing(sid, uid);
 					}
 					stopBroadcast();
 					return null;
@@ -173,9 +168,9 @@ public class KStream extends AbstractStream {
 			}
 		});
 		outgoingMedia.addMediaFlowInStateChangeListener(evt -> log.warn("Media FlowIn :: {}", evt));
-		addListener(processor, sd.getSid(), sd.getUid(), sdpOffer);
+		addListener(sd.getSid(), sd.getUid(), sdpOffer);
 		if (room.isRecording()) {
-			startRecord(processor);
+			startRecord();
 		}
 		Client c = sd.getClient();
 		WebSocketHelper.sendRoom(new TextRoomMessage(c.getRoomId(), c, RoomMessage.Type.RIGHT_UPDATED, c.getUid()));
@@ -187,7 +182,14 @@ public class KStream extends AbstractStream {
 		}
 	}
 
-	public void addListener(final StreamProcessor processor, String sid, String uid, String sdpOffer) {
+	private RtpEndpoint createRtpEndpoint(MediaPipeline pipeline) {
+		RtpEndpoint endpoint = new RtpEndpoint.Builder(pipeline).build();
+		endpoint.addTag("outUid", this.uid);
+		endpoint.addTag("uid", uid);
+		return endpoint;
+	}
+
+	public void addListener(String sid, String uid, String sdpOffer) {
 		final boolean self = uid.equals(this.uid);
 		log.info("USER {}: have started {} in room {}", uid, self ? "broadcasting" : "receiving", room.getRoomId());
 		log.trace("USER {}: SdpOffer is {}", uid, sdpOffer);
@@ -196,7 +198,7 @@ public class KStream extends AbstractStream {
 			return;
 		}
 
-		final WebRtcEndpoint endpoint = getEndpointForUser(processor, sid, uid);
+		final WebRtcEndpoint endpoint = getEndpointForUser(sid, uid);
 		final String sdpAnswer = endpoint.processOffer(sdpOffer);
 
 		log.debug("gather candidates");
@@ -208,7 +210,7 @@ public class KStream extends AbstractStream {
 				.put("sdpAnswer", sdpAnswer));
 	}
 
-	private WebRtcEndpoint getEndpointForUser(final StreamProcessor processor, String sid, String uid) {
+	private WebRtcEndpoint getEndpointForUser(String sid, String uid) {
 		if (uid.equals(this.uid)) {
 			log.debug("PARTICIPANT {}: configuring loopback", this.uid);
 			return outgoingMedia;
@@ -221,11 +223,11 @@ public class KStream extends AbstractStream {
 			listener.release();
 		}
 		log.debug("PARTICIPANT {}: creating new endpoint for {}", uid, this.uid);
-		listener = createEndpoint(processor, sid, uid);
+		listener = createEndpoint(sid, uid);
 		listeners.put(uid, listener);
 
 		log.debug("PARTICIPANT {}: obtained endpoint for {}", uid, this.uid);
-		Client cur = processor.getBySid(this.sid);
+		Client cur = kHandler.getStreamProcessor().getBySid(this.sid);
 		if (cur == null) {
 			log.warn("Client for endpoint dooesn't exists");
 		} else {
@@ -244,7 +246,7 @@ public class KStream extends AbstractStream {
 		return listener;
 	}
 
-	private WebRtcEndpoint createEndpoint(final StreamProcessor processor, String sid, String uid) {
+	private WebRtcEndpoint createEndpoint(String sid, String uid) {
 		WebRtcEndpoint endpoint = createWebRtcEndpoint(pipeline);
 		endpoint.addTag("outUid", this.uid);
 		endpoint.addTag("uid", uid);
@@ -258,10 +260,10 @@ public class KStream extends AbstractStream {
 		return endpoint;
 	}
 
-	public void startRecord(StreamProcessor processor) {
+	public void startRecord() {
 		log.debug("startRecord outMedia OK ? {}", outgoingMedia != null);
 		if (outgoingMedia == null) {
-			release(processor, true);
+			release(true);
 			return;
 		}
 		final String chunkUid = "rec_" + room.getRecordingId() + "_" + randomUUID();
@@ -351,7 +353,7 @@ public class KStream extends AbstractStream {
 	}
 
 	@Override
-	public void release(IStreamProcessor processor, boolean remove) {
+	public void release(boolean remove) {
 		if (outgoingMedia != null) {
 			releaseListeners();
 			outgoingMedia.release(new Continuation<Void>() {
@@ -380,7 +382,7 @@ public class KStream extends AbstractStream {
 			outgoingMedia = null;
 		}
 		if (remove) {
-			processor.release(this, false);
+			kHandler.getStreamProcessor().release(this, false);
 		}
 	}
 
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KTestStream.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KTestStream.java
index 0839693..6949528 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KTestStream.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KTestStream.java
@@ -19,7 +19,10 @@
 package org.apache.openmeetings.core.remote;
 
 import static java.util.UUID.randomUUID;
+import static org.apache.openmeetings.core.remote.KurentoHandler.MODE_TEST;
 import static org.apache.openmeetings.core.remote.KurentoHandler.PARAM_CANDIDATE;
+import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_MODE;
+import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_ROOM;
 import static org.apache.openmeetings.core.remote.KurentoHandler.sendError;
 import static org.apache.openmeetings.core.remote.TestStreamProcessor.newTestKurentoMsg;
 import static org.apache.openmeetings.util.OmFileHelper.EXTENSION_WEBM;
@@ -27,6 +30,7 @@ import static org.apache.openmeetings.util.OmFileHelper.TEST_SETUP_PREFIX;
 import static org.apache.openmeetings.util.OmFileHelper.getStreamsDir;
 
 import java.io.File;
+import java.util.Map;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -50,6 +54,8 @@ import com.github.openjson.JSONObject;
 
 public class KTestStream extends AbstractStream {
 	private static final Logger log = LoggerFactory.getLogger(KTestStream.class);
+	private static final Map<String, String> TAGS = Map.of(TAG_MODE, MODE_TEST, TAG_ROOM, MODE_TEST);
+	private final KurentoHandler kHandler;
 	private MediaPipeline pipeline;
 	private WebRtcEndpoint webRtcEndpoint;
 	private PlayerEndpoint player;
@@ -59,9 +65,13 @@ public class KTestStream extends AbstractStream {
 	private ScheduledFuture<?> recHandle;
 	private int recTime;
 
-	public KTestStream(IWsClient c, JSONObject msg, MediaPipeline pipeline) {
+	public KTestStream(IWsClient c, JSONObject msg, KurentoHandler kHandler) {
 		super(null, c.getUid());
-		this.pipeline = pipeline;
+		this.kHandler = kHandler;
+		createPipeline(() -> startTestRecording(c, msg));
+	}
+
+	private void startTestRecording(IWsClient c, JSONObject msg) {
 		webRtcEndpoint = createWebRtcEndpoint(pipeline);
 		webRtcEndpoint.connect(webRtcEndpoint);
 
@@ -122,34 +132,31 @@ public class KTestStream extends AbstractStream {
 		});
 	}
 
-	public void play(final IWsClient inClient, JSONObject msg, MediaPipeline inPipeline) {
-		this.pipeline = inPipeline;
-		webRtcEndpoint = createWebRtcEndpoint(pipeline);
-		player = createPlayerEndpoint(pipeline, recPath);
-		player.connect(webRtcEndpoint);
-		webRtcEndpoint.addMediaSessionStartedListener(evt -> {
-				log.info("Media session started {}", evt);
-				player.addErrorListener(event -> {
-						log.info("ErrorEvent for player with uid '{}': {}", inClient.getUid(), event.getDescription());
-						sendPlayEnd(inClient);
-					});
-				player.addEndOfStreamListener(event -> {
-						log.info("EndOfStreamEvent for player with uid '{}'", inClient.getUid());
-						sendPlayEnd(inClient);
-					});
-				player.play();
-			});
+	public void play(final IWsClient inClient, JSONObject msg) {
+		createPipeline(() -> {
+			webRtcEndpoint = createWebRtcEndpoint(pipeline);
+			player = createPlayerEndpoint(pipeline, recPath);
+			player.connect(webRtcEndpoint);
+			webRtcEndpoint.addMediaSessionStartedListener(evt -> {
+					log.info("Media session started {}", evt);
+					player.addErrorListener(event -> {
+							log.info("ErrorEvent for player with uid '{}': {}", inClient.getUid(), event.getDescription());
+							sendPlayEnd(inClient);
+						});
+					player.addEndOfStreamListener(event -> {
+							log.info("EndOfStreamEvent for player with uid '{}'", inClient.getUid());
+							sendPlayEnd(inClient);
+						});
+					player.play();
+				});
+			addIceListener(inClient);
 
-		String sdpOffer = msg.getString("sdpOffer");
-		String sdpAnswer = webRtcEndpoint.processOffer(sdpOffer);
-
-		addIceListener(inClient);
-
-		WebSocketHelper.sendClient(inClient, newTestKurentoMsg()
-				.put("id", "playResponse")
-				.put("sdpAnswer", sdpAnswer));
+			WebSocketHelper.sendClient(inClient, newTestKurentoMsg()
+					.put("id", "playResponse")
+					.put("sdpAnswer", webRtcEndpoint.processOffer(msg.getString("sdpOffer"))));
 
-		webRtcEndpoint.gatherCandidates();
+			webRtcEndpoint.gatherCandidates();
+		});
 	}
 
 	public void addCandidate(IceCandidate cand) {
@@ -158,6 +165,21 @@ public class KTestStream extends AbstractStream {
 		}
 	}
 
+	private void createPipeline(Runnable action) {
+		release(false);
+		this.pipeline = kHandler.createPipiline(TAGS, new Continuation<Void>() {
+			@Override
+			public void onSuccess(Void result) throws Exception {
+				action.run();
+			}
+
+			@Override
+			public void onError(Throwable cause) throws Exception {
+				log.warn("Unable to create pipeline for test stream", cause);
+			}
+		});
+	}
+
 	private void addIceListener(IWsClient inClient) {
 		webRtcEndpoint.addIceCandidateFoundListener(evt -> {
 				IceCandidate cand = evt.getCandidate();
@@ -192,6 +214,13 @@ public class KTestStream extends AbstractStream {
 		recPath = OmFileHelper.getRecUri(f);
 	}
 
+	private void releaseEndpoint() {
+		if (webRtcEndpoint != null) {
+			webRtcEndpoint.release();
+			webRtcEndpoint = null;
+		}
+	}
+
 	private void releasePipeline() {
 		if (pipeline != null) {
 			pipeline.release();
@@ -200,29 +229,29 @@ public class KTestStream extends AbstractStream {
 	}
 
 	private void releaseRecorder() {
-		releasePipeline();
+		releaseEndpoint();
 		if (recorder != null) {
 			recorder.release();
 			recorder = null;
 		}
+		releasePipeline();
 	}
 
 	private void releasePlayer() {
-		releasePipeline();
+		releaseEndpoint();
 		if (player != null) {
 			player.release();
 			player = null;
 		}
+		releasePipeline();
 	}
 
 	@Override
-	public void release(IStreamProcessor processor, boolean remove) {
-		if (webRtcEndpoint != null) {
-			webRtcEndpoint.release();
-			webRtcEndpoint = null;
-		}
+	public void release(boolean remove) {
 		releasePlayer();
 		releaseRecorder();
-		processor.release(this, true);
+		if (remove) {
+			kHandler.getTestProcessor().release(this, true);
+		}
 	}
 }
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KurentoHandler.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KurentoHandler.java
index 21ff59e..7023063 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KurentoHandler.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KurentoHandler.java
@@ -41,7 +41,7 @@ import javax.annotation.PreDestroy;
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
-import org.apache.openmeetings.IApplication;
+import org.apache.openmeetings.core.sip.SipManager;
 import org.apache.openmeetings.core.util.WebSocketHelper;
 import org.apache.openmeetings.db.dao.record.RecordingChunkDao;
 import org.apache.openmeetings.db.dao.room.RoomDao;
@@ -65,6 +65,7 @@ import org.kurento.client.MediaPipeline;
 import org.kurento.client.ObjectCreatedEvent;
 import org.kurento.client.PlayerEndpoint;
 import org.kurento.client.RecorderEndpoint;
+import org.kurento.client.RtpEndpoint;
 import org.kurento.client.Tag;
 import org.kurento.client.Transaction;
 import org.kurento.client.WebRtcEndpoint;
@@ -122,8 +123,6 @@ public class KurentoHandler {
 	@Autowired
 	private IClientManager cm;
 	@Autowired
-	private IApplication app;
-	@Autowired
 	private RoomDao roomDao;
 	@Autowired
 	private RecordingChunkDao chunkDao;
@@ -131,6 +130,8 @@ public class KurentoHandler {
 	private TestStreamProcessor testProcessor;
 	@Autowired
 	private StreamProcessor streamProcessor;
+	@Autowired
+	private SipManager sipManager;
 
 	boolean isConnected() {
 		boolean connctd = connected.get() && client != null && !client.isClosed();
@@ -298,12 +299,11 @@ public class KurentoHandler {
 		streamProcessor.remove((Client)c);
 	}
 
-	MediaPipeline createPipiline(Long roomId, String uid, Continuation<Void> continuation) {
+	MediaPipeline createPipiline(Map<String, String> tags, Continuation<Void> continuation) {
 		Transaction t = beginTransaction();
 		MediaPipeline pipe = client.createMediaPipeline(t);
 		pipe.addTag(t, TAG_KUID, kuid);
-		pipe.addTag(t, TAG_ROOM, String.valueOf(roomId));
-		pipe.addTag(t, TAG_STREAM_UID, uid);
+		tags.forEach((key, value) -> pipe.addTag(t, key, value));
 		t.commit(continuation);
 		return pipe;
 	}
@@ -396,14 +396,18 @@ public class KurentoHandler {
 		return kuid;
 	}
 
-	IApplication getApp() {
-		return app;
+	public TestStreamProcessor getTestProcessor() {
+		return testProcessor;
 	}
 
 	StreamProcessor getStreamProcessor() {
 		return streamProcessor;
 	}
 
+	SipManager getSipManager() {
+		return sipManager;
+	}
+
 	RecordingChunkDao getChunkDao() {
 		return chunkDao;
 	}
@@ -460,7 +464,7 @@ public class KurentoHandler {
 								{
 									return;
 								} else {
-									stream.release(streamProcessor);
+									stream.release();
 								}
 							}
 						}
@@ -480,6 +484,8 @@ public class KurentoHandler {
 					clazz = RecorderEndpoint.class;
 				} else if (curPoint instanceof PlayerEndpoint) {
 					clazz = PlayerEndpoint.class;
+				} else if (curPoint instanceof RtpEndpoint) {
+					clazz = RtpEndpoint.class;
 				}
 				final Class<? extends Endpoint> fClazz = clazz;
 				scheduler.schedule(() -> {
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/StreamProcessor.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/StreamProcessor.java
index 05e0a9b..4855d86 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/StreamProcessor.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/StreamProcessor.java
@@ -134,7 +134,7 @@ public class StreamProcessor implements IStreamProcessor {
 					if (StreamType.SCREEN == sd.getType() && sd.hasActivity(Activity.RECORD) && !sd.hasActivity(Activity.SCREEN)) {
 						break;
 					}
-					sender.addListener(this, c.getSid(), c.getUid(), msg.getString("sdpOffer"));
+					sender.addListener(c.getSid(), c.getUid(), msg.getString("sdpOffer"));
 				}
 				break;
 			case "wannaShare":
@@ -184,13 +184,17 @@ public class StreamProcessor implements IStreamProcessor {
 				KRoom room = kHandler.getRoom(c.getRoomId());
 				sender = room.join(sd, kHandler);
 			}
+			if (msg.has("width")) {
+				sd.setWidth(msg.getInt("width")).setHeight(msg.getInt("height"));
+				cm.update(c);
+			}
 			startBroadcast(sender, sd, msg.getString("sdpOffer"), () -> {
 				if (StreamType.SCREEN == sd.getType() && sd.hasActivity(Activity.RECORD) && !isRecording(c.getRoomId())) {
 					startRecording(c);
 				}
 			});
 		} catch (KurentoServerException e) {
-			sender.release(this);
+			sender.release();
 			WebSocketHelper.sendClient(c, newStoppedMsg(sd));
 			sendError(c, "Failed to start broadcast: " + e.getMessage());
 			log.error("Failed to start broadcast", e);
@@ -208,7 +212,7 @@ public class StreamProcessor implements IStreamProcessor {
 	 * @return the current KStream
 	 */
 	void startBroadcast(KStream stream, StreamDesc sd, String sdpOffer, Runnable then) {
-		stream.startBroadcast(this, sd, sdpOffer, then);
+		stream.startBroadcast(sd, sdpOffer, then);
 	}
 
 	private static boolean isBroadcasting(final Client c) {
@@ -494,7 +498,7 @@ public class StreamProcessor implements IStreamProcessor {
 		for (StreamDesc sd : c.getStreams()) {
 			AbstractStream s = getByUid(sd.getUid());
 			if (s != null) {
-				s.release(this);
+				s.release();
 				WebSocketHelper.sendRoomOthers(c.getRoomId(), c.getUid(), newStoppedMsg(sd));
 			}
 		}
@@ -545,7 +549,7 @@ public class StreamProcessor implements IStreamProcessor {
 	public void release(AbstractStream stream, boolean releaseStream) {
 		final String uid = stream.getUid();
 		if (releaseStream) {
-			stream.release(this);
+			stream.release();
 		}
 		Client c = cm.getBySid(stream.getSid());
 		if (c != null) {
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/TestStreamProcessor.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/TestStreamProcessor.java
index c93c92b..499d772 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/TestStreamProcessor.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/TestStreamProcessor.java
@@ -22,9 +22,7 @@ package org.apache.openmeetings.core.remote;
 import static org.apache.openmeetings.core.remote.KurentoHandler.MODE_TEST;
 import static org.apache.openmeetings.core.remote.KurentoHandler.PARAM_CANDIDATE;
 import static org.apache.openmeetings.core.remote.KurentoHandler.PARAM_ICE;
-import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_KUID;
 import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_MODE;
-import static org.apache.openmeetings.core.remote.KurentoHandler.TAG_ROOM;
 
 import java.util.Map;
 import java.util.Map.Entry;
@@ -33,8 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.openmeetings.core.util.WebSocketHelper;
 import org.apache.openmeetings.db.entity.basic.IWsClient;
 import org.kurento.client.IceCandidate;
-import org.kurento.client.MediaPipeline;
-import org.kurento.client.Transaction;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -58,9 +54,9 @@ class TestStreamProcessor implements IStreamProcessor {
 				break;
 			case "record":
 				if (user != null) {
-					user.release(this);
+					user.release();
 				}
-				user = new KTestStream(c, msg, createTestPipeline());
+				user = new KTestStream(c, msg, kHandler);
 				streamByUid.put(c.getUid(), user);
 				break;
 			case "iceCandidate":
@@ -79,7 +75,7 @@ class TestStreamProcessor implements IStreamProcessor {
 				break;
 			case "play":
 				if (user != null) {
-					user.play(c, msg, createTestPipeline());
+					user.play(c, msg);
 				}
 				break;
 			default:
@@ -92,16 +88,6 @@ class TestStreamProcessor implements IStreamProcessor {
 		return uid == null ? null : streamByUid.get(uid);
 	}
 
-	private MediaPipeline createTestPipeline() {
-		Transaction t = kHandler.beginTransaction();
-		MediaPipeline pipe = kHandler.getClient().createMediaPipeline(t);
-		pipe.addTag(t, TAG_KUID, kHandler.getKuid());
-		pipe.addTag(t, TAG_MODE, MODE_TEST);
-		pipe.addTag(t, TAG_ROOM, MODE_TEST);
-		t.commit();
-		return pipe;
-	}
-
 	static JSONObject newTestKurentoMsg() {
 		return KurentoHandler.newKurentoMsg().put(TAG_MODE, MODE_TEST);
 	}
@@ -109,7 +95,7 @@ class TestStreamProcessor implements IStreamProcessor {
 	void remove(IWsClient c) {
 		AbstractStream s = getByUid(c.getUid());
 		if (s != null) {
-			s.release(this);
+			s.release();
 		}
 	}
 
@@ -121,7 +107,7 @@ class TestStreamProcessor implements IStreamProcessor {
 	@Override
 	public void destroy() {
 		for (Entry<String, KTestStream> e : streamByUid.entrySet()) {
-			e.getValue().release(this);
+			e.getValue().release();
 			streamByUid.remove(e.getKey());
 		}
 		streamByUid.clear();
diff --git a/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRecordingFlowMocked.java b/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRecordingFlowMocked.java
index 428ecf8..377d48b 100644
--- a/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRecordingFlowMocked.java
+++ b/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRecordingFlowMocked.java
@@ -148,8 +148,6 @@ class TestRecordingFlowMocked extends BaseMockedTest {
 	private void testStartRecordWhenSharingWasNot() throws Exception {
 		JSONObject msg = new JSONObject(MSG_BASE.toString())
 				.put("id", "wannaRecord")
-				.put("width", 640)
-				.put("height", 480)
 				.put("shareType", "shareType")
 				.put("fps", "fps")
 				;
@@ -172,6 +170,8 @@ class TestRecordingFlowMocked extends BaseMockedTest {
 				.put("type", "kurento")
 				.put("uid", streamDescUID)
 				.put("sdpOffer", "SDP-OFFER")
+				.put("width", 640)
+				.put("height", 480)
 				;
 		handler.onMessage(c, msgBroadcastStarted);
 
diff --git a/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRoomFlowMocked.java b/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRoomFlowMocked.java
index 8c00ab3..d120f9b 100644
--- a/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRoomFlowMocked.java
+++ b/openmeetings-core/src/test/java/org/apache/openmeetings/core/remote/TestRoomFlowMocked.java
@@ -146,8 +146,6 @@ class TestRoomFlowMocked extends BaseMockedTest {
 		runWrapped(() -> {
 			JSONObject msg = new JSONObject(MSG_BASE.toString())
 					.put("id", "wannaRecord")
-					.put("width", 640)
-					.put("height", 480)
 					.put("shareType", "shareType")
 					.put("fps", "fps")
 					;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/TimerService.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/TimerService.java
index 2a2800b..a8bd24b 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/TimerService.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/TimerService.java
@@ -123,7 +123,7 @@ public class TimerService {
 
 	public void scheduleSipCheck(Room r) {
 		// sip allowed and configured
-		if (sipManager.getSipUser(r) != null && !sipCheckMap.containsKey(r.getId())) {
+		if (sipManager.getSipUser(r) != null && r.isSipEnabled() && !sipCheckMap.containsKey(r.getId())) {
 			doSipCheck(r.getId());
 		}
 	}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
index 17920c5..315953b 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
@@ -117,14 +117,6 @@
 					</select>
 				</div>
 				<div class="row-no-gutters">
-					<label class="col-7"><wicket:message key="740"/></label>
-					<input class="width col-4" type="number" value="800"/>
-				</div>
-				<div class="row-no-gutters">
-					<label class="col-7"><wicket:message key="741"/></label>
-					<input class="height col-4" type="number" value="600"/>
-				</div>
-				<div class="row-no-gutters">
 					<label class="col-7"><wicket:message key="1089"/></label>
 					<select name="fps" class="fps col-4 custom-select">
 						<option value="2">2 FPS</option>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-sharer.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-sharer.js
index 3561a7a..a801caf 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-sharer.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-sharer.js
@@ -4,7 +4,7 @@ var SHARE_STARTED = 'started';
 var SHARE_STOPPED = 'stopped';
 var Sharer = (function() {
 	const self = {};
-	let sharer, type, fps, sbtn, rbtn, width, height
+	let sharer, type, fps, sbtn, rbtn
 		, shareState = SHARE_STOPPED, recState = SHARE_STOPPED;
 
 	/**
@@ -43,8 +43,6 @@ var Sharer = (function() {
 						id: 'wannaShare'
 						, shareType: type.val()
 						, fps: fps.val()
-						, width: width.val()
-						, height: height.val()
 					});
 				} else {
 					VideoManager.sendMessage({
@@ -53,8 +51,6 @@ var Sharer = (function() {
 					});
 				}
 			});
-			width = sharer.find('.width');
-			height = sharer.find('.height');
 			rbtn = sharer.find('.record-start-stop').off();
 			if (Room.getOptions().allowRecording) {
 				rbtn.show().click(function() {
@@ -64,8 +60,6 @@ var Sharer = (function() {
 							id: 'wannaRecord'
 							, shareType: type.val()
 							, fps: fps.val()
-							, width: width.val()
-							, height: height.val()
 						});
 					} else {
 						VideoManager.sendMessage({
@@ -96,8 +90,6 @@ var Sharer = (function() {
 			, typeDis = _typeDisabled();
 		_disable(type, dis);
 		_disable(fps, dis || typeDis);
-		_disable(width, dis);
-		_disable(height, dis);
 		btn.find('span').text(btn.data(dis ? 'stop' : 'start'));
 		if (dis) {
 			btn.addClass('stop');
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video-manager.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video-manager.js
index ad4e40f..491d5d0 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video-manager.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video-manager.js
@@ -6,7 +6,9 @@ var VideoManager = (function() {
 	function _onVideoResponse(m) {
 		const w = $('#' + VideoUtil.getVid(m.uid))
 			, v = w.data();
-		v.processSdpAnswer(m.sdpAnswer);
+		if (v) {
+			v.processSdpAnswer(m.sdpAnswer);
+		}
 	}
 	function _onBroadcast(msg) {
 		const sd = msg.stream
@@ -61,7 +63,9 @@ var VideoManager = (function() {
 				{
 					const w = $('#' + VideoUtil.getVid(m.uid))
 						, v = w.data();
-					v.processIceCandidate(m.candidate);
+					if (v) {
+						v.processIceCandidate(m.candidate);
+					}
 				}
 				break;
 			case 'newStream':
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video.js
index 215b114..01a480b 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-video.js
@@ -181,11 +181,18 @@ var Video = (function() {
 						return OmUtil.error('Sender sdp offer error ' + genErr);
 					}
 					OmUtil.log('Invoking Sender SDP offer callback function');
-					VideoManager.sendMessage({
-						id : 'broadcastStarted'
-						, uid: sd.uid
-						, sdpOffer: offerSdp
-					});
+					const bmsg = {
+							id : 'broadcastStarted'
+							, uid: sd.uid
+							, sdpOffer: offerSdp
+						}, vtracks = state.stream.getVideoTracks();
+					if (vtracks && vtracks.length > 0) {
+						const vts = vtracks[0].getSettings();
+						bmsg.width = vts.width;
+						bmsg.height = vts.height;
+						bmsg.fps = vts.frameRate;
+					}
+					VideoManager.sendMessage(bmsg);
 					if (isSharing) {
 						Sharer.setShareState(SHARE_STARTED);
 					}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingsPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingsPanel.java
index aec9bb7..1cd806e 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingsPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingsPanel.java
@@ -23,7 +23,6 @@ import static org.apache.openmeetings.util.OmFileHelper.getRecordingChunk;
 import static org.apache.openmeetings.web.app.WebSession.getUserId;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 import org.apache.openmeetings.core.converter.IRecordingConverter;
 import org.apache.openmeetings.core.converter.InterviewConverter;
@@ -34,7 +33,6 @@ import org.apache.openmeetings.db.dto.record.RecordingContainerData;
 import org.apache.openmeetings.db.entity.file.BaseFileItem;
 import org.apache.openmeetings.db.entity.record.Recording;
 import org.apache.openmeetings.db.entity.record.Recording.Status;
-import org.apache.openmeetings.db.entity.record.RecordingChunk;
 import org.apache.openmeetings.web.common.InvitationDialog;
 import org.apache.openmeetings.web.common.NameDialog;
 import org.apache.openmeetings.web.common.UserBasePanel;
@@ -113,12 +111,11 @@ public class RecordingsPanel extends UserBasePanel {
 							Recording r = (Recording)getLastSelected();
 							isInterview = r.isInterview();
 
-							if (r.getOwnerId() != null && r.getOwnerId().equals(getUserId()) && r.getStatus() != Status.RECORDING && r.getStatus() != Status.CONVERTING) {
-								List<RecordingChunk> chunks = chunkDao.getByRecording(r.getId())
+							if (r.getRoomId() != null && r.getOwnerId() != null && r.getOwnerId().equals(getUserId()) && r.getStatus() != Status.RECORDING && r.getStatus() != Status.CONVERTING) {
+								// will enable re-conversion if at least some of the chunks are OK
+								enabled = chunkDao.getByRecording(r.getId())
 										.stream()
-										.filter(chunk -> r.getRoomId() == null || !getRecordingChunk(r.getRoomId(), chunk.getStreamName()).exists())
-										.collect(Collectors.toList());
-								enabled = !chunks.isEmpty();
+										.anyMatch(chunk -> getRecordingChunk(r.getRoomId(), chunk.getStreamName()).exists());
 							}
 						}
 						setEnabled(enabled);