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 2018/10/04 17:37:25 UTC

[openmeetings] branch master updated: [OPENMEETINGS-1649] initial work on turn server

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 cd49869  [OPENMEETINGS-1649] initial work on turn server
cd49869 is described below

commit cd4986965221fd02df7ea6d2c13b8fb6b902478a
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Fri Oct 5 00:37:07 2018 +0700

    [OPENMEETINGS-1649] initial work on turn server
---
 .../apache/openmeetings/core/remote/KStream.java   |  4 --
 .../openmeetings/core/remote/KurentoHandler.java   | 71 ++++++++++++++++++++--
 openmeetings-server/src/site/site.xml              |  1 +
 .../src/site/xdoc/InstallMediaServer.xml           | 35 +++++++++++
 .../apache/openmeetings/web/room/raw-settings.js   | 45 +++++++++-----
 .../webapp/WEB-INF/classes/applicationContext.xml  |  9 ++-
 6 files changed, 138 insertions(+), 27 deletions(-)

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 f6642a9..835cb6a 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
@@ -31,7 +31,6 @@ import org.apache.openmeetings.db.entity.basic.Client;
 import org.apache.openmeetings.db.entity.basic.Client.Activity;
 import org.apache.openmeetings.db.util.ws.RoomMessage;
 import org.apache.openmeetings.db.util.ws.TextRoomMessage;
-import org.apache.wicket.util.string.Strings;
 import org.kurento.client.Continuation;
 import org.kurento.client.EventListener;
 import org.kurento.client.IceCandidate;
@@ -160,9 +159,6 @@ public class KStream implements IKStream {
 
 	private WebRtcEndpoint createEndpoint(final KurentoHandler h, final KStream sender) {
 		WebRtcEndpoint endpoint = new WebRtcEndpoint.Builder(pipeline).build();
-		if (!Strings.isEmpty(h.getTurnServerUrl())) {
-			endpoint.setTurnUrl(h.getTurnServerUrl());
-		}
 		endpoint.addTag("suid", uid);
 		endpoint.addTag("uid", sender.getUid());
 
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 2dae1dc..9d8eeda 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
@@ -21,6 +21,9 @@ package org.apache.openmeetings.core.remote;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -29,6 +32,10 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.directory.api.util.Strings;
 import org.apache.openmeetings.core.util.WebSocketHelper;
 import org.apache.openmeetings.db.entity.basic.Client;
 import org.apache.openmeetings.db.entity.basic.Client.StreamDesc;
@@ -53,6 +60,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import com.github.openjson.JSONArray;
 import com.github.openjson.JSONObject;
 
 public class KurentoHandler {
@@ -61,10 +69,15 @@ public class KurentoHandler {
 	private final static String TAG_KUID = "kuid";
 	private final static String TAG_MODE = "mode";
 	private final static String TAG_ROOM = "roomId";
+	private final static String HMAC_SHA1_ALGORITHM = "HmacSHA1";
 	public final static String KURENTO_TYPE = "kurento";
 	private long checkTimeout = 200; //ms
 	private String kurentoWsUrl;
-	private String turnServerUrl;
+	private String turnUrl;
+	private String turnUser;
+	private String turnSecret;
+	private String turnMode;
+	private int turnTtl = 60; //minutes
 	private KurentoClient client;
 	private String kuid;
 	private ScheduledExecutorService scheduler;
@@ -200,7 +213,13 @@ public class KurentoHandler {
 		if (MODE_TEST.equals(msg.optString(TAG_MODE))) {
 			KTestStream user = getTestByUid(_c.getUid());
 			switch (cmdId) {
-				case "start":
+				case "wannaRecord":
+					WebSocketHelper.sendClient(_c, newTestKurentoMsg()
+							.put("id", "canRecord")
+							.put("configuration", new JSONObject().put("iceServers", getTurnServers(true)))
+							);
+					break;
+				case "record":
 				{
 					//TODO FIXME assert null user ???
 					user = new KTestStream(_c, msg, createTestPipeline());
@@ -430,6 +449,34 @@ public class KurentoHandler {
 		return r;
 	}
 
+	private JSONArray getTurnServers(final boolean test) {
+		JSONArray arr = new JSONArray();
+		if (!Strings.isEmpty(turnUrl)) {
+			try {
+				JSONObject turn = new JSONObject();
+				if ("rest".equalsIgnoreCase(turnMode)) {
+					Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
+					mac.init(new SecretKeySpec(turnSecret.getBytes(), HMAC_SHA1_ALGORITHM));
+					StringBuilder user = new StringBuilder()
+							.append((test ? 30 : turnTtl) + System.currentTimeMillis() / 1000L);
+					if (!Strings.isEmpty(turnUser)) {
+						user.append(':').append(turnUser);
+					}
+					turn.put("username", user)
+						.put("credential", Base64.getEncoder().encodeToString(mac.doFinal(user.toString().getBytes())));
+				} else {
+					turn.put("username", turnUser)
+						.put("credential", turnSecret);
+				}
+				turn.put("url", String.format("turn:%s", turnUrl));
+				arr.put(turn);
+			} catch (NoSuchAlgorithmException|InvalidKeyException e) {
+				log.error("Unexpected error while creating turn", e);
+			}
+		}
+		return arr;
+	}
+
 	public void setCheckTimeout(long checkTimeout) {
 		this.checkTimeout = checkTimeout;
 	}
@@ -438,11 +485,23 @@ public class KurentoHandler {
 		this.kurentoWsUrl = kurentoWsUrl;
 	}
 
-	public String getTurnServerUrl() {
-		return turnServerUrl;
+	public void setTurnUrl(String turnUrl) {
+		this.turnUrl = turnUrl;
+	}
+
+	public void setTurnUser(String turnUser) {
+		this.turnUser = turnUser;
+	}
+
+	public void setTurnSecret(String turnSecret) {
+		this.turnSecret = turnSecret;
+	}
+
+	public void setTurnMode(String turnMode) {
+		this.turnMode = turnMode;
 	}
 
-	public void setTurnServerUrl(String turnServerUrl) {
-		this.turnServerUrl = turnServerUrl;
+	public void setTurnTtl(int turnTtl) {
+		this.turnTtl = turnTtl;
 	}
 }
diff --git a/openmeetings-server/src/site/site.xml b/openmeetings-server/src/site/site.xml
index db9d1fc..faabb8e 100644
--- a/openmeetings-server/src/site/site.xml
+++ b/openmeetings-server/src/site/site.xml
@@ -30,6 +30,7 @@
 		<menu name="Installation">
 			<item name="Installation" href="/installation.html" />
 			<item name="Upgrade" href="/Upgrade.html" />
+			<item name="Media Server Installation" href="/InstallMediaServer.html" />
 			<item name="Tutorials" href="https://cwiki.apache.org/confluence/display/OPENMEETINGS/Tutorials+for+installing+OpenMeetings+and+Tools" />
 			<item name="Command Line Admin" href="/CommandLineAdmin.html" />
 		</menu>
diff --git a/openmeetings-server/src/site/xdoc/InstallMediaServer.xml b/openmeetings-server/src/site/xdoc/InstallMediaServer.xml
new file mode 100644
index 0000000..0e42071
--- /dev/null
+++ b/openmeetings-server/src/site/xdoc/InstallMediaServer.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ -->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+	<properties>
+		<title>Media Server Installation</title>
+		<author email="dev@openmeetings.apache.org">Apache OpenMeetings Team</author>
+	</properties>
+	<body>
+		<section name="Install Kurento Media server">
+			<p>
+				<a href="https://doc-kurento.readthedocs.io/en/stable/user/installation.html">Install Kurento Media server</a>
+				<div class="bs-callout bs-callout-danger">
+					It should be run under same user as OM
+				</div>
+			</p>
+		</section>
+		<section name="Specify/Install Turn server">
+			<div class="bs-callout bs-callout-info">Optional step</div>
+		</section>
+	</body>
+</document>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-settings.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-settings.js
index 07aaf61..fa51418 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-settings.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-settings.js
@@ -65,7 +65,7 @@ var MicLevel = (function() {
 	};
 });
 var VideoSettings = (function() {
-	let vs, lm, s, cam, mic, res, o, rtcPeer, offerSdp, timer
+	let vs, lm, s, cam, mic, res, o, rtcPeer, timer
 		, vidScroll, vid, recBtn, playBtn, recAllowed = false
 		, level;
 	const MsgBase = {type: 'kurento', mode: 'test'};
@@ -103,7 +103,6 @@ var VideoSettings = (function() {
 			level = null;
 		}
 		_micActivity(0);
-		offerSdp = null;
 	}
 	function _close() {
 		_clear();
@@ -159,16 +158,9 @@ var VideoSettings = (function() {
 			.click(function() {
 				recBtn.prop('disabled', true).button('refresh');
 				_setEnabled(true);
-
-				OmUtil.info('Invoking SDP offer callback function');
-				const cnts = _constraints();
 				OmUtil.sendMessage({
-					id : 'start'
-					, sdpOffer: offerSdp
-					, video: cnts.video !== false
-					, audio: cnts.audio !== false
+					id : 'wannaRecord'
 				}, MsgBase);
-				rtcPeer.on('icecandidate', _onIceCandidate);
 			});
 		playBtn = vs.find('.play')
 			.button({icon: "ui-icon-play"})
@@ -284,7 +276,7 @@ var VideoSettings = (function() {
 		}
 		return cnts;
 	}
-	function _readValues() {
+	function _readValues(msg, func) {
 		const v = cam.find('option:selected')
 			, m = mic.find('option:selected')
 			, o = res.find('option:selected').data();
@@ -300,10 +292,16 @@ var VideoSettings = (function() {
 		_clear();
 		const cnts = _constraints();
 		if (cnts.video !== false || cnts.audio !== false) {
+			const options = {
+				localVideo: vid[0]
+				, mediaConstraints: cnts
+			};
+			if (msg && msg.configuration) {
+				options.configuration = msg.configuration;
+			}
 			rtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(
-				{
-					localVideo: vid[0], mediaConstraints: cnts
-				}, function(error) {
+				options
+				, function(error) {
 					if (error) {
 						return OmUtil.error(error);
 					}
@@ -313,8 +311,11 @@ var VideoSettings = (function() {
 						if (error) {
 							return OmUtil.error('Error generating the offer');
 						}
-						offerSdp = _offerSdp;
-						_allowRec(true);
+						if (typeof(func) === 'function') {
+							func(_offerSdp, cnts);
+						} else {
+							_allowRec(true);
+						}
 					});
 				});
 		}
@@ -428,6 +429,18 @@ var VideoSettings = (function() {
 				if ('test' === m.mode) {
 					OmUtil.info('Received message: ', m);
 					switch (m.id) {
+						case 'canRecord':
+							_readValues(m, function(_offerSdp, cnts) {
+								OmUtil.info('Invoking SDP offer callback function');
+								OmUtil.sendMessage({
+									id : 'record'
+									, sdpOffer: _offerSdp
+									, video: cnts.video !== false
+									, audio: cnts.audio !== false
+								}, MsgBase);
+								rtcPeer.on('icecandidate', _onIceCandidate);
+							});
+							break;
 						case 'playResponse':
 						case 'startResponse':
 							OmUtil.log('SDP answer received from server. Processing ...');
diff --git a/openmeetings-web/src/main/webapp/WEB-INF/classes/applicationContext.xml b/openmeetings-web/src/main/webapp/WEB-INF/classes/applicationContext.xml
index 23ba20d..8199493 100644
--- a/openmeetings-web/src/main/webapp/WEB-INF/classes/applicationContext.xml
+++ b/openmeetings-web/src/main/webapp/WEB-INF/classes/applicationContext.xml
@@ -140,5 +140,12 @@
 
 	<!-- Kurento -->
 	<bean id="kurentoHandler" class="org.apache.openmeetings.core.remote.KurentoHandler" init-method="init" destroy-method="destroy"
-			p:kurentoWsUrl="ws://127.0.0.1:8888/kurento" p:checkTimeout="200"/>
+			p:kurentoWsUrl="ws://127.0.0.1:8888/kurento"
+			p:turnUrl=""
+			p:turnUser=""
+			p:turnSecret=""
+			p:turnMode="rest"
+			p:turnTtl="3600"
+			p:checkTimeout="200"
+			/>
 </beans>