Merge pull request #390 from pedrobmarin/merging-into-bbb-1.1

Remote tracking BBB master
This commit is contained in:
Pedro Beschorner Marin 2017-04-04 17:46:48 -03:00 committed by GitHub
commit ee099b8f0d
777 changed files with 30598 additions and 11396 deletions

2
.gitignore vendored
View File

@ -30,3 +30,5 @@ bigbluebutton-web/target-eclipse*
record-and-playback/.loadpath
clients/flash/**/build
clients/flash/**/.gradle
**/.idea/*
*.iml

View File

@ -1,7 +1,6 @@
package org.bigbluebutton.core.pubsub.receivers;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.common.messages.GetPresentationInfoMessage;
import org.bigbluebutton.common.messages.GetSlideInfoMessage;
@ -15,19 +14,18 @@ import org.bigbluebutton.common.messages.SendCursorUpdateMessage;
import org.bigbluebutton.common.messages.SendPageCountErrorMessage;
import org.bigbluebutton.common.messages.SendSlideGeneratedMessage;
import org.bigbluebutton.common.messages.SharePresentationMessage;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
import com.google.gson.JsonParser;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
public class PresentationMessageListener implements MessageHandler {
public static final String OFFICE_DOC_CONVERSION_SUCCESS_KEY = "OFFICE_DOC_CONVERSION_SUCCESS";
public static final String OFFICE_DOC_CONVERSION_FAILED_KEY = "OFFICE_DOC_CONVERSION_FAILED";
public static final String OFFICE_DOC_CONVERSION_INVALID_KEY = "OFFICE_DOC_CONVERSION_INVALID";
public static final String SUPPORTED_DOCUMENT_KEY = "SUPPORTED_DOCUMENT";
public static final String UNSUPPORTED_DOCUMENT_KEY = "UNSUPPORTED_DOCUMENT";
public static final String PAGE_COUNT_FAILED_KEY = "PAGE_COUNT_FAILED";
@ -145,13 +143,14 @@ public class PresentationMessageListener implements MessageHandler {
String conference = (String) map.get("conference");
String messageKey = (String) map.get("messageKey");
if (messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_SUCCESS_KEY) ||
messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_FAILED_KEY) ||
messageKey.equalsIgnoreCase(SUPPORTED_DOCUMENT_KEY) ||
messageKey.equalsIgnoreCase(UNSUPPORTED_DOCUMENT_KEY) ||
messageKey.equalsIgnoreCase(GENERATING_THUMBNAIL_KEY) ||
messageKey.equalsIgnoreCase(GENERATED_THUMBNAIL_KEY) ||
messageKey.equalsIgnoreCase(PAGE_COUNT_FAILED_KEY)){
if (messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_SUCCESS_KEY) ||
messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_FAILED_KEY) ||
messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_INVALID_KEY) ||
messageKey.equalsIgnoreCase(SUPPORTED_DOCUMENT_KEY) ||
messageKey.equalsIgnoreCase(UNSUPPORTED_DOCUMENT_KEY) ||
messageKey.equalsIgnoreCase(GENERATING_THUMBNAIL_KEY) ||
messageKey.equalsIgnoreCase(GENERATED_THUMBNAIL_KEY) ||
messageKey.equalsIgnoreCase(PAGE_COUNT_FAILED_KEY)){
sendConversionUpdate(messageKey, conference, code, presId, filename);
} else if(messageKey.equalsIgnoreCase(PAGE_COUNT_EXCEEDED_KEY)){

View File

@ -66,6 +66,7 @@ class BigBlueButtonInGW(
msg.payload.durationInMinutes,
msg.payload.autoStartRecording,
msg.payload.allowStartStopRecording,
msg.payload.webcamsOnlyForModerator,
msg.payload.moderatorPassword,
msg.payload.viewerPassword,
msg.payload.createTime,

View File

@ -8,8 +8,8 @@ import java.util.concurrent.TimeUnit
case object StopMeetingActor
case class MeetingProperties(meetingID: String, externalMeetingID: String, parentMeetingID: String, meetingName: String,
recorded: Boolean, voiceBridge: String, deskshareBridge: String, duration: Int,
autoStartRecording: Boolean, allowStartStopRecording: Boolean, moderatorPass: String,
viewerPass: String, createTime: Long, createDate: String,
autoStartRecording: Boolean, allowStartStopRecording: Boolean, webcamsOnlyForModerator: Boolean,
moderatorPass: String, viewerPass: String, createTime: Long, createDate: String,
red5DeskShareIP: String, red5DeskShareApp: String, isBreakout: Boolean, sequence: Int, metadata: java.util.Map[String, String])
case class MeetingExtensionProp(maxExtensions: Int = 2, numExtensions: Int = 0, extendByMinutes: Int = 20,

View File

@ -12,8 +12,8 @@ object Role extends Enumeration {
object Metadata extends Enumeration {
type Metadata = String
val INACTIVITY_DEADLINE = "mconf-live-inactivity-deadline"
val INACTIVITY_TIMELEFT = "mconf-live-inactivity-timeleft"
val INACTIVITY_DEADLINE = "inactivity-deadline"
val INACTIVITY_TIMELEFT = "inactivity-timeleft"
}
object GuestPolicy extends Enumeration {

View File

@ -88,8 +88,13 @@ trait PresentationApp {
}
def handleResizeAndMoveSlide(msg: ResizeAndMoveSlide) {
val page = presModel.resizePage(msg.xOffset, msg.yOffset,
msg.widthRatio, msg.heightRatio);
// Force coordinate that are out-of-bounds inside valid values
val xOffset = if (msg.xOffset <= 0) msg.xOffset else 0
val yOffset = if (msg.yOffset <= 0) msg.yOffset else 0
val width = if (msg.widthRatio <= 100) msg.widthRatio else 100
val height = if (msg.heightRatio <= 100) msg.heightRatio else 100
val page = presModel.resizePage(xOffset, yOffset, width, height);
page foreach (p => outGW.send(new ResizeAndMoveSlideOutMsg(mProps.meetingID, mProps.recorded, p)))
}

View File

@ -13,6 +13,7 @@ trait AppsTestFixtures {
val durationInMinutes = 10
val autoStartRecording = false
val allowStartStopRecording = false
val webcamsOnlyForModerator = false;
val moderatorPassword = "modpass"
val viewerPassword = "viewpass"
val createTime = System.currentTimeMillis
@ -25,7 +26,7 @@ trait AppsTestFixtures {
meetingName, record,
voiceConfId, deskshareConfId,
durationInMinutes,
autoStartRecording, allowStartStopRecording,
autoStartRecording, allowStartStopRecording, webcamsOnlyForModerator,
moderatorPassword, viewerPassword,
createTime, createDate, red5DeskShareIP, red5DeskShareApp,
isBreakout, sequence)

View File

@ -242,10 +242,8 @@ public String getJoinURL(String username, String meetingID, String record, Strin
}
//
// Create a meeting and return a URL to join it as moderator. This is used for the API demos.
// Create a meeting and return a URL to join it as attendee. This is used for the API demos.
//
// Passed
// - username
@ -253,19 +251,16 @@ public String getJoinURL(String username, String meetingID, String record, Strin
// - record ["true", "false"]
// - welcome message (null causes BigBlueButton to use the default welcome message
// - metadata (passed through when record="true"
// - xml (used for pre-upload of slides)_
// - xml (used for pre-upload of slides)
// - isModerator [true, false]
//
// Returned
// - valid join URL using the username
//
// Note this meeting will use username for meetingID
//
// VERSION ADJUSTED TO THE NEEDS OF THE HTML5 CLIENT
// -redirect=false //so that we get xml returned instead of being redirected to the meeting
// -password=ap //at this stage the html5 client is viewer only (Feb 2015)
public String getJoinURLHTML5(String username, String meetingID, String record, String welcome, Map<String, String> metadata, String xml) {
public String getJoinURLHTML5(String username, String meetingID, String record, String welcome, Map<String, String> metadata, String xml, boolean isModerator) {
String base_url_create = BigBlueButtonURL + "api/create?";
String base_url_join = BigBlueButtonURL + "api/join?";
@ -280,6 +275,13 @@ public String getJoinURLHTML5(String username, String meetingID, String record,
xml_param = xml;
}
String defaultModeratorPW = "mp";
String defaultAttendeePW = "ap";
String html5UserPassword = defaultAttendeePW; // default html5 user to attendee
if (isModerator) {
html5UserPassword = defaultModeratorPW;
}
Random random = new Random();
String voiceBridge_param = "&voiceBridge=" + (70000 + random.nextInt(9999));
@ -298,8 +300,11 @@ public String getJoinURLHTML5(String username, String meetingID, String record,
//
String create_parameters = "name=" + urlEncode(meetingID)
+ "&meetingID=" + urlEncode(meetingID) + welcome_param + voiceBridge_param
+ "&attendeePW=ap&moderatorPW=mp"
+ "&meetingID=" + urlEncode(meetingID)
+ welcome_param
+ voiceBridge_param
+ "&attendeePW=" + defaultAttendeePW
+ "&moderatorPW=" + defaultModeratorPW
+ "&record=" + record + getMetaData( metadata );
@ -322,8 +327,11 @@ public String getJoinURLHTML5(String username, String meetingID, String record,
// Looks good, now return a URL to join that meeting
//
// Note that REDIRECT=FALSE -- we will use the url to extract meetingID, userID, authToken
// and will pass them to the joining url for the html5 client (different format)
// Also we set PASSWORD=AP FOR ATTENDEE
String join_parameters = "meetingID=" + urlEncode(meetingID)
+ "&fullName=" + urlEncode(username) + "&redirect=false&password=ap"; //REDIRECT=FALSE (HTML5 CLIENT) PASSWORD=AP FOR ATTENDEE
+ "&fullName=" + urlEncode(username) + "&redirect=false&password=" + html5UserPassword;
return base_url_join + join_parameters + "&checksum="
+ checksum("join" + join_parameters + salt);

View File

@ -1,173 +0,0 @@
<!--
BigBlueButton - http://www.bigbluebutton.org
Copyright (c) 2008-2009 by respective authors (see below). All rights reserved.
BigBlueButton is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>.
Author: Fred Dixon <ffdixon@bigbluebutton.org>
-->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Record (Matterhorn)</title>
<style type="text/css">
#formcreate{
width:500px;
height:500px;
}
#formcreate ul{
list-style:none;
}
#formcreate li{
display:block;
width:400px;
margin-bottom:5px;
}
#formcreate label{
display:block;
float:left;
width:150px;
text-align:right;
}
#labdescription{
vertical-align:top;
}
</style>
</head>
<body>
<%@ include file="bbb_api.jsp"%>
<%@ include file="demo_header.jsp"%>
<%
if (request.getParameterMap().isEmpty()) {
//
// Assume we want to create a meeting
//
%>
<h2>Record (Matterhorn)</h2>
<form id="formcreate" name="formcreate" method="get" action="">
<fieldset>
<legend>Meeting Information</legend>
<ul>
<li>
<label for="confname">Meeting Name:</label>
<input id="confname" autofocus required name="confname" type="text" />
</li>
<li>
<label for="username1">Your Name:</label>
<input id="username1" required name="username1" type="text" />
</li>
</ul>
</fieldset>
<fieldset>
<legend>Metadata Details</legend>
<ul>
<li>
<label for="meta_title">Title:</label>
<input type="text" id="meta_title" name="meta_title" />
</li>
<li>
<label for="meta_subject">Subject:</label>
<input type="text" id="meta_subject" name="meta_subject" />
</li>
<li>
<label id="labdescription" for="meta_description">Description:</label>
<textarea id="meta_description" name="meta_description" cols="17" rows="3"></textarea>
</li>
<li>
<label for="meta_creator">Creator:</label>
<input type="text" id="meta_creator" name="meta_creator" />
</li>
<li>
<label for="meta_contributor">Contributor:</label>
<input type="text" id="meta_contributor" name="meta_contributor" />
</li>
<li>
<label for="meta_language">Language:</label>
<input type="text" id="meta_language" name="meta_language" />
</li>
<li>
<label for="meta_identifier">Identifier:</label>
<input type="text" id="meta_identifier" name="meta_identifier" />
</li>
</ul>
</fieldset>
<input type="submit" value="Create" >
<input type="hidden" name="action" value="create" />
</form>
<%
} else if (request.getParameter("action").equals("create")) {
String confname = request.getParameter("confname");
String username = request.getParameter("username1");
//metadata
Map<String,String> metadata=new HashMap<String,String>();
metadata.put("title",request.getParameter("meta_title"));
metadata.put("subject",request.getParameter("meta_subject"));
metadata.put("description",request.getParameter("meta_description"));
metadata.put("creator",request.getParameter("meta_creator"));
metadata.put("contributor",request.getParameter("meta_contributor"));
metadata.put("language",request.getParameter("meta_language"));
metadata.put("identifier",request.getParameter("meta_identifier"));
//
// This is the URL for to join the meeting as moderator
//
String url = BigBlueButtonURL.replace("bigbluebutton/","demo/");
String preUploadPDF = "<?xml version='1.0' encoding='UTF-8'?><modules><module name='presentation'><document url='"+url+"pdfs/matterhorn.pdf'/></module></modules>";
String joinURL = getJoinURL(username, confname, "true", null, metadata, preUploadPDF);
if (joinURL.startsWith("http://") || joinURL.startsWith("https://")) {
%>
<script language="javascript" type="text/javascript">
window.location.href="<%=joinURL%>";
</script>
<%
} else {
%>
Error: getJoinURL() failed
<p/>
<%=joinURL %>
<%
}
}
%>
<p>
See: <a href="http://code.google.com/p/bigbluebutton/wiki/MatterhornIntegration">Matterhorn Integration</a>
<%@ include file="demo_footer.jsp"%>
</body>
</html>

View File

@ -35,6 +35,8 @@ Author: Fred Dixon <ffdixon@bigbluebutton.org>
<body>
<p>You must have the BigBlueButton HTML5 client installed to use this API demo.</p>
<%@ include file="bbb_api.jsp"%>
<%
@ -51,25 +53,32 @@ if (request.getParameterMap().isEmpty()) {
<table cellpadding="5" cellspacing="5" style="width: 400px; ">
<tbody>
<tr>
<td>
&nbsp;</td>
<td style="text-align: right; ">
Full Name:</td>
<td style="width: 5px; ">
&nbsp;</td>
<td style="text-align: left ">
<input type="text" autofocus required name="username" /></td>
<td>&nbsp;</td>
<td style="text-align: right; ">Full Name:</td>
<td style="width: 5px; ">&nbsp;</td>
<td style="text-align: left "><input type="text" autofocus required name="username" /></td>
</tr>
<tr>
<td>&nbsp;</td>
<td style="text-align: right; ">Meeting Name:</td>
<td style="width: 5px; ">&nbsp;</td>
<td style="text-align: left "><input type="text" required name="meetingname" value="Demo Meeting" /></td>
<tr>
<tr>
<td>&nbsp;</td>
<td style="text-align: right; ">Moderator Role:</td>
<td style="width: 5px; ">&nbsp;</td>
<td style="text-align: left "><input type=checkbox name=isModerator value="true"></td>
<tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td><input type="submit" value="Join" /></td>
<tr>
<td>
&nbsp;</td>
<td>
&nbsp;</td>
<td>
&nbsp;</td>
<td>
<input type="submit" value="Join" /></td>
</tr>
</tbody>
</table>
<INPUT TYPE=hidden NAME=action VALUE="create">
@ -84,10 +93,10 @@ if (request.getParameterMap().isEmpty()) {
//
String username = request.getParameter("username");
String url = BigBlueButtonURL.replace("bigbluebutton/","demo/");
// String preUploadPDF = "<?xml version='1.0' encoding='UTF-8'?><modules><module name='presentation'><document url='"+url+"pdfs/sample.pdf'/></module></modules>";
String meetingname = request.getParameter("meetingname");
boolean isModerator = Boolean.parseBoolean(request.getParameter("isModerator"));
String joinURL = getJoinURLHTML5(request.getParameter("username"), "Demo Meeting", "false", null, null, null);
String joinURL = getJoinURLHTML5(username, meetingname, "false", null, null, null, isModerator);
Document doc = null;
doc = parseXml(getURL(joinURL));

View File

@ -7,7 +7,6 @@
<a href="demo8.jsp">Join & Upload</a> (URL) &nbsp;&nbsp;
<a href="demo10.jsp">Record</a> |
<a href="demo6.jsp">Record</a> (Matterhorn) &nbsp;&nbsp;
<a href="create.jsp">Create</a> &nbsp;&nbsp;
@ -15,12 +14,9 @@
<a href="demo4.jsp">Activity Monitor</a> &nbsp;&nbsp;
<a href="demo_mozilla_persona.jsp">Login with Persona</a> &nbsp;&nbsp;
<a href="demo_openid.jsp">Login with Openid</a> &nbsp;&nbsp;
<a href="demo11.jsp">Javascript API</a> &nbsp;&nbsp;
<a href="mobile.jsp">Mobile Demo</a> &nbsp;&nbsp;
<a href="demoHTML5.jsp">HTML5 Client Demo</a> &nbsp;&nbsp;

View File

@ -1,169 +0,0 @@
<!--
BigBlueButton - http://www.bigbluebutton.org
Copyright (c) 2008-2009 by respective authors (see below). All rights reserved.
BigBlueButton is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>.
Author: Marcos Calderon <mcmarkos86@gmail.com>
-->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Join Demo Meeting using Mozilla Persona</title>
<script src="https://login.persona.org/include.js" type="text/javascript"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<%@ include file="bbb_api.jsp"%>
<%@ page import="com.google.gson.Gson"%>
<%@ page import="com.google.gson.reflect.TypeToken"%>
<%
if (request.getParameterMap().isEmpty()) {
//
// Assume we want to create a meeting
//
%>
<%@ include file="demo_header.jsp"%>
<script type="text/javascript">
//browserid
$(function() {
$('#browserid').click(function() {
navigator.id.get(gotAssertion);
return false;
});
});
function gotAssertion(assertion) {
if (assertion) {
var assertion_field = document.getElementById("assertion-field");
assertion_field.value = assertion;
var login_form = document.getElementById("form1");
login_form.submit();
}
}
function loggedIn(res){
alert(res.email);
}
</script>
<h2>Join Demo Meeting using BrowserID (<a href="http://mozilla.org/persona/">Mozilla Persona</a>)</h2>
<FORM id="form1" NAME="form1" METHOD="GET">
<table cellpadding="5" cellspacing="5" style="width: 400px; ">
<tbody>
<tr>
<td>
&nbsp;</td>
<td style="text-align: left ">
<a href="#" id="browserid" title="Sign-in with BrowserID">
<img src="https://browserid.org/i/sign_in_blue.png" alt="Sign in">
</a>
</td>
</tr>
</tbody>
</table>
<INPUT TYPE=hidden NAME=action VALUE="create">
<input type="hidden" name="assertion" id="assertion-field" />
</FORM>
<%
} else if (request.getParameter("action").equals("create")) {
//
// Got an action=create
//
String url = BigBlueButtonURL.replace("bigbluebutton/","demo/");
String joinURL = "";
try{
String data = URLEncoder.encode("assertion", "UTF-8") + "=" + URLEncoder.encode(request.getParameter("assertion"), "UTF-8");
data += "&" + URLEncoder.encode("audience", "UTF-8") + "=" + URLEncoder.encode(BigBlueButtonURL.replace("/bigbluebutton/",""),"UTF-8");
URL urlBrowserID = new URL("https://verifier.login.persona.org/verify");
URLConnection conn = urlBrowserID.openConnection();
((HttpURLConnection)conn).setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String jsonResp = "";
String line;
while ((line = rd.readLine()) != null) {
jsonResp += line;
}
wr.close();
rd.close();
Gson gson = new Gson();
HashMap<String,String> map = gson.fromJson(jsonResp, new TypeToken<Map<String, String>>() {}.getType());
if(map.get("status").equalsIgnoreCase("okay")){
joinURL = getJoinURL(map.get("email"), "Demo Meeting", "false", null, null, null);
}
}catch(Exception e){
}
// String preUploadPDF = "<?xml version='1.0' encoding='UTF-8'?><modules><module name='presentation'><document url='"+url+"pdfs/sample.pdf'/></module></modules>";
if (joinURL.startsWith("http://") || joinURL.startsWith("https://")) {
%>
<script language="javascript" type="text/javascript">
window.location.href="<%=joinURL%>";
</script>
<%
} else {
%>
Error: getJoinURL() failed
<p/>
<%=joinURL %>
<%
}
}
%>
<%@ include file="demo_footer.jsp"%>
</body>
</html>

View File

@ -1,166 +0,0 @@
<!--
BigBlueButton - http://www.bigbluebutton.org
Copyright (c) 2008-2009 by respective authors (see below). All rights reserved.
BigBlueButton is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
Author: Jesus Federico <jesus@123it.ca>
-->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Join Demo Meeting using OpenID</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
var form = $('#form1');
$('#google').click(function() {
$("<input>").attr({
'type':'hidden',
'name':'connect',
'value':'google'
}).appendTo(form);
$('#form1').submit();
return false;
});
$('#yahoo').click(function() {
$("<input>").attr({
'type':'hidden',
'name':'connect',
'value':'yahoo'
}).appendTo(form);
$('#form1').submit();
return false;
});
$('#custom').click(function() {
$("<input>").attr({
'type':'hidden',
'name':'connect',
'value':'custom'
}).appendTo(form);
$('#form1').submit();
return false;
});
});
</script>
</head>
<body>
<%@ include file="bbb_api.jsp"%>
<%@ include file="bbb_jopenid.jsp"%>
<%
String urlPath = request.getRequestURI();
String urlHost = new URL(BigBlueButtonURL).getProtocol() + "://" + new URL(BigBlueButtonURL).getAuthority();
if (request.getParameterMap().isEmpty()) {
//
// Assume we want to create a meeting
//
%>
<%@ include file="demo_header.jsp"%>
<h2>Join Demo Meeting using openID</h2>
<FORM id="form1" NAME="form1" METHOD="GET" ACTION="#">
<table cellpadding="5" cellspacing="5" style="width: 400px; ">
<tbody>
<tr>
<td>&nbsp;</td>
<td style="text-align: left ">
<a href="#" id="yahoo" title="Sign-in with Yahoo OpenID">
<img src="images/yahoo.png" alt="Sign in"></br>
</a>
</td>
</tr>
<!--
<tr>
<td>&nbsp;</td>
<td style="text-align: left ">
<a href="#" id="custom" title="Sign-in with Custom OpenID">
<img src="images/openid.png" alt="Sign in"></br>
</a>
</td>
</tr>
-->
</tbody>
</table>
</FORM>
<%
} else if (request.getParameter("connect")!=null ) {
manager.setRealm(urlHost);
manager.setReturnTo(urlHost + urlPath);
Endpoint endpoint = null;
if (request.getParameter("connect").equals("google")) {
endpoint = manager.lookupEndpoint("Google");
} else if (request.getParameter("connect").equals("yahoo")) {
endpoint = manager.lookupEndpoint("Yahoo");
} else if (request.getParameter("connect").equals("custom")) {
endpoint = manager.lookupEndpoint("Google");
//endpoint = manager.lookupEndpoint("Custom");
}
Association association = manager.lookupAssociation(endpoint);
request.getSession().setAttribute(ATTR_MAC, association.getRawMacKey());
request.getSession().setAttribute(ATTR_ALIAS, endpoint.getAlias());
String url = manager.getAuthenticationUrl(endpoint, association);
response.sendRedirect(url);
} else if (request.getParameter("openid.ns")!=null && !request.getParameter("openid.ns").equals("")) {
byte[] mac_key = (byte[]) request.getSession().getAttribute(ATTR_MAC);
String alias = (String) request.getSession().getAttribute(ATTR_ALIAS);
Authentication authentication = manager.getAuthentication(request, mac_key, alias);
String joinURL = getJoinURL(authentication.getFullname(), "Demo Meeting", null, null, null, null );
if (joinURL.startsWith("http://") || joinURL.startsWith("https://")) {
%>
<script language="javascript" type="text/javascript">
window.location.href="<%=joinURL%>";
</script>
<%
} else {
%>
Error: getJoinURL() failed
<p/>
<%=joinURL %>
<%
}
}
%>
<%@ include file="demo_footer.jsp"%>
</body>
</html>

View File

@ -109,7 +109,7 @@ function createMeetingTable(meeting) {
form += '<input type="submit" value="End"/></FORM>';
form += '</th>';
var tableContent = '<table name="' + meeting.meetingID + '" class="hor-minimalist-b" cellspacing="0" summary="The current participants in a meeting"><caption>' + meeting.meetingID + '<caption><tr><th scope="col" abbr="Participants">Participants</th><th scope="col" abbr="Name">Name</th><th scope="col" abbr="Role">Role</th>';
var tableContent = '<table name="' + meeting.meetingID + '" class="hor-minimalist-b" cellspacing="0" summary="The current participants in a meeting"><caption>' + meeting.meetingName + '<caption><tr><th scope="col" abbr="Participants">Participants</th><th scope="col" abbr="Name">Name</th><th scope="col" abbr="Role">Role</th>';
//uncomment below to add the ability to end meetings in the activity monitor
//tableContent += form;
@ -158,3 +158,4 @@ function createMeetingTable(meeting) {
function encode(string) {
return hex_md5(string);
}

View File

@ -1,4 +1,4 @@
bbbsystemcheck.title = Mconf-Live Client Check
bbbsystemcheck.title = BigBlueButton Client Check
bbbsystemcheck.refresh = Refresh
bbbsystemcheck.mail = Mail
bbbsystemcheck.version = Client Check Version

View File

@ -1,4 +1,4 @@
bbbsystemcheck.title = Diagnóstico do cliente Mconf-Live
bbbsystemcheck.title = Diagnóstico do cliente BigBlueButton
bbbsystemcheck.refresh = Recarregar
bbbsystemcheck.mail = E-mail
bbbsystemcheck.version = Versão deste verificador

View File

@ -208,9 +208,14 @@
var swfObj = getSwfObj();
swfObj.webRTCEchoTest(success, errorcode);
if (callActive === true) {
leaveWebRTCVoiceConference();
}
webrtc_hangup(function() {
console.log("[BBBClientCheck] Handling webRTC hangup callback");
if (userAgent) {
var userAgentTemp = userAgent;
userAgent = null;
userAgentTemp.stop();
}
});
}
BBB.getMyUserInfo = function(callback) {
@ -221,7 +226,7 @@
myRole: "undefined",
amIPresenter: "undefined",
dialNumber: "undefined",
voiceBridge: "00000",
voiceBridge: "",
customdata: "undefined"
}
@ -229,42 +234,67 @@
}
// webrtc test callbacks
BBB.webRTCCallSucceeded = function() {
console.log("[BBBClientCheck] Handling webRTCCallSucceeded");
BBB.webRTCEchoTestFailed = function(errorcode) {
console.log("[BBBClientCheck] Handling webRTCEchoTestFailed");
sendWebRTCEchoTestAnswer(false, errorcode);
}
BBB.webRTCEchoTestEnded = function() {
console.log("[BBBClientCheck] Handling webRTCEchoTestEnded");
}
BBB.webRTCEchoTestStarted = function() {
console.log("[BBBClientCheck] Handling webRTCEchoTestStarted");
sendWebRTCEchoTestAnswer(true, 'Success');
}
BBB.webRTCEchoTestConnecting = function() {
console.log("[BBBClientCheck] Handling webRTCEchoTestConnecting");
}
BBB.webRTCEchoTestWaitingForICE = function() {
console.log("[BBBClientCheck] Handling webRTCEchoTestWaitingForICE");
}
BBB.webRTCEchoTestWebsocketSucceeded = function() {
console.log("[BBBClientCheck] Handling webRTCEchoTestWebsocketSucceeded");
var swfObj = getSwfObj();
swfObj.webRTCSocketTest(true, 'Connected');
}
BBB.webRTCCallFailed = function(inEchoTest, errorcode, cause) {
console.log("[BBBClientCheck] Handling webRTCCallFailed, errorcode " + errorcode + ", cause: " + cause);
if (errorcode == 1002) {
// failed to connect the websocket
var swfObj = getSwfObj();
swfObj.webRTCSocketTest(false, errorcode);
} else {
sendWebRTCEchoTestAnswer(false, errorcode);
}
BBB.webRTCEchoTestWebsocketFailed = function(errorcode) {
console.log("[BBBClientCheck] Handling webRTCEchoTestWebsocketFailed");
var swfObj = getSwfObj();
swfObj.webRTCSocketTest(false, errorcode);
}
BBB.webRTCCallEnded = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallEnded");
// webrtc callbacks
BBB.webRTCConferenceCallFailed = function(errorcode) {
console.log("[BBBClientCheck] Handling webRTCConferenceCallFailed");
}
BBB.webRTCCallStarted = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallStarted");
sendWebRTCEchoTestAnswer(true, 'Connected');
BBB.webRTCConferenceCallEnded = function() {
console.log("[BBBClientCheck] Handling webRTCConferenceCallEnded");
}
BBB.webRTCCallConnecting = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallConnecting");
BBB.webRTCConferenceCallStarted = function() {
console.log("[BBBClientCheck] Handling webRTCConferenceCallStarted");
}
BBB.webRTCCallWaitingForICE = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallWaitingForICE");
BBB.webRTCConferenceCallConnecting = function() {
console.log("[BBBClientCheck] Handling webRTCConferenceCallConnecting");
}
BBB.webRTCCallTransferring = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallTransferring");
BBB.webRTCConferenceCallWaitingForICE = function() {
console.log("[BBBClientCheck] Handling webRTCConferenceCallWaitingForICE");
}
BBB.webRTCConferenceCallWebsocketSucceeded = function() {
console.log("[BBBClientCheck] Handling webRTCConferenceCallWebsocketSucceeded");
}
BBB.webRTCConferenceCallWebsocketFailed = function(errorcode) {
console.log("[BBBClientCheck] Handling webRTCConferenceCallWebsocketFailed");
}
BBB.webRTCMediaRequest = function() {
@ -278,4 +308,36 @@
BBB.webRTCMediaFail = function() {
console.log("[BBBClientCheck] Handling webRTCMediaFail");
}
BBB.webRTCCallStarted = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallStarted");
BBB.webRTCEchoTestStarted();
};
BBB.webRTCCallConnecting = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallConnecting");
BBB.webRTCEchoTestWebsocketSucceeded();
};
BBB.webRTCCallEnded = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallEnded");
};
BBB.webRTCCallFailed = function(inEchoTest, errorcode, cause) {
console.log("[BBBClientCheck] Handling webRTCCallFailed");
BBB.webRTCEchoTestFailed(errorcode);
BBB.webRTCEchoTestWebsocketFailed();
};
BBB.webRTCCallWaitingForICE = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallWaitingForICE");
};
BBB.webRTCCallTransferring = function(inEchoTest) {
console.log("[BBBClientCheck] Handling webRTCCallTransferring");
};
BBB.webRTCCallProgressCallback = function(progress) {
console.log("[BBBClientCheck] Handling webRTCCallProgressCallback");
};
}

View File

@ -192,7 +192,7 @@ function createUAWithStuns(username, server, callback, stunsConfig, makeCallFunc
*/
var configuration = {
uri: 'sip:' + encodeURIComponent(username) + '@' + server,
wsServers: 'ws://' + server + '/ws',
wsServers: ('https:' == document.location.protocol ? 'wss://' : 'ws://') + server + '/ws',
displayName: username,
register: false,
traceSip: true,

View File

@ -32,6 +32,7 @@ public class Constants {
public static final String RECORDING = "recording";
public static final String AUTO_START_RECORDING = "auto_start_recording";
public static final String ALLOW_START_STOP_RECORDING = "allow_start_stop_recording";
public static final String WEBCAMS_ONLY_FOR_MODERATOR = "webcams_only_for_moderator";
public static final String LAYOUT_ID = "layout_id";
public static final String LISTENONLY = "listenOnly";
public static final String LISTEN_ONLY = "listen_only";

View File

@ -14,6 +14,7 @@ public class CreateMeetingMessage implements IBigBlueButtonMessage {
public final Long duration;
public final Boolean autoStartRecording;
public final Boolean allowStartStopRecording;
public final Boolean webcamsOnlyForModerator;
public final String moderatorPass;
public final String viewerPass;
public final Long createTime;
@ -22,8 +23,9 @@ public class CreateMeetingMessage implements IBigBlueButtonMessage {
public CreateMeetingMessage(String id, String externalId, String name, Boolean record, String voiceBridge,
Long duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass,
String viewerPass, Long createTime, String createDate, Map<String, String> metadata) {
Boolean allowStartStopRecording,Boolean webcamsOnlyForModerator,
String moderatorPass, String viewerPass,
Long createTime, String createDate, Map<String, String> metadata) {
this.id = id;
this.externalId = externalId;
this.name = name;
@ -32,6 +34,7 @@ public class CreateMeetingMessage implements IBigBlueButtonMessage {
this.duration = duration;
this.autoStartRecording = autoStartRecording;
this.allowStartStopRecording = allowStartStopRecording;
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
this.moderatorPass = moderatorPass;
this.viewerPass = viewerPass;
this.createTime = createTime;

View File

@ -63,6 +63,7 @@ public class MessageFromJsonConverter {
Long duration = payload.get(Constants.DURATION).getAsLong();
Boolean autoStartRecording = payload.get(Constants.AUTO_START_RECORDING).getAsBoolean();
Boolean allowStartStopRecording = payload.get(Constants.ALLOW_START_STOP_RECORDING).getAsBoolean();
Boolean webcamsOnlyForModerator = payload.get(Constants.WEBCAMS_ONLY_FOR_MODERATOR).getAsBoolean();
String moderatorPassword = payload.get(Constants.MODERATOR_PASS).getAsString();
String viewerPassword = payload.get(Constants.VIEWER_PASS).getAsString();
Long createTime = payload.get(Constants.CREATE_TIME).getAsLong();
@ -74,7 +75,8 @@ public class MessageFromJsonConverter {
return new CreateMeetingMessage(id, externalId, name, record, voiceBridge,
duration, autoStartRecording, allowStartStopRecording,
moderatorPassword, viewerPassword, createTime, createDate, metadata);
webcamsOnlyForModerator, moderatorPassword, viewerPassword,
createTime, createDate, metadata);
}
private static IBigBlueButtonMessage processDestroyMeeting(JsonObject payload) {

View File

@ -25,6 +25,7 @@ public class CreateMeetingRequest implements IBigBlueButtonMessage {
public final Integer durationInMinutes;
public final Boolean autoStartRecording;
public final Boolean allowStartStopRecording;
public final Boolean webcamsOnlyForModerator;
public final String moderatorPassword;
public final String viewerPassword;
public final Long createTime;
@ -37,9 +38,9 @@ public class CreateMeetingRequest implements IBigBlueButtonMessage {
String parentId, String name, Boolean record,
String voiceConfId, Integer duration,
Boolean autoStartRecording, Boolean allowStartStopRecording,
String moderatorPass, String viewerPass, Long createTime,
String createDate, Boolean isBreakout, Integer sequence,
Map<String, String> metadata) {
Boolean webcamsOnlyForModerator, String moderatorPass,
String viewerPass, Long createTime, String createDate,
Boolean isBreakout, Integer sequence, Map<String, String> metadata) {
this.id = id;
this.externalId = externalId;
this.parentId = parentId;
@ -49,6 +50,7 @@ public class CreateMeetingRequest implements IBigBlueButtonMessage {
this.durationInMinutes = duration;
this.autoStartRecording = autoStartRecording;
this.allowStartStopRecording = allowStartStopRecording;
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
this.moderatorPassword = moderatorPass;
this.viewerPassword = viewerPass;
this.createTime = createTime;

View File

@ -10,48 +10,50 @@ import org.junit.Test;
import com.google.gson.Gson;
public class CreateMeetingRequestTest {
@Test
public void testCreateMeetingRequest() {
String meetingId = "abc123";
String externalId = "extabc123";
String parentId = "";
Boolean record = false;
Integer durationInMinutes = 20;
String name = "Breakout room 1";
String voiceConfId = "851153";
Boolean autoStartRecording = false;
Boolean allowStartStopRecording = false;
Boolean isBreakout = true;
Integer sequence = 4;
String viewerPassword = "vp";
String moderatorPassword = "mp";
long createTime = System.currentTimeMillis();
String createDate = new Date(createTime).toString();
@Test
public void testCreateMeetingRequest() {
String meetingId = "abc123";
String externalId = "extabc123";
String parentId = "";
Boolean record = false;
Integer durationInMinutes = 20;
String name = "Breakout room 1";
String voiceConfId = "851153";
Boolean autoStartRecording = false;
Boolean allowStartStopRecording = false;
Boolean webcamsOnlyForModerator = false;
Boolean isBreakout = true;
Integer sequence = 4;
String viewerPassword = "vp";
String moderatorPassword = "mp";
long createTime = System.currentTimeMillis();
String createDate = new Date(createTime).toString();
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("meta_test", "test");
CreateMeetingRequestPayload payload = new CreateMeetingRequestPayload(
meetingId, externalId, parentId, name, record, voiceConfId,
durationInMinutes, autoStartRecording, allowStartStopRecording,
moderatorPassword, viewerPassword, createTime, createDate,
isBreakout, sequence, metadata);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
System.out.println(json);
webcamsOnlyForModerator, moderatorPassword, viewerPassword,
createTime, createDate, isBreakout, sequence, metadata);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
System.out.println(json);
CreateMeetingRequest rxMsg = gson.fromJson(json, CreateMeetingRequest.class);
CreateMeetingRequest rxMsg = gson.fromJson(json,
CreateMeetingRequest.class);
Assert.assertEquals(rxMsg.header.name, CreateMeetingRequest.NAME);
Assert.assertEquals(rxMsg.payload.id, meetingId);
Assert.assertEquals(rxMsg.payload.externalId, externalId);
Assert.assertEquals(rxMsg.payload.parentId, parentId);
Assert.assertEquals(rxMsg.payload.name, name);
Assert.assertEquals(rxMsg.payload.voiceConfId, voiceConfId);
Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword);
Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword);
Assert.assertEquals(rxMsg.payload.durationInMinutes, durationInMinutes);
Assert.assertEquals(rxMsg.payload.isBreakout, isBreakout);
Assert.assertEquals(rxMsg.payload.sequence, sequence);
}
Assert.assertEquals(rxMsg.header.name, CreateMeetingRequest.NAME);
Assert.assertEquals(rxMsg.payload.id, meetingId);
Assert.assertEquals(rxMsg.payload.externalId, externalId);
Assert.assertEquals(rxMsg.payload.parentId, parentId);
Assert.assertEquals(rxMsg.payload.name, name);
Assert.assertEquals(rxMsg.payload.voiceConfId, voiceConfId);
Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword);
Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword);
Assert.assertEquals(rxMsg.payload.durationInMinutes, durationInMinutes);
Assert.assertEquals(rxMsg.payload.isBreakout, isBreakout);
Assert.assertEquals(rxMsg.payload.sequence, sequence);
}
}

56
bbb-common-web/.gitignore vendored Normal file
View File

@ -0,0 +1,56 @@
.DS_Store
._.DS_Store*
.metadata
.project
.classpath
.settings
.history
.worksheet
gen
**/*.swp
**/*~.nib
**/build/
**/*.pbxuser
**/*.perspective
**/*.perspectivev3
*.xcworkspace
*.xcuserdatad
**/target
target
*.iml
project/*.ipr
project/*.iml
project/*.iws
project/out
project/*/target
project/target
project/*/bin
project/*/build
project/*.iml
project/*/*.iml
project/.idea
project/.idea/*
.idea
.idea/*
.idea/**/*
.DS_Store
project/.DS_Store
project/*/.DS_Store
tm.out
tmlog*.log
*.tm*.epoch
out/
provisioning/.vagrant
provisioning/*/.vagrant
provisioning/*/*.known
/sbt/akka-patterns-store/
/daemon/src/build/
*.lock
log/
tmp/
build/
akka-patterns-store/
lib_managed/
.cache
bin/

1
bbb-common-web/README.md Normal file
View File

@ -0,0 +1 @@
see http://code.google.com/p/bigbluebutton/wiki/DevelopingBBB

117
bbb-common-web/build.sbt Executable file
View File

@ -0,0 +1,117 @@
name := "bbb-common-web"
organization := "org.bigbluebutton"
version := "0.0.1-SNAPSHOT"
scalaVersion := "2.11.7"
scalacOptions ++= Seq(
"-unchecked",
"-deprecation",
"-Xlint",
"-Ywarn-dead-code",
"-language:_",
"-target:jvm-1.8",
"-encoding", "UTF-8"
)
// We want to have our jar files in lib_managed dir.
// This way we'll have the right path when we import
// into eclipse.
retrieveManaged := true
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", "junitxml")
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports")
val scalaV = "2.11.7"
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaV
libraryDependencies += "org.scala-lang" % "scala-library" % scalaV
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaV
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.5"
libraryDependencies += "commons-io" % "commons-io" % "2.4"
libraryDependencies += "org.freemarker" % "freemarker" % "2.3.23"
libraryDependencies += "com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % "2.6.3"
// https://mvnrepository.com/artifact/org.codehaus.woodstox/woodstox-core-asl
libraryDependencies += "org.codehaus.woodstox" % "woodstox-core-asl" % "4.4.1"
libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.5"
libraryDependencies += "org.pegdown" % "pegdown" % "1.4.0" % "test"
libraryDependencies += "junit" % "junit" % "4.12" % "test"
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
// https://mvnrepository.com/artifact/org.mockito/mockito-core
libraryDependencies += "org.mockito" % "mockito-core" % "2.7.12" % "test"
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.1" % "test"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test"
seq(Revolver.settings: _*)
//-----------
// Packaging
//
// Reference:
// http://xerial.org/blog/2014/03/24/sbt/
// http://www.scala-sbt.org/sbt-pgp/usage.html
// http://www.scala-sbt.org/0.13/docs/Using-Sonatype.html
// http://central.sonatype.org/pages/requirements.html
// http://central.sonatype.org/pages/releasing-the-deployment.html
//-----------
// Build pure Java lib (i.e. without scala)
// Do not append Scala versions to the generated artifacts
crossPaths := false
// This forbids including Scala related libraries into the dependency
autoScalaLibrary := false
/***************************
* When developing, change the version above to x.x.x-SNAPSHOT then use the file resolver to
* publish to the local maven repo using "sbt publish"
*/
// Uncomment this to publish to local maven repo while commenting out the nexus repo
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))
// Comment this out when publishing to local maven repo using SNAPSHOT version.
// To push to sonatype "sbt publishSigned"
//publishTo := {
// val nexus = "https://oss.sonatype.org/"
// if (isSnapshot.value)
// Some("snapshots" at nexus + "content/repositories/snapshots")
// else
// Some("releases" at nexus + "service/local/staging/deploy/maven2")
//}
// Enables publishing to maven repo
publishMavenStyle := true
publishArtifact in Test := false
pomIncludeRepository := { _ => false }
pomExtra := (
<scm>
<url>git@github.com:bigbluebutton/bigbluebutton.git</url>
<connection>scm:git:git@github.com:bigbluebutton/bigbluebutton.git</connection>
</scm>
<developers>
<developer>
<id>ritzalam</id>
<name>Richard Alam</name>
<url>http://www.bigbluebutton.org</url>
</developer>
</developers>)
licenses := Seq("LGPL-3.0" -> url("http://opensource.org/licenses/LGPL-3.0"))
homepage := Some(url("http://www.bigbluebutton.org"))

View File

View File

@ -0,0 +1 @@
sbt.version=0.13.8

View File

@ -0,0 +1,9 @@
addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.0")

View File

@ -33,8 +33,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.bigbluebutton.api.domain.Recording;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.api.domain.RecordingMetadata;
import org.bigbluebutton.api.util.RecordingMetadataReaderHelper;
// TODO: REVIEW THIS REDIS SERVICE
//import org.bigbluebutton.api.messaging.MessagingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -46,7 +50,7 @@ public class RecordingService {
private String unpublishedDir = "/var/bigbluebutton/unpublished";
private String deletedDir = "/var/bigbluebutton/deleted";
private RecordingServiceHelper recordingServiceHelper;
private MessagingService messagingService;
//private MessagingService messagingService;
private String recordStatusDir;
public void startIngestAndProcessing(String meetingId) {
@ -66,6 +70,38 @@ public class RecordingService {
}
}
public List<RecordingMetadata> getRecordingsMetadata(List<String> recordIDs, List<String> states) {
List<RecordingMetadata> recs = new ArrayList<RecordingMetadata>();
Map<String, List<File>> allDirectories = getAllDirectories(states);
if (recordIDs.isEmpty()) {
for (Map.Entry<String, List<File>> entry : allDirectories.entrySet()) {
recordIDs.addAll(getAllRecordingIds(entry.getValue()));
}
}
for (String recordID : recordIDs) {
for (Map.Entry<String, List<File>> entry : allDirectories.entrySet()) {
List<File> _recs = getRecordingsForPath(recordID, entry.getValue());
Iterator<File> iterator = _recs.iterator();
while (iterator.hasNext()) {
RecordingMetadata r = getRecordingMetadata(iterator.next());
if (r != null) {
recs.add(r);
}
}
}
}
return recs;
}
private static RecordingMetadata getRecordingMetadata(File dir) {
File file = new File(dir.getPath() + File.separatorChar + "metadata.xml");
RecordingMetadata rec = RecordingMetadataReaderHelper.getRecordingMetadata(file);
return rec;
}
public List<Recording> getRecordings(List<String> recordIDs, List<String> states) {
List<Recording> recs = new ArrayList<Recording>();
@ -121,6 +157,45 @@ public class RecordingService {
return matchesMetadata;
}
public boolean recordingMatchesMetadata(RecordingMetadata recording, Map<String, String> metadataFilters) {
boolean matchesMetadata = true;
for (Map.Entry<String, String> filter : metadataFilters.entrySet()) {
String metadataValue = recording.getMeta().get().get(filter.getKey());
if ( metadataValue == null ) {
// The recording doesn't have metadata specified
matchesMetadata = false;
} else {
String filterValue = filter.getValue();
if( filterValue.charAt(0) == '%' && filterValue.charAt(filterValue.length()-1) == '%' && metadataValue.contains(filterValue.substring(1, filterValue.length()-1)) ){
// Filter value embraced by two wild cards
// AND the filter value is part of the metadata value
} else if( filterValue.charAt(0) == '%' && metadataValue.endsWith(filterValue.substring(1, filterValue.length())) ) {
// Filter value starts with a wild cards
// AND the filter value ends with the metadata value
} else if( filterValue.charAt(filterValue.length()-1) == '%' && metadataValue.startsWith(filterValue.substring(0, filterValue.length()-1)) ) {
// Filter value ends with a wild cards
// AND the filter value starts with the metadata value
} else if( metadataValue.equals(filterValue) ) {
// Filter value doesnt have wildcards
// AND the filter value is the same as metadata value
} else {
matchesMetadata = false;
}
}
}
return matchesMetadata;
}
public List<RecordingMetadata> filterRecordingsByMetadata(List<RecordingMetadata> recordings, Map<String, String> metadataFilters) {
List<RecordingMetadata> resultRecordings = new ArrayList<RecordingMetadata>();
for (RecordingMetadata entry : recordings) {
if (recordingMatchesMetadata(entry, metadataFilters))
resultRecordings.add(entry);
}
return resultRecordings;
}
public Map<String, Recording> filterRecordingsByMetadata(Map<String, Recording> recordings, Map<String, String> metadataFilters) {
Map<String, Recording> resultRecordings = new HashMap<String, Recording>();
for (Map.Entry<String, Recording> entry : recordings.entrySet()) {
@ -191,7 +266,7 @@ public class RecordingService {
return rec;
}
private void deleteRecording(String id, String path) {
private static void deleteRecording(String id, String path) {
String[] format = getPlaybackFormats(path);
for (int i = 0; i < format.length; i++) {
List<File> recordings = getDirectories(path + File.separatorChar + format[i]);
@ -204,12 +279,12 @@ public class RecordingService {
}
}
private void createDirectory(File directory) {
private static void createDirectory(File directory) {
if (!directory.exists())
directory.mkdirs();
}
private void deleteDirectory(File directory) {
private static void deleteDirectory(File directory) {
/**
* Go through each directory and check if it's not empty. We need to
* delete files inside a directory before a directory can be deleted.
@ -226,7 +301,7 @@ public class RecordingService {
directory.delete();
}
private List<File> getDirectories(String path) {
private static List<File> getDirectories(String path) {
List<File> files = new ArrayList<File>();
try {
DirectoryStream<Path> stream = Files.newDirectoryStream(FileSystems.getDefault().getPath(path));
@ -242,7 +317,7 @@ public class RecordingService {
return files;
}
private String[] getPlaybackFormats(String path) {
private static String[] getPlaybackFormats(String path) {
List<File> dirs = getDirectories(path);
String[] formats = new String[dirs.size()];
@ -297,80 +372,121 @@ public class RecordingService {
return r;
}
public boolean changeState(String recordingId, String state) {
boolean anyResult = false;
public void changeState(String recordingId, String state) {
if (state.equals(Recording.STATE_PUBLISHED)) {
// It can only be published if it is unpublished
anyResult |= changeState(unpublishedDir, recordingId, state);
changeState(unpublishedDir, recordingId, state);
} else if (state.equals(Recording.STATE_UNPUBLISHED)) {
// It can only be unpublished if it is published
anyResult |= changeState(publishedDir, recordingId, state);
changeState(publishedDir, recordingId, state);
} else if (state.equals(Recording.STATE_DELETED)) {
// It can be deleted from any state
anyResult |= changeState(publishedDir, recordingId, state);
anyResult |= changeState(unpublishedDir, recordingId, state);
changeState(publishedDir, recordingId, state);
changeState(unpublishedDir, recordingId, state);
}
return anyResult;
}
private boolean changeState(String path, String recordingId, String state) {
private void changeState(String path, String recordingId, String state) {
String[] format = getPlaybackFormats(path);
boolean anyResult = false;
for (int i = 0; i < format.length; i++) {
List<File> recordings = getDirectories(path + File.separatorChar + format[i]);
for (int f = 0; f < recordings.size(); f++) {
if (recordings.get(f).getName().equalsIgnoreCase(recordingId)) {
Recording r = getRecordingInfo(recordings.get(f));
if (r != null) {
File dest;
if (state.equals(Recording.STATE_PUBLISHED)) {
dest = new File(publishedDir + File.separatorChar + format[i]);
} else if (state.equals(Recording.STATE_UNPUBLISHED)) {
dest = new File(unpublishedDir + File.separatorChar + format[i]);
} else if (state.equals(Recording.STATE_DELETED)) {
dest = new File(deletedDir + File.separatorChar + format[i]);
} else {
log.debug(String.format("State: %s, is not supported", state));
return anyResult;
}
if (!dest.exists())
dest.mkdirs();
boolean moved = recordings.get(f).renameTo(new File(dest, recordings.get(f).getName()));
if (moved) {
log.debug("Recording successfully moved!");
r.setState(state);
r.setPublished(state.equals(Recording.STATE_PUBLISHED));
if (state.equals(Recording.STATE_DELETED)) {
r.setPlaybackFormat(null);
deleteRecording(recordingId, deletedDir);
}
recordingServiceHelper.writeRecordingInfo(dest.getAbsolutePath() + File.separatorChar + recordings.get(f).getName(), r);
sendRedisEvent(r.getId(), r.getId(), r.getExternalMeetingId(), format[i], state);
log.debug(String.format("Recording successfully %s!", state));
} else {
log.debug("Recording was not moved");
}
anyResult |= moved;
File dest;
if (state.equals(Recording.STATE_PUBLISHED)) {
dest = new File(publishedDir + File.separatorChar + format[i]);
RecordingService.publishRecording(dest, recordingId, recordings.get(f), format[i]);
} else if (state.equals(Recording.STATE_UNPUBLISHED)) {
dest = new File(unpublishedDir + File.separatorChar + format[i]);
RecordingService.unpublishRecording(dest, recordingId, recordings.get(f), format[i]);
} else if (state.equals(Recording.STATE_DELETED)) {
dest = new File(deletedDir + File.separatorChar + format[i]);
RecordingService.deleteRecording(dest, recordingId, recordings.get(f), format[i]);
} else {
log.debug(String.format("State: %s, is not supported", state));
return;
}
}
}
}
return anyResult;
}
private void sendRedisEvent(String recordId, String meetingId, String externalMeetingId, String format, String state) {
log.debug("Sending Redis event for meeting {} {}", meetingId, format);
if (state.equals(Recording.STATE_PUBLISHED)) {
messagingService.publishRecording(recordId, meetingId, externalMeetingId, format, true);
} else if (state.equals(Recording.STATE_UNPUBLISHED)) {
messagingService.publishRecording(recordId, meetingId, externalMeetingId, format, false);
} else if (state.equals(Recording.STATE_DELETED)) {
messagingService.deleteRecording(recordId, meetingId, externalMeetingId, format);
} else {
log.debug("No event for {}", state);
public static void publishRecording(File destDir, String recordingId, File recordingDir, String format) {
File metadataXml = RecordingMetadataReaderHelper.getMetadataXmlLocation(recordingDir.getPath());
RecordingMetadata r = RecordingMetadataReaderHelper.getRecordingMetadata(metadataXml);
if (r != null) {
if (!destDir.exists()) destDir.mkdirs();
try {
FileUtils.moveDirectory(recordingDir, new File(destDir.getPath() + File.separatorChar + recordingId));
r.setState(Recording.STATE_PUBLISHED);
r.setPublished(true);
File medataXmlFile = RecordingMetadataReaderHelper.getMetadataXmlLocation(
destDir.getAbsolutePath() + File.separatorChar + recordingId);
// Process the changes by saving the recording into metadata.xml
RecordingMetadataReaderHelper.saveRecordingMetadata(medataXmlFile, r);
//Recording rec = recordingServiceHelper.getRecordingInfo(recordingDir);
//messagingService.publishRecording(rec.getId(), rec.getId(), rec.getExternalMeetingId(), format, true);
} catch (IOException e) {
log.error("Failed to publish recording : " + recordingId, e);
}
}
}
public static void unpublishRecording(File destDir, String recordingId, File recordingDir, String format) {
File metadataXml = RecordingMetadataReaderHelper.getMetadataXmlLocation(recordingDir.getPath());
RecordingMetadata r = RecordingMetadataReaderHelper.getRecordingMetadata(metadataXml);
if (r != null) {
if (!destDir.exists()) destDir.mkdirs();
try {
FileUtils.moveDirectory(recordingDir, new File(destDir.getPath() + File.separatorChar + recordingId));
r.setState(Recording.STATE_UNPUBLISHED);
r.setPublished(false);
File medataXmlFile = RecordingMetadataReaderHelper.getMetadataXmlLocation(
destDir.getAbsolutePath() + File.separatorChar + recordingId);
// Process the changes by saving the recording into metadata.xml
RecordingMetadataReaderHelper.saveRecordingMetadata(medataXmlFile, r);
//Recording rec = recordingServiceHelper.getRecordingInfo(recordingDir);
//messagingService.publishRecording(rec.getId(), rec.getId(), rec.getExternalMeetingId(), format, false);
} catch (IOException e) {
log.error("Failed to unpublish recording : " + recordingId, e);
}
}
}
public static void deleteRecording(File destDir, String recordingId, File recordingDir, String format) {
File metadataXml = RecordingMetadataReaderHelper.getMetadataXmlLocation(recordingDir.getPath());
RecordingMetadata r = RecordingMetadataReaderHelper.getRecordingMetadata(metadataXml);
if (r != null) {
if (!destDir.exists()) destDir.mkdirs();
try {
FileUtils.moveDirectory(recordingDir, new File(destDir.getPath() + File.separatorChar + recordingId));
r.setState(Recording.STATE_DELETED);
r.setPublished(false);
File medataXmlFile = RecordingMetadataReaderHelper.getMetadataXmlLocation(
destDir.getAbsolutePath() + File.separatorChar + recordingId);
// Process the changes by saving the recording into metadata.xml
RecordingMetadataReaderHelper.saveRecordingMetadata(medataXmlFile, r);
//Recording rec = recordingServiceHelper.getRecordingInfo(recordingDir);
//messagingService.deleteRecording(rec.getId(), rec.getId(), rec.getExternalMeetingId(), format);
} catch (IOException e) {
log.error("Failed to delete recording : " + recordingId, e);
}
}
}
private List<File> getAllDirectories(String state) {
List<File> allDirectories = new ArrayList<File>();
@ -436,22 +552,8 @@ public class RecordingService {
Map<String,File> recsIndexed = indexRecordings(recs);
if ( recsIndexed.containsKey(recordID) ) {
File recFile = recsIndexed.get(recordID);
Recording rec = getRecordingInfo(recFile);
if (rec != null) {
for (Map.Entry<String,String> meta : metaParams.entrySet()) {
if ( !"".equals(meta.getValue()) ) {
// As it has a value, if the meta parameter exists update it, otherwise add it
rec.updateMetadata(meta.getKey(), meta.getValue());
} else {
// As it doesn't have a value, if it exists delete it
if ( rec.containsMetadata(meta.getKey()) ) {
rec.deleteMetadata(meta.getKey());
}
}
}
// Process the changes by saving the recording into metadata.xml
recordingServiceHelper.writeRecordingInfo(recFile.getAbsolutePath(), rec);
}
File metadataXml = RecordingMetadataReaderHelper.getMetadataXmlLocation(recFile.getPath());
updateRecordingMetadata(metadataXml, metaParams, metadataXml);
}
}
}
@ -459,6 +561,27 @@ public class RecordingService {
return;
}
public static void updateRecordingMetadata(File srxMetadataXml, Map<String,String> metaParams, File destMetadataXml) {
RecordingMetadata rec = RecordingMetadataReaderHelper.getRecordingMetadata(srxMetadataXml);
if (rec != null && rec.getMeta() != null) {
for (Map.Entry<String,String> meta : metaParams.entrySet()) {
if ( !"".equals(meta.getValue()) ) {
// As it has a value, if the meta parameter exists update it, otherwise add it
rec.getMeta().set(meta.getKey(), meta.getValue());
} else {
// As it doesn't have a value, if it exists delete it
if ( rec.getMeta().containsKey(meta.getKey()) ) {
rec.getMeta().remove(meta.getKey());
}
}
}
// Process the changes by saving the recording into metadata.xml
RecordingMetadataReaderHelper.saveRecordingMetadata(destMetadataXml, rec);
}
}
private Map<String,File> indexRecordings(List<File> recs) {
Map<String,File> indexedRecs = new HashMap<String,File>();
@ -492,7 +615,7 @@ public class RecordingService {
return baseDir;
}
public void setMessagingService(MessagingService service) {
messagingService = service;
}
//public void setMessagingService(MessagingService service) {
// messagingService = service;
//}
}

View File

@ -0,0 +1,44 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
public class Breakout {
/**
* <breakout parentMeetingId="f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374" sequence="2" meetingId="f2041d123b6a4b994e7ad87ee9d348496a73472c-1489173065780"/>
*/
@JacksonXmlProperty(isAttribute = true)
private String parentMeetingId = "";
@JacksonXmlProperty(isAttribute = true)
private int sequence = 0;
@JacksonXmlProperty(isAttribute = true)
private String meetingId = "";
public void setParentMeetingId(String parentMeetingId) {
this.parentMeetingId = parentMeetingId;
}
public String getParentMeetingId() {
return parentMeetingId;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public int getSequence() {
return sequence;
}
public void setMeetingId(String meetingId) {
this.meetingId = meetingId;
}
public String getMeetingId() {
return meetingId;
}
}

View File

@ -0,0 +1,18 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
public class BreakoutRoom {
@JacksonXmlText
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,54 @@
package org.bigbluebutton.api.domain;
public class Download {
private String format;
private String url;
private int length;
private String md5;
private String key;
private String size;
public Download(String format, String url, String md5, String key, String size, int length) {
this.format = format;
this.url = url;
this.length = length;
this.md5 = md5;
this.size = size;
this.key = key;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getMd5() {
return md5;
}
public void setSize(String size) {
this.size = size;
}
public String getSize() {
return size;
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
}

View File

@ -0,0 +1,21 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
/**
* Created by ritz on 2017-03-11.
*/
public class Extensions {
@JacksonXmlProperty(localName = "preview")
@JacksonXmlElementWrapper(useWrapping = false)
private Preview preview;
public void setPreview(Preview preview) {
this.preview = preview;
}
public Preview getPreview() {
return preview;
}
}

View File

@ -0,0 +1,59 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
public class Image {
/**
* <image width="176" height="136" alt="Welcome to">
* http://192.168.23.22/presentation/32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780/presentation/743dd59a958334b4cdcdaa302846d0c0eadcf9ff-1489173070800/thumbnails/thumb-1.png
* </image>
*/
@JacksonXmlProperty(isAttribute = true)
private String width;
@JacksonXmlProperty(isAttribute = true)
private String height;
@JacksonXmlProperty(isAttribute = true)
private String alt;
@JacksonXmlText
private String value;
public void setWidth(String width) {
this.width = width;
}
public String getWidth() {
return width;
}
public void setHeight(String height) {
this.height = height;
}
public String getHeight() {
return height;
}
public void setAlt(String alt) {
this.alt = alt;
}
public String getAlt() {
return alt;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -19,19 +19,13 @@
package org.bigbluebutton.api.domain;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Meeting {
private static Logger log = LoggerFactory.getLogger(Meeting.class);
private static final long MILLIS_IN_A_MINUTE = 60000;
@ -57,6 +51,7 @@ public class Meeting {
private boolean record;
private boolean autoStartRecording = false;
private boolean allowStartStopRecording = false;
private boolean webcamsOnlyForModerator = false;
private String dialNumber;
private String defaultAvatarURL;
private String defaultConfigToken;
@ -67,6 +62,7 @@ public class Meeting {
private final ConcurrentMap<String, Long> registeredUsers;
private final ConcurrentMap<String, Config> configs;
private final Boolean isBreakout;
private final List<String> breakoutRooms = new ArrayList();
private long lastUserLeftOn = 0;
@ -82,6 +78,7 @@ public class Meeting {
record = builder.record;
autoStartRecording = builder.autoStartRecording;
allowStartStopRecording = builder.allowStartStopRecording;
webcamsOnlyForModerator = builder.webcamsOnlyForModerator;
duration = builder.duration;
webVoice = builder.webVoice;
telVoice = builder.telVoice;
@ -100,6 +97,14 @@ public class Meeting {
configs = new ConcurrentHashMap<String, Config>();
}
public void addBreakoutRoom(String meetingId) {
breakoutRooms.add(meetingId);
}
public List<String> getBreakoutRooms() {
return breakoutRooms;
}
public String storeConfig(boolean defaultConfig, String config) {
String token = RandomStringUtils.randomAlphanumeric(8);
while (configs.containsKey(token)) {
@ -267,6 +272,10 @@ public class Meeting {
return allowStartStopRecording;
}
public boolean getWebcamsOnlyForModerator() {
return webcamsOnlyForModerator;
}
public boolean hasUserJoined() {
return userHasJoined;
}
@ -397,7 +406,8 @@ public class Meeting {
private int maxUsers;
private boolean record;
private boolean autoStartRecording;
private boolean allowStartStopRecording;
private boolean allowStartStopRecording;
private boolean webcamsOnlyForModerator;
private String moderatorPass;
private String viewerPass;
private int duration;
@ -448,6 +458,11 @@ public class Meeting {
return this;
}
public Builder withWebcamsOnlyForModerator(boolean only) {
this.webcamsOnlyForModerator = only;
return this;
}
public Builder withWebVoice(String w) {
this.webVoice = w;
return this;

View File

@ -0,0 +1,53 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public class MeetingInfo {
/**
* <meeting id="random-2810069" name="random-2810069" breakout="false"/>
*/
@JacksonXmlProperty(isAttribute = true)
private String id;
@JacksonXmlProperty(isAttribute = true)
private String externalId;
@JacksonXmlProperty(isAttribute = true)
private String name;
@JacksonXmlProperty(isAttribute = true)
private boolean breakout;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public String getExternalId() {
return externalId;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setBreakout(boolean breakout) {
this.breakout = breakout;
}
public boolean isBreakout() {
return breakout;
}
}

View File

@ -0,0 +1,31 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.Map;
import java.util.TreeMap;
@JacksonXmlRootElement(localName = "meta")
public class Metadata {
private Map<String,String> map = new TreeMap<String,String>();
@JsonAnyGetter
public Map<String, String> get() {
return map;
}
@JsonAnySetter
public void set(String name, String value) {
map.put(name, value);
}
public void remove(String key) {
map.remove(key);
}
public boolean containsKey(String key) {
return map.containsKey(key);
}
}

View File

@ -0,0 +1,18 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public class Preview {
@JacksonXmlElementWrapper(localName = "images")
@JacksonXmlProperty(localName = "image")
private Image[] images;
public void setImages(Image[] images) {
this.images = images;
}
public Image[] getImages() {
return images;
}
}

View File

@ -35,6 +35,7 @@ public class Recording {
private boolean published;
private String startTime;
private String endTime;
private String numParticipants;
private String rawSize;
private Map<String, String> metadata = new TreeMap<String, String>();
private List<Playback> playbacks=new ArrayList<Playback>();
@ -104,6 +105,14 @@ public class Recording {
return endTime;
}
public void setNumParticipants(String numParticipants) {
this.numParticipants = numParticipants;
}
public String getNumParticipants() {
return numParticipants;
}
public void setEndTime(String endTime) {
this.endTime = convertOldDateFormat(endTime);
}

View File

@ -0,0 +1,207 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.Map;
import java.util.TreeMap;
@JacksonXmlRootElement(localName = "recording")
public class RecordingMetadata {
/**
* <recording>
<id>32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780</id>
<state>published</state>
<published>true</published>
<start_time>1489173065780</start_time>
<end_time>1489173199386</end_time>
<breakout parentMeetingId="f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374" sequence="2" meetingId="f2041d123b6a4b994e7ad87ee9d348496a73472c-1489173065780"/>
<meta>
<meetingId>f2041d123b6a4b994e7ad87ee9d348496a73472c-1489173065780</meetingId>
<meetingName>random-2810069 (Room - 2)</meetingName>
<isBreakout>true</isBreakout>
</meta>
<playback>
<format>presentation</format>
<link>http://192.168.23.22/playback/presentation/0.9.0/playback.html?meetingId=32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780</link>
<processing_time>9841</processing_time>
<duration>126376</duration>
<extensions>
<preview>
<images>
<image width="176" height="136" alt="Welcome to">http://192.168.23.22/presentation/32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780/presentation/743dd59a958334b4cdcdaa302846d0c0eadcf9ff-1489173070800/thumbnails/thumb-1.png</image>
</images>
</preview>
</extensions>
</playback>
</recording>
*/
private String metadataXml;
private Boolean processingError = false;
private String id;
private String state;
private boolean published;
@JacksonXmlProperty(localName = "start_time")
private String startTime;
@JacksonXmlProperty(localName = "end_time")
private String endTime;
@JacksonXmlProperty(localName = "participants")
private int participants;
@JacksonXmlProperty(localName = "meeting")
private MeetingInfo meetingInfo;
private String meetingId = "";
private String meetingName = "";
private Breakout breakout;
@JacksonXmlElementWrapper(localName = "breakoutRooms")
@JacksonXmlProperty(localName = "breakoutRoom")
private BreakoutRoom[] breakoutRooms;
private Metadata meta;
private RecordingMetadataPlayback playback;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public String getMeetingId() {
MeetingInfo info = getMeeting();
if (info == null) {
// return the recording id
return id;
}
return info.getId();
}
public String getMeetingName() {
MeetingInfo info = getMeeting();
if (info == null) {
return getMeta().get().get("meetingName");
}
return info.getName();
}
public Boolean isBreakout() {
MeetingInfo info = getMeeting();
if (info == null) {
return Boolean.parseBoolean(getMeta().get().get("isBreakout"));
}
return info.isBreakout();
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setPublished(boolean published) {
this.published = published;
}
public boolean getPublished() {
return published;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getStartTime() {
return startTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
public String getEndTime() {
return endTime;
}
public void setParticipants(int participants) {
this.participants = participants;
}
public int getParticipants() {
return participants;
}
public void setMeeting(MeetingInfo meetingInfo) {
this.meetingInfo = meetingInfo;
}
public MeetingInfo getMeeting() {
return meetingInfo;
}
public void setBreakout(Breakout breakout) {
this.breakout = breakout;
}
public Breakout getBreakout() {
return breakout;
}
public void setBreakoutRooms(BreakoutRoom[] breakoutRooms) {
this.breakoutRooms = breakoutRooms;
}
public BreakoutRoom[] getBreakoutRooms() {
return breakoutRooms;
}
public void setMeta(Metadata meta) {
this.meta = meta;
}
public Metadata getMeta() {
return meta;
}
public RecordingMetadataPlayback getPlayback() {
return playback;
}
public void setMetadataXml(String metadataXml) {
this.metadataXml = metadataXml;
}
public String getMetadataXml() {
return metadataXml;
}
public void setProcessingError(Boolean error) {
processingError = error;
}
public Boolean hasError() {
return processingError;
}
public Integer calculateDuration() {
if ((endTime == null) || (endTime == "") || (startTime == null) || (startTime == "")) return 0;
int start = (int) Math.ceil((Long.parseLong(startTime)) / 60000.0);
int end = (int) Math.ceil((Long.parseLong(endTime)) / 60000.0);
return end - start;
}
}

View File

@ -0,0 +1,66 @@
package org.bigbluebutton.api.domain;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "playback")
public class RecordingMetadataPlayback {
private String format;
private String link;
@JacksonXmlProperty(localName = "processing_time")
private Long processingTime = 0L;
private Long duration = 0L;
private Extensions extensions;
public void setFormat(String format) {
this.format = format;
}
public String getFormat() {
return format;
}
public void setLink(String link) {
this.link = link;
}
public String getLink() {
return link;
}
public void setProcessingTime(Long processingTime) {
this.processingTime = processingTime;
}
public Long getProcessingTime() {
return processingTime;
}
public void setDuration(Long duration) {
this.duration = duration;
}
public Long getDuration() {
return duration;
}
public Long calculateDuration() {
if (duration > 0) {
// convert to minutes
return duration / 60000;
} else {
return 0L;
}
}
public void setExtensions(Extensions extensions) {
this.extensions = extensions;
}
public Extensions getExtensions() {
return extensions;
}
}

View File

@ -0,0 +1,23 @@
package org.bigbluebutton.api.util;
import org.bigbluebutton.api.domain.Meeting;
public class MeetingResponseDetail {
private final String createdOn;
private final Meeting meeting;
public MeetingResponseDetail(String createdOn, Meeting meeting) {
this.createdOn = createdOn;
this.meeting = meeting;
}
public String getCreatedOn() {
return createdOn;
}
public Meeting getMeeting() {
return meeting;
}
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.api.util;
import java.util.Collection;
public class MeetingsResponse {
public final Collection<MeetingResponseDetail> meetings;
public MeetingsResponse(Collection<MeetingResponseDetail> meetings) {
this.meetings = meetings;
}
}

View File

@ -0,0 +1,26 @@
package org.bigbluebutton.api.util;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ParamsUtil {
private static final Pattern VALID_ID_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9- ]*$");
public static final String invalidChars = ",";
public static String stripControlChars(String text) {
return text.replaceAll("\\p{Cc}", "");
}
public static boolean isValidMeetingId(String meetingId) {
//return VALID_ID_PATTERN.matcher(meetingId).matches();
return !containsChar(meetingId, invalidChars);
}
public static boolean containsChar(String text, String chars) {
return StringUtils.containsAny(text, chars);
}
}

View File

@ -0,0 +1,84 @@
package org.bigbluebutton.api.util;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.bigbluebutton.api.domain.RecordingMetadata;
import com.fasterxml.jackson.databind.SerializationFeature;
import javax.xml.stream.*;
import java.io.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RecordingMetadataReaderHelper {
private static Logger log = LoggerFactory.getLogger(RecordingMetadataReaderHelper.class);
public static String inputStreamToString(InputStream is) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb.toString();
}
public static RecordingMetadata getRecordingMetadata(File metadataXml) {
XMLInputFactory factory = XMLInputFactory.newInstance();
JacksonXmlModule module = new JacksonXmlModule();
// and then configure, for example:
module.setDefaultUseWrapper(false);
XmlMapper mapper = new XmlMapper(module);
//Reading from xml file and creating XMLStreamReader
XMLStreamReader reader = null;
RecordingMetadata recMeta = null;
try {
reader = factory.createXMLStreamReader(new FileInputStream(metadataXml));
recMeta = mapper.readValue(reader, RecordingMetadata.class);
recMeta.setMetadataXml(metadataXml.getParentFile().getName());
} catch (XMLStreamException e) {
log.error("Failed to read metadata xml for recording: " + metadataXml.getAbsolutePath(), e);
} catch (FileNotFoundException e) {
log.error("File not found: " + metadataXml.getAbsolutePath(), e);
} catch (IOException e) {
log.error("IOException on " + metadataXml.getAbsolutePath(), e);
}
if (recMeta == null) {
recMeta = new RecordingMetadata();
recMeta.setMetadataXml(metadataXml.getParentFile().getName());
recMeta.setProcessingError(true);
}
return recMeta;
}
public static File getMetadataXmlLocation(String destDir) {
return new File(destDir + File.separatorChar + "metadata.xml");
}
public static void saveRecordingMetadata(File metadataXml, RecordingMetadata recordingMetadata) {
//XMLOutputFactory factory = XMLOutputFactory.newInstance();
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
XmlMapper mapper = new XmlMapper(module);
//Reading from xml file and creating XMLStreamReader
//XMLStreamWriter writer = null;
try {
//writer = factory.createXMLStreamWriter(new FileOutputStream(metadataXml));
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.writeValue(metadataXml, recordingMetadata);
} catch (FileNotFoundException e) {
log.error("File not found: " + metadataXml.getAbsolutePath(), e);
} catch (IOException e) {
log.error("IOException on " + metadataXml.getAbsolutePath(), e);
}
}
}

View File

@ -0,0 +1,124 @@
package org.bigbluebutton.api.util;
import org.bigbluebutton.api.domain.Meeting;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import freemarker.template.*;
import org.bigbluebutton.api.domain.RecordingMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ResponseBuilder {
private static Logger log = LoggerFactory.getLogger(ResponseBuilder.class);
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
public ResponseBuilder(File templatesLoc) {
try {
cfg.setDirectoryForTemplateLoading(templatesLoc);
} catch (IOException e) {
e.printStackTrace();
}
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
}
public String formatPrettyDate(Long timestamp) {
return new Date(timestamp).toString();
}
public String buildGetMeetingInfoResponse(Meeting meeting, String returnCode) {
String createdOn = formatPrettyDate(meeting.getCreateTime());
Template ftl = null;
try {
ftl = cfg.getTemplate("get-meeting-info.ftlx");
} catch (IOException e) {
log.error("Cannot find get-meeting-info.ftl template for meeting : " + meeting.getInternalId(), e);
}
StringWriter xmlText = new StringWriter();
Map root = new HashMap();
root.put("returnCode", returnCode);
root.put("createdOn", createdOn);
root.put("meeting", meeting);
try {
ftl.process(root, xmlText);
} catch (TemplateException e) {
log.error("Template exception for meeting : " + meeting.getInternalId(), e);
} catch (IOException e) {
log.error("IO exception for meeting : " + meeting.getInternalId(), e);
}
return xmlText.toString();
}
public String buildGetMeetingsResponse(Collection<Meeting> meetings, String returnCode) {
ArrayList<MeetingResponseDetail> meetingResponseDetails = new ArrayList<MeetingResponseDetail>();
for (Meeting meeting : meetings) {
String createdOn = formatPrettyDate(meeting.getCreateTime());
MeetingResponseDetail details = new MeetingResponseDetail(createdOn, meeting);
meetingResponseDetails.add(details);
}
Template ftl = null;
try {
ftl = cfg.getTemplate("get-meetings.ftlx");
} catch (IOException e) {
log.error("IO exception for get-meetings.ftlx : ", e);
}
StringWriter xmlText = new StringWriter();
Map root = new HashMap();
root.put("returnCode", returnCode);
root.put("meetingDetailsList", meetingResponseDetails);
try {
ftl.process(root, xmlText);
} catch (TemplateException e) {
log.error("Template exception : ", e);
} catch (IOException e) {
log.error("IO exception for get-meetings.ftlx : ", e);
}
return xmlText.toString();
}
public String buildGetRecordingsResponse(List<RecordingMetadata> recordings, String returnCode) {
Template ftl = null;
try {
ftl = cfg.getTemplate("get-recordings.ftlx");
} catch (IOException e) {
log.error("IO exception for get-recordings.ftl : ", e);
}
StringWriter xmlText = new StringWriter();
Map root = new HashMap();
root.put("returnCode", returnCode);
root.put("recordings", recordings);
try {
ftl.process(root, xmlText);
} catch (TemplateException e) {
log.error("Template exception : ", e);
} catch (IOException e) {
log.error("IO exception for get-recordings.ftlx : ", e);
}
return xmlText.toString();
}
}

View File

@ -0,0 +1,25 @@
<recording>
<id>3c97908bb2dc4f794914776d639405709479c3da-1490721543626</id>
<state>published</state>
<published>true</published>
<start_time>1490721543626</start_time>
<end_time>1490721560835</end_time>
<participants>1</participants>
<meta>
<meeting-name>grailserror</meeting-name>
<room-id>bn-19631b57</room-id>
<isBreakout>false</isBreakout>
<meetingName>grailserror</meetingName>
<gl-listed>false</gl-listed>
<gl-token>bn-19631b57-grailserror</gl-token>
<gl-webhooks-callback-url>http://localhost/b/rooms/bn-19631b57/grailserror/callback</gl-webhooks-callback-url>
<meetingId>77f6566f80654ce7dff9e14b2c01cbd99fb75abc</meetingId>
</meta>
<playback>
<format>presentation</format>
<link>http://demo.bigbluebutton.org/playback/presentation/0.9.0/playback.html?meetingId=3c97908bb2dc4f794914776d639405709479c3da-1490721543626</link>
<processing_time>20772</processing_time>
<duration>9807</duration>
</playback>
</recording>

View File

@ -0,0 +1,79 @@
<#compress>
<response>
<#-- Where code is a 'SUCCESS' or 'FAILED' String -->
<returncode>${returnCode}</returncode>
<meetingName>${meeting.getName()?html}</meetingName>
<meetingID>${meeting.getExternalId()?html}</meetingID>
<internalMeetingID>${meeting.getInternalId()}</internalMeetingID>
<createTime>${meeting.getCreateTime()?c}</createTime>
<createDate>${createdOn}</createDate>
<voiceBridge>${meeting.getTelVoice()}</voiceBridge>
<dialNumber>${meeting.getDialNumber()}</dialNumber>
<attendeePW>${meeting.getViewerPassword()?html}</attendeePW>
<moderatorPW>${meeting.getModeratorPassword()?html}</moderatorPW>
<running>${meeting.isRunning()?c}</running>
<duration>${meeting.getDuration()}</duration>
<hasUserJoined>${meeting.hasUserJoined()?c}</hasUserJoined>
<recording>${meeting.isRecord()?c}</recording>
<hasBeenForciblyEnded>${meeting.isForciblyEnded()?c}</hasBeenForciblyEnded>
<startTime>${meeting.getStartTime()?c}</startTime>
<endTime>${meeting.getEndTime()}</endTime>
<participantCount>${meeting.getNumUsers()}</participantCount>
<listenerCount>${meeting.getNumListenOnly()}</listenerCount>
<voiceParticipantCount>${meeting.getNumVoiceJoined()}</voiceParticipantCount>
<videoCount>${meeting.getNumVideos()}</videoCount>
<maxUsers>${meeting.getMaxUsers()}</maxUsers>
<moderatorCount>${meeting.getNumModerators()}</moderatorCount>
<attendees>
<#list meeting.getUsers() as att>
<attendee>
<userID>${att.getInternalUserId()}</userID>
<fullName>${att.getFullname()?html}</fullName>
<role>${att.getRole()}</role>
<isPresenter>${att.isPresenter()?c}</isPresenter>
<isListeningOnly>${att.isListeningOnly()?c}</isListeningOnly>
<hasJoinedVoice>${att.isVoiceJoined()?c}</hasJoinedVoice>
<hasVideo>${att.hasVideo()?c}</hasVideo>
<#if meeting.getUserCustomData(att.getExternalUserId())??>
<#assign ucd = meeting.getUserCustomData(att.getExternalUserId())>
<customdata>
<#list ucd?keys as prop>
<${(prop)?html}>${(ucd[prop])?html}</${(prop)?html}>
</#list>
</customdata>
</#if>
</attendee>
</#list>
</attendees>
<#assign m = meeting.getMetadata()>
<metadata>
<#list m?keys as prop>
<${(prop)?html}>${(m[prop])?html}</${(prop)?html}>
</#list>
</metadata>
<#if messageKey?has_content>
<messageKey>${messageKey}</messageKey>
</#if>
<#if message?has_content>
<message>${message}</message>
</#if>
<isBreakout>${meeting.isBreakout()?c}</isBreakout>
<#if meeting.isBreakout()>
<parentMeetingID>${meeting.getParentMeetingId()}</parentMeetingID>
<sequence>${meeting.getSequence()}</sequence>
</#if>
<#list meeting.getBreakoutRooms()>
<breakoutRooms>
<#items as room>
<breakout>${room}</breakout>
</#items>
</breakoutRooms>
</#list>
</response>
</#compress>

View File

@ -0,0 +1,79 @@
<#compress>
<response>
<#-- Where code is a 'SUCCESS' or 'FAILED' String -->
<returncode>${returnCode}</returncode>
<#list meetingDetailsList>
<meetings>
<#items as meetingDetail>
<#assign meeting = meetingDetail.getMeeting()>
<meeting>
<meetingName>${meeting.getName()?html}</meetingName>
<meetingID>${meeting.getExternalId()?html}</meetingID>
<internalMeetingID>${meeting.getInternalId()}</internalMeetingID>
<createTime>${meeting.getCreateTime()?c}</createTime>
<createDate>${meetingDetail.getCreatedOn()}</createDate>
<voiceBridge>${meeting.getTelVoice()}</voiceBridge>
<dialNumber>${meeting.getDialNumber()}</dialNumber>
<attendeePW>${meeting.getViewerPassword()?html}</attendeePW>
<moderatorPW>${meeting.getModeratorPassword()?html}</moderatorPW>
<running>${meeting.isRunning()?c}</running>
<duration>${meeting.getDuration()}</duration>
<hasUserJoined>${meeting.hasUserJoined()?c}</hasUserJoined>
<recording>${meeting.isRecord()?c}</recording>
<hasBeenForciblyEnded>${meeting.isForciblyEnded()?c}</hasBeenForciblyEnded>
<startTime>${meeting.getStartTime()?c}</startTime>
<endTime>${meeting.getEndTime()}</endTime>
<participantCount>${meeting.getNumUsers()}</participantCount>
<listenerCount>${meeting.getNumListenOnly()}</listenerCount>
<voiceParticipantCount>${meeting.getNumVoiceJoined()}</voiceParticipantCount>
<videoCount>${meeting.getNumVideos()}</videoCount>
<maxUsers>${meeting.getMaxUsers()}</maxUsers>
<moderatorCount>${meeting.getNumModerators()}</moderatorCount>
<attendees>
<#list meetingDetail.meeting.getUsers() as att>
<attendee>
<userID>${att.getInternalUserId()}</userID>
<fullName>${att.getFullname()?html}</fullName>
<role>${att.getRole()}</role>
<isPresenter>${att.isPresenter()?c}</isPresenter>
<isListeningOnly>${att.isListeningOnly()?c}</isListeningOnly>
<hasJoinedVoice>${att.isVoiceJoined()?c}</hasJoinedVoice>
<hasVideo>${att.hasVideo()?c}</hasVideo>
<#if meeting.getUserCustomData(att.getExternalUserId())??>
<#assign ucd = meetingDetail.meeting.getUserCustomData(att.getExternalUserId())>
<customdata>
<#list ucd?keys as prop>
<${(prop)?html}>${(ucd[prop])?html}</${(prop)?html}>
</#list>
</customdata>
</#if>
</attendee>
</#list>
</attendees>
<#assign m = meetingDetail.meeting.getMetadata()>
<metadata>
<#list m?keys as prop>
<${(prop)?html}>${(m[prop])?html}</${(prop)?html}>
</#list>
</metadata>
<isBreakout>${meetingDetail.meeting.isBreakout()?c}</isBreakout>
<#if meetingDetail.meeting.isBreakout()>
<parentMeetingID>${meetingDetail.meeting.getParentMeetingId()}</parentMeetingID>
<sequence>${meetingDetail.meeting.getSequence()}</sequence>
</#if>
<#list meetingDetail.meeting.getBreakoutRooms()>
<breakoutRooms>
<#items as room>
<breakout>${room}</breakout>
</#items>
</breakoutRooms>
</#list>
</meeting>
</#items>
</meetings>
</#list>
</response>
</#compress>

View File

@ -0,0 +1,20 @@
<#-- GET_RECORDINGS FreeMarker XML template -->
<#compress>
<response>
<#-- Where code is a 'SUCCESS' or 'FAILED' String -->
<returncode>${returnCode}</returncode>
<recordings>
<#list recordings as r>
<recording>
<#if r.hasError()>
<error>
<recordID>${r.getMetadataXml()?html}</recordID>
</error>
<#else>
<#include "include-recording.ftlx">
</#if>
</recording>
</#list>
</recordings>
</response>
</#compress>

View File

@ -0,0 +1,77 @@
<recordID>${r.getId()}</recordID>
<#if r.getMeeting()??>
<meetingID>${r.getMeeting().getId()?html}</meetingID>
<externalMeetingID>${r.getMeeting().getExternalId()?html}</externalMeetingID>
<name>${r.getMeeting().getName()?html}</name>
<isBreakout>${r.getMeeting().isBreakout()?c}</isBreakout>
<#else>
<meetingID>${r.getMeetingId()?html}</meetingID>
<name>${r.getMeetingName()?html}></name>
<isBreakout>${r.isBreakout()?c}</isBreakout>
</#if>
<published>${r.getPublished()?string}</published>
<state>${r.getState()?string}</state>
<startTime><#if r.getStartTime()?? && r.getStartTime() != "">${r.getStartTime()}</#if></startTime>
<endTime><#if r.getEndTime()?? && r.getEndTime() != "">${r.getEndTime()}</#if></endTime>
<participants><#if r.getParticipants()??>${r.getParticipants()}</#if></participants>
<#if r.getBreakout()??>
<#assign breakout = r.getBreakout()>
<breakout>
<parentId>${breakout.getParentMeetingId()}</parentId>
<sequence>${breakout.getSequence()?c}</sequence>
</breakout>
</#if>
<#if r.getBreakoutRooms()??>
<#list r.getBreakoutRooms()>
<breakoutRooms>
<#items as broom>
<breakoutRoom>${broom.getValue()}</breakoutRoom>
</#items>
</breakoutRooms>
</#list>
</#if>
<#assign m = r.getMeta().get()>
<metadata>
<#list m?keys as prop>
<${(prop)?html}>${((m[prop])?html)!""}</${(prop)?html}>
</#list>
</metadata>
<#assign pb = r.getPlayback()>
<playback>
<format>
<type>${pb.getFormat()}</type>
<url>${pb.getLink()}</url>
<processingTime>${pb.getProcessingTime()?c}</processingTime>
<#if pb.getDuration() == 0>
<length>${r.calculateDuration()?c}</length>
<#else>
<length>${pb.calculateDuration()?c}</length>
</#if>
<#if pb.getExtensions()??>
<#if pb.getExtensions().getPreview()??>
<#assign prev = pb.getExtensions().getPreview()>
<preview>
<#if prev.getImages()??>
<#list prev.getImages()>
<images>
<#items as image>
<#if image??>
<image width="${image.getWidth()}" height="${image.getHeight()}" alt="${image.getAlt()?html}">${image.getValue()!"Link not found."}</image>
</#if>
</#items>
</images>
</#list>
</#if>
</preview>
</#if>
</#if>
</format>
</playback>

View File

@ -0,0 +1,22 @@
<recording>
<id>f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374</id>
<state>published</state>
<published>true</published>
<start_time>1489172964374</start_time>
<end_time>1489173221647</end_time>
<breakoutRooms>
<breakoutRoom>32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780</breakoutRoom>
<breakoutRoom>721d83a3907548734d4a505992ebb94ec1454a91-1489173065780</breakoutRoom>
</breakoutRooms>
<meta>
<isBreakout>false</isBreakout>
<meetingId>random-2810069</meetingId>
<meetingName>random-2810069</meetingName>
</meta>
<playback>
<format>presentation</format>
<link>http://192.168.23.22/playback/presentation/0.9.0/playback.html?meetingId=f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374</link>
<processing_time>87598</processing_time>
<duration>226417</duration>
</playback>
</recording>

View File

@ -0,0 +1,18 @@
<recording>
<id>f3ffe06acedf425565cc024c8ebe89a6552e8782-1489095153417</id>
<state>published</state>
<published>true</published>
<start_time>1489095153417</start_time>
<end_time>1489095772224</end_time>
<meta>
<meetingId>random-2810069</meetingId>
<meetingName>random-2810069</meetingName>
<isBreakout>false</isBreakout>
</meta>
<playback>
<format>presentation</format>
<link>http://192.168.23.22/playback/presentation/0.9.0/playback.html?meetingId=f3ffe06acedf425565cc024c8ebe89a6552e8782-1489095153417</link>
<processing_time>191283</processing_time>
<duration>545949</duration>
</playback>
</recording>

View File

@ -0,0 +1,7 @@
<playback>
<format>presentation</format>
<link>http://192.168.23.22/playback/presentation/0.9.0/playback.html?meetingId=f3ffe06acedf425565cc024c8ebe89a6552e8782-1489095153417</link>
<processing_time>191283</processing_time>
<duration>545949</duration>
</playback>

View File

@ -0,0 +1,31 @@
<recording>
<id>f3ffe06acedf425565cc024c8ebe89a6552e8782-1489435015784</id>
<state>foo</state>
<published>false</published>
<start_time>1489435015784</start_time>
<end_time>1489435076055</end_time>
<meeting id="f3ffe06acedf425565cc024c8ebe89a6552e8782-1489435015784" externalId="random-2810069" name="random-2810069" breakout="false"/>
<breakout parentMeetingId="f3ffe06acedf425565cc024c8ebe89a6552e8782-1489172964374" sequence="2" meetingId="f2041d123b6a4b994e7ad87ee9d348496a73472c-1489173065780"/>
<breakoutRooms>
<breakoutRoom>32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780</breakoutRoom>
<breakoutRoom>721d83a3907548734d4a505992ebb94ec1454a91-1489173065780</breakoutRoom>
</breakoutRooms>
<meta>
<meetingName>random-2810069</meetingName>
<meetingId>random-2810069</meetingId>
<isBreakout>false</isBreakout>
</meta>
<playback>
<format>presentation</format>
<link>http://192.168.23.22/playback/presentation/0.9.0/playback.html?meetingId=f3ffe06acedf425565cc024c8ebe89a6552e8782-1489435015784</link>
<processing_time>28627</processing_time>
<duration>32213</duration>
<extensions>
<preview>
<images>
<image width="176" height="136" alt="Welcome to">http://192.168.23.22/presentation/32ee8bcccfad34f85c58a12f87fc4268130a4fd3-1489173065780/presentation/743dd59a958334b4cdcdaa302846d0c0eadcf9ff-1489173070800/thumbnails/thumb-1.png</image>
</images>
</preview>
</extensions>
</playback>
</recording>

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<recording id="panzoom_events">
<event timestamp="0.0" orig="2440467878.0">
<viewBox>0.0 0.0 1600.0 1200.0</viewBox>
</event>
<event timestamp="19.1" orig="2440504635.0">
<viewBox>0.0 0.0 1600.0 1200.0</viewBox>
</event>
<event timestamp="22.6" orig="2440508157.0">
<viewBox>0.0 0.0 1600.0 1200.0</viewBox>
</event>
</recording>

View File

@ -0,0 +1,2 @@
<?xml version="1.0"?>
<popcorn/>

View File

@ -0,0 +1,23 @@
package org.bigbluebutton.api.util
import org.scalatest._
class ParamsUtilTest extends UnitSpec {
it should "strip out control chars from text" in {
val text = "a\u0000b\u0007c\u008fd"
val cleaned = ParamsUtil.stripControlChars(text)
assert("abcd" == cleaned)
}
it should "complain about invalid chars in meetingId" in {
val meetingId = "Demo , Meeting"
assert(ParamsUtil.isValidMeetingId(meetingId) == false)
}
it should "accept valid chars in meetingId" in {
val meetingId = "Demo Meeting - 123"
assert(ParamsUtil.isValidMeetingId(meetingId) == true)
}
}

View File

@ -0,0 +1,79 @@
package org.bigbluebutton.api.util
import java.io.{File, FileInputStream}
import javax.xml.stream.{XMLInputFactory, XMLStreamReader}
import com.fasterxml.jackson.dataformat.xml.{JacksonXmlModule, XmlMapper}
import org.bigbluebutton.api.domain.{RecordingMetadata, RecordingMetadataPlayback}
class RecordingMetadataReaderHelperTest extends UnitSpec {
it should "deserialize playback part of metadata.xml" in {
val factory: XMLInputFactory = XMLInputFactory.newInstance();
val xml = new File("src/test/resources/playback-metadata.xml")
val module: JacksonXmlModule = new JacksonXmlModule();
// and then configure, for example:
module.setDefaultUseWrapper(false);
val mapper: XmlMapper = new XmlMapper(module)
//Reading from xml file and creating XMLStreamReader
val reader: XMLStreamReader = factory.createXMLStreamReader(new FileInputStream(xml))
val playback: RecordingMetadataPlayback = mapper.readValue(reader, classOf[RecordingMetadataPlayback])
println("***** FOOO =" + mapper.writeValueAsString(playback))
assert(playback.getDuration == 545949)
}
it should "deserialize metadata.xml" in {
val factory: XMLInputFactory = XMLInputFactory.newInstance();
val xml = new File("src/test/resources/breakout-room-metadata.xml")
val module: JacksonXmlModule = new JacksonXmlModule();
// and then configure, for example:
module.setDefaultUseWrapper(false);
val mapper: XmlMapper = new XmlMapper(module)
//Reading from xml file and creating XMLStreamReader
val reader: XMLStreamReader = factory.createXMLStreamReader(new FileInputStream(xml))
val recMeta: RecordingMetadata = mapper.readValue(reader, classOf[RecordingMetadata])
println("***** FOOO =" + mapper.writeValueAsString(recMeta))
//assert(recMeta.getPlayback.getDuration == 126376)
assert(true)
}
it should "save metadata.xml" in {
val factory: XMLInputFactory = XMLInputFactory.newInstance();
val xml = new File("src/test/resources/breakout-room-metadata.xml")
val module: JacksonXmlModule = new JacksonXmlModule();
// and then configure, for example:
module.setDefaultUseWrapper(false);
val mapper: XmlMapper = new XmlMapper(module)
//Reading from xml file and creating XMLStreamReader
val reader: XMLStreamReader = factory.createXMLStreamReader(new FileInputStream(xml))
val recMeta: RecordingMetadata = mapper.readValue(reader, classOf[RecordingMetadata])
recMeta.getMeta().set("FOO", "BAR");
val metadataXml = RecordingMetadataReaderHelper.getMetadataXmlLocation("target")
if (metadataXml.exists()) metadataXml.delete()
RecordingMetadataReaderHelper.saveRecordingMetadata(metadataXml, recMeta)
assert(metadataXml.exists())
}
}

View File

@ -0,0 +1,77 @@
package org.bigbluebutton.api.util
import java.io.File
import java.util
import org.apache.commons.io.FileUtils
import org.bigbluebutton.api.RecordingService
class RecordingServiceTest extends UnitSpec {
it should "deserialize playback part of metadata.xml" in {
val srcXml = new File("src/test/resources/breakout-room-metadata.xml")
val metaParams: util.Map[String, String] = new util.TreeMap[String, String]()
metaParams.put("foo", "bar")
metaParams.put("bar", "baz")
val destXml = new File("target/updated-metadata.xml")
RecordingService.updateRecordingMetadata(srcXml, metaParams, destXml)
}
it should "publish recording" in {
// Make a copy of our sample recording
val destDir = new File("target/sample-recording/publish")
if (destDir.exists()) FileUtils.deleteDirectory(destDir)
val srcDir = new File("src/test/resources/sample-recording")
FileUtils.copyDirectory(srcDir, destDir)
val recordingId = "foo"
val recordingDir = new File(destDir.getPath() + File.separatorChar + recordingId)
val publishedDir = new File("target/recording/published")
RecordingService.publishRecording(publishedDir, recordingId, recordingDir)
assert(true)
}
it should "unpublish recording" in {
// Make a copy of our sample recording
val destDir = new File("target/sample-recording/unpublish")
if (destDir.exists()) FileUtils.deleteDirectory(destDir)
val srcDir = new File("src/test/resources/sample-recording")
FileUtils.copyDirectory(srcDir, destDir)
val recordingId = "foo"
val recordingDir = new File(destDir.getPath() + File.separatorChar + recordingId)
val unpublishedDir = new File("target/recording/unpublished")
if (unpublishedDir.exists()) FileUtils.deleteDirectory(unpublishedDir)
RecordingService.unpublishRecording(unpublishedDir, recordingId, recordingDir)
assert(unpublishedDir.exists())
}
it should "delete recording" in {
// Make a copy of our sample recording
val destDir = new File("target/sample-recording/delete")
if (destDir.exists()) FileUtils.deleteDirectory(destDir)
val srcDir = new File("src/test/resources/sample-recording")
FileUtils.copyDirectory(srcDir, destDir)
val recordingId = "foo"
val recordingDir = new File(destDir.getPath() + File.separatorChar + recordingId)
val deletedDir = new File("target/recording/deleted")
if (deletedDir.exists()) FileUtils.deleteDirectory(deletedDir)
RecordingService.deleteRecording(deletedDir, recordingId, recordingDir)
assert(deletedDir.exists())
}
}

View File

@ -0,0 +1,234 @@
package org.bigbluebutton.api.util
import java.io.File
import java.util
import org.apache.commons.io.FileUtils
import org.bigbluebutton.api.domain.{Meeting, RecordingMetadata, User}
import org.scalatest._
class ResponseBuilderTest extends UnitSpec {
it should "find template" in {
val current = new java.io.File( "." ).getCanonicalPath()
println("Current dir:"+current)
val meetingInfo = new util.TreeMap[String, String]()
meetingInfo.put("foo", "foo")
meetingInfo.put("bar", "baz")
val meeting: Meeting = new Meeting.Builder("extMid", "intMid", System.currentTimeMillis())
.withName("Demo Meeting").withMaxUsers(25)
.withModeratorPass("mp").withViewerPass("ap")
.withRecording(true).withDuration(600)
.withLogoutUrl("/logoutUrl").withTelVoice("85115").withWebVoice("85115")
.withDialNumber("6135551234").withDefaultAvatarURL("/avatar")
.withAutoStartRecording(false).withAllowStartStopRecording(true)
.withWebcamsOnlyForModerator(false).withMetadata(meetingInfo)
.withWelcomeMessageTemplate("hello").withWelcomeMessage("hello")
.isBreakout(false).build
meeting.setParentMeetingId("ParentMeetingId")
meeting.setSequence(0);
meeting.addBreakoutRoom("breakout-room-id-1")
meeting.addBreakoutRoom("breakout-room-id-2")
meeting.addBreakoutRoom("breakout-room-id-3")
val user: User = new User("uid1", "extuid1", "Richard", "moderator", "/aygwapo")
meeting.userJoined(user)
val user2: User = new User("uid2", "extuid2", "Richard 2", "moderator", "/aygwapo")
meeting.userJoined(user2)
val user3: User = new User("uid3", "extuid3", "Richard 3", "moderator", "/aygwapo")
meeting.userJoined(user2)
val custData = new util.HashMap[String, String]()
custData.put("gwapo", "totoo")
meeting.addUserCustomData("extuid1", custData)
val templateLoc = new File("src/test/resources")
val builder = new ResponseBuilder(templateLoc)
def response = builder.buildGetMeetingInfoResponse(meeting, "success")
// println(response)
assert(templateLoc.exists())
}
it should "return meetings" in {
val meetingInfo1 = new util.TreeMap[String, String]()
meetingInfo1.put("foo", "foo")
meetingInfo1.put("bar", "baz")
val meeting1: Meeting = new Meeting.Builder("extMid1", "intMid1", System.currentTimeMillis())
.withName("Demo Meeting 1").withMaxUsers(25)
.withModeratorPass("mp").withViewerPass("ap")
.withRecording(true).withDuration(600)
.withLogoutUrl("/logoutUrl").withTelVoice("85115").withWebVoice("85115")
.withDialNumber("6135551234").withDefaultAvatarURL("/avatar")
.withAutoStartRecording(false).withAllowStartStopRecording(true)
.withWebcamsOnlyForModerator(false).withMetadata(meetingInfo1)
.withWelcomeMessageTemplate("hello").withWelcomeMessage("hello")
.isBreakout(false).build
meeting1.setParentMeetingId("ParentMeetingId")
meeting1.setSequence(0);
meeting1.addBreakoutRoom("breakout-room-id-1")
meeting1.addBreakoutRoom("breakout-room-id-2")
meeting1.addBreakoutRoom("breakout-room-id-3")
val userm11: User = new User("uid1", "extuid1", "Richard", "moderator", "/aygwapo")
meeting1.userJoined(userm11)
val userm12: User = new User("uid2", "extuid2", "Richard 2", "moderator", "/aygwapo")
meeting1.userJoined(userm12)
val userm13: User = new User("uid3", "extuid3", "Richard 3", "moderator", "/aygwapo")
meeting1.userJoined(userm13)
val custDatam1 = new util.HashMap[String, String]()
custDatam1.put("gwapo", "totoo")
meeting1.addUserCustomData("extuid1", custDatam1)
val meetingInfo2 = new util.TreeMap[String, String]()
meetingInfo2.put("foo", "foo")
meetingInfo2.put("bar", "baz")
val meeting2: Meeting = new Meeting.Builder("extMid2", "intMid2", System.currentTimeMillis())
.withName("Demo Meeting 2").withMaxUsers(25)
.withModeratorPass("mp").withViewerPass("ap")
.withRecording(true).withDuration(600)
.withLogoutUrl("/logoutUrl").withTelVoice("85115").withWebVoice("85115")
.withDialNumber("6135551234").withDefaultAvatarURL("/avatar")
.withAutoStartRecording(false).withAllowStartStopRecording(true)
.withWebcamsOnlyForModerator(false).withMetadata(meetingInfo2)
.withWelcomeMessageTemplate("hello").withWelcomeMessage("hello")
.isBreakout(false).build
meeting2.setParentMeetingId("ParentMeetingId")
meeting2.setSequence(0);
meeting2.addBreakoutRoom("breakout-room-id-1")
meeting2.addBreakoutRoom("breakout-room-id-2")
meeting2.addBreakoutRoom("breakout-room-id-3")
val userm21: User = new User("uid1", "extuid1", "Richard", "moderator", "/aygwapo")
meeting2.userJoined(userm21)
val userm22: User = new User("uid2", "extuid2", "Richard 2", "moderator", "/aygwapo")
meeting2.userJoined(userm22)
val userm23: User = new User("uid3", "extuid3", "Richard 3", "moderator", "/aygwapo")
meeting2.userJoined(userm23)
val custDatam2 = new util.HashMap[String, String]()
custDatam2.put("gwapo", "totoo")
meeting2.addUserCustomData("extuid1", custDatam2)
val meetingInfo3 = new util.TreeMap[String, String]()
meetingInfo3.put("foo", "foo")
meetingInfo3.put("bar", "baz")
val meeting3: Meeting = new Meeting.Builder("extMid", "intMid", System.currentTimeMillis())
.withName("Demo Meeting").withMaxUsers(25)
.withModeratorPass("mp").withViewerPass("ap")
.withRecording(true).withDuration(600)
.withLogoutUrl("/logoutUrl").withTelVoice("85115").withWebVoice("85115")
.withDialNumber("6135551234").withDefaultAvatarURL("/avatar")
.withAutoStartRecording(false).withAllowStartStopRecording(true)
.withWebcamsOnlyForModerator(false).withMetadata(meetingInfo3)
.withWelcomeMessageTemplate("hello").withWelcomeMessage("hello")
.isBreakout(false).build
meeting3.setParentMeetingId("ParentMeetingId")
meeting3.setSequence(0);
meeting3.addBreakoutRoom("breakout-room-id-1")
meeting3.addBreakoutRoom("breakout-room-id-2")
meeting3.addBreakoutRoom("breakout-room-id-3")
val user: User = new User("uid1", "extuid1", "Richard", "moderator", "/aygwapo")
meeting3.userJoined(user)
val user2: User = new User("uid2", "extuid2", "Richard 2", "moderator", "/aygwapo")
meeting3.userJoined(user2)
val user3: User = new User("uid3", "extuid3", "Richard 3", "moderator", "/aygwapo")
meeting3.userJoined(user2)
val custData = new util.HashMap[String, String]()
custData.put("gwapo", "totoo")
meeting3.addUserCustomData("extuid1", custData)
val meetings = new util.ArrayList[Meeting]()
meetings.add(meeting1)
meetings.add(meeting2)
meetings.add(meeting3)
val templateLoc = new File("src/test/resources")
val builder = new ResponseBuilder(templateLoc)
def response = builder.buildGetMeetingsResponse(meetings, "success")
// println(response)
assert(templateLoc.exists())
}
it should "reply to getRecordings api call" in {
val templateLoc = new File("src/test/resources")
val builder = new ResponseBuilder(templateLoc)
val metadataXml = new File("src/test/resources/breakout-room-metadata.xml")
val recMeta = RecordingMetadataReaderHelper.getRecordingMetadata(metadataXml);
val recList = new util.ArrayList[RecordingMetadata]()
recList.add(recMeta)
def response = builder.buildGetRecordingsResponse(recList, "success")
println(response)
assert(templateLoc.exists())
}
it should "support old metadata.xml in getRecordings api call" in {
val templateLoc = new File("src/test/resources")
val builder = new ResponseBuilder(templateLoc)
// Make a copy of our sample recording
val destDir = new File("target/recording-metadata/presentation")
if (destDir.exists()) FileUtils.deleteDirectory(destDir)
val srcDir = new File("src/test/resources/recording-metadata/presentation")
FileUtils.copyDirectory(srcDir, destDir)
val recDirs = destDir.listFiles()
val recList = new util.ArrayList[RecordingMetadata]()
println("START **********************")
recDirs.map {rec =>
val metadataXml = new File(rec.getAbsolutePath + File.separatorChar + "metadata.xml")
// println(metadataXml.getAbsolutePath)
val recMeta = RecordingMetadataReaderHelper.getRecordingMetadata(metadataXml);
// val recList = new util.ArrayList[RecordingMetadata]()
// println("START **********************")
if (recMeta != null) recList.add(recMeta)
// val response = builder.buildGetRecordingsResponse(recList, "success")
// println(response)
// println("END **********************")
}
val response = builder.buildGetRecordingsResponse(recList, "success")
println(response)
println("END **********************")
assert(templateLoc.exists())
}
}

View File

@ -0,0 +1,8 @@
package org.bigbluebutton.api.util
import org.scalatest.FlatSpec
import org.scalatest.BeforeAndAfterAll
import org.scalatest.WordSpec
import org.scalatest.Matchers
abstract class UnitSpec extends FlatSpec with Matchers with BeforeAndAfterAll

View File

@ -41,6 +41,9 @@ ltiMode=extended
# Defines if LTI credentials are required
# Format: [false|<true>]
ltiRestrictedAccess=true
# Sets all the meetings to be recorded by default
# Format: [<false>|true]
ltiAllRecordedByDefault=false
#----------------------------------------------------
# Inject configuration values into BigbluebuttonSrvice beans
@ -53,4 +56,4 @@ beans.ltiService.endPoint=${ltiEndPoint}
beans.ltiService.consumers=${ltiConsumers}
beans.ltiService.mode=${ltiMode}
beans.ltiService.restrictedAccess=${ltiRestrictedAccess}
beans.ltiService.recordedByDefault=${ltiAllRecordedByDefault}

View File

@ -83,8 +83,8 @@ class ToolController {
result = doJoinMeeting(params)
} else {
log.debug "LTI service running in extended mode."
if ( !Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) {
log.debug "No bbb_record parameter was sent; immediately redirecting to BBB session!"
if ( !Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) && !ltiService.allRecordedByDefault() ) {
log.debug "Parameter custom_record was not sent; immediately redirecting to BBB session!"
result = doJoinMeeting(params)
}
}
@ -222,7 +222,7 @@ class ToolController {
log.debug "Overriding default welcome message with: [" + welcome + "]"
}
if ( params.containsKey(Parameter.CUSTOM_RECORD) && Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) {
if ( params.containsKey(Parameter.CUSTOM_RECORD) && Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault() ) {
welcome += "<br><b>" + message(code: "bigbluebutton.welcome.record") + "</b><br>"
log.debug "Adding record warning to welcome message, welcome is now: [" + welcome + "]"
}

View File

@ -92,7 +92,7 @@ class BigbluebuttonService {
Integer duration = 0
if( "extended".equals(mode) ){
voiceBridge = getValidatedBBBVoiceBridge(params.get(Parameter.CUSTOM_VOICEBRIDGE))
record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD))
record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault()
duration = getValidatedBBBDuration(params.get(Parameter.CUSTOM_DURATION))
}

View File

@ -32,6 +32,7 @@ class LtiService {
def consumers = "demo:welcome"
def mode = "simple"
def restrictedAccess = "true"
def recordedByDefault = "false"
Map<String, String> consumerMap
@ -137,10 +138,14 @@ class LtiService {
log.debug("Exception: Message=" + e.getMessage())
}
return ssl_enabled
return ssl_enabled
}
def boolean hasRestrictedAccess() {
return Boolean.parseBoolean(this.restrictedAccess);
}
def boolean allRecordedByDefault() {
return Boolean.parseBoolean(this.recordedByDefault);
}
}

184
bbb-screenshare/NOTES.md Executable file
View File

@ -0,0 +1,184 @@
These are instructions taken from JavaCV website to write the instructions above. We keep a copy here to make sure the instructions we have are compatible with out version of javacpp-presets.
** NOTE: **
Taken from https://github.com/bytedeco/javacpp-presets/wiki/Build-Environments
Introduction
------------
This page contains a description of the environments that are used to build the JavaCPP Presets for [Android (ARM and x86)](#android-arm-and-x86), [Linux (x86 and x86_64)](#linux-x86-and-x86_64), [Linux (ARM)](#linux-arm), [Mac OS X (x86_64)](#mac-os-x-x86_64), and [Windows (x86 and x86_64)](#windows-x86-and-x86_64). We also explain our choices given the requirements and provide some recommendations. Furthermore, JavaCPP is by no means limited to these platforms, so if you happen to know how to set up an environment for other platforms, by all means, please do add that information to this page to share with others. Thank you!
Prerequisites for all platforms
-------------------------------
The build process for the modules of `javacpp-presets` usually depends on the same version of the `javacpp` module. To insure that you have the latest matching version of both, please execute the following before starting the build in the `javacpp-presets` directory:
```bash
$ git clone https://github.com/bytedeco/javacpp.git --branch <tag>
$ git clone https://github.com/bytedeco/javacpp-presets.git --branch <tag>
$ cd javacpp
$ mvn clean install
```
For the latest tag please check
https://github.com/bytedeco/javacpp-presets/tags
Android (ARM and x86)
---------------------
To produce native libraries for Android, we basically only need to install the JDK and the NDK, which is available for Linux, Mac OS X, and Windows. However, the build scripts of some libraries only run correctly under Linux, so we recommend using a recent distribution of Linux (such as Fedora or Ubuntu) as build environment for Android.
### Preparations
1. Download the latest version of the [NDK](https://developer.android.com/ndk/downloads/), which is r10e at the time of this writing and contains important fixes for OpenMP, among other things
2. Install the NDK under `~/Android/android-ndk`, where the build scripts will look for it by default
3. Finally, make sure to have installed at least OpenJDK and Maven as per the instructions of your distribution
After which the following commands can be used to start the build inside the `javacpp-presets` directory:
```bash
$ ANDROID_NDK=/path/to/android-ndk/ bash cppbuild.sh -platform android-xxx install
$ mvn clean install -Djavacpp.platform=android-xxx -Djavacpp.platform.root=/path/to/android-ndk/ -Djavacpp.platform.compiler=/path/to/target-g++
```
where `android-xxx` is either `android-arm` or `android-x86`, and where `target-g++` is either `arm-linux-androideabi-g++` or `i686-linux-android-g++`.
Linux (x86 and x86_64)
----------------------
To produce native libraries that can run on the largest possible number of Linux installations out there, it is recommended to build under CentOS 7. This is because it relies on an old enough version of glibc, which nevertheless works for all the libraries found in the JavaCPP Presets, and since newer versions of glibc are backward compatible, all recent distributions of Linux should support the binaries generated. We do not actually need to install CentOS 7 though. Pretty much any recent distribution of Linux comes with a package for [Docker](https://www.docker.com/). It is also possible to map existing directories, for example `/usr/local/lib/bazel` and `/usr/local/cuda` as shown in the steps below, to reuse an existing [Bazel](http://bazel.io/docs/install.html) or [CUDA](https://developer.nvidia.com/cuda-downloads) installation as well as any other set of files for the purpose of the build.
### Preparations
1. Install Docker under, for example, Fedora and Ubuntu, respectively:
```bash
$ sudo yum install docker
$ sudo apt-get install docker.io
```
2. When using SELinux, it might also be necessary to disable temporarily the firewall, for example:
```
$ sudo systemctl stop firewalld
$ sudo systemctl start docker
```
3. Start the container for CentOS 7 (the command might be `docker.io` instead of `docker`):
```bash
$ sudo docker run --privileged -it -v /usr/local/lib/bazel:/usr/local/lib/bazel -v /usr/local/cuda:/usr/local/cuda centos:7 /bin/bash
```
4. Finally, inside the container, we need to install a bunch of things:
```bash
$ ln -s /usr/local/lib/bazel/bin/bazel /usr/local/bin/bazel
$ yum install epel-release
$ yum install clang gcc-c++ gcc-gfortran java-devel maven python numpy swig git file which wget unzip tar bzip2 gzip xz patch make cmake3 perl nasm yasm alsa-lib-devel freeglut-devel gtk2-devel libusb-devel libusb1-devel zlib-devel
$ yum install `rpm -qa | sed s/.x86_64$/.i686/`
```
After which the following commands can be used to start the build inside the `javacpp-presets` directory:
```bash
$ bash cppbuild.sh -platform linux-xxx install
$ mvn clean install -Djavacpp.platform=linux-xxx
```
where `linux-xxx` is either `linux-x86` or `linux-x86_64`.
Linux (ARM)
-----------
There are a growing number of arm platforms running Linux, though testing of this build has mainly focussed on the Pi family of products. Compiling natively on these platforms can be quite time consuming, and as well to automate the build process, the build setup here relies on cross-compiling for the target arm platform. This has been tested using Ubuntu x64 15.10, but it should be reasonably similar approach for other host OS's.
You'll need to have setup a build environment with the usual packages (pkgconfig, build-essentials, yasm, nasm, maven3, etc). The major addition you'll need is a set of cross compilers: arm-linux-gnueabihf-gcc, arm-linux-gnueabihf-g++ and arm-linux-gnueabihf-cpp. It's best to get these via your OS's package manager, as there are other dependencies for these.
Originally a 5.x compiler was used, but this seems to caused problems in creating a dependency on newer glibc functionality which might not be present on your target arm device. The 4.8 or 4.9 version of the compiler seems to work fine, but, in targetting support for the Pi1 and well as Pi2-Pi3 (i.e. arm6 as well as arm7) it seems some compiler flag combinations might not be support by the standard gnueabihf compiler toolchain.
There are pi specific compilers available, so these have been used in the current setup.
1. Get the Pi tools: git clone https://github.com/raspberrypi/tools.git
2. From this folder, move out a specific compiler version to a more generic location: sudo cp -r ./tools/arm-bcm2708/arm-bcm2708-linux-gnueabi /opt
3. Setup alternatives of the generic compiler names in /usr/bin to point to this new compiler
```bash
$ update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /opt/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi-gcc 46
$ update-alternatives --install /usr/bin/arm-linux-gnueabihf-g++ arm-linux-gnueabihf-g++ /opt/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi-g++ 46
$ update-alternatives --install /usr/bin/arm-linux-gnueabihf-cpp arm-linux-gnueabihf-cpp /opt/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi-cpp 46
```
(This way if you want to explore using other newer compilers, just add them to alternatives and the same build setup should work fine)
This should now have you setup ready to build for arm. It could be an idea to test at this stage with building a simple hello world executable (save your hello world test code as hello.cpp):
$ arm-linux-gnueabihf-g++ -O3 -g3 -Wall -fPIC -march=armv6 -mfpu=vfp -mfloat-abi=hard hello.cpp -c -o hello.o
$ arm-linux-gnueabihf-g++ -o hello hello.o
$ file hello
And you should see in the returned info that hello is built for ARM
With the build environment setup, now its on to building JavaCV. Not all components have been setup with the linux-armhf build configurations, so rather than building the entire project only a subset are built here, but enough to have core functionality (OpenCV, FFmpeg) working with some additional parts (artoolkitplus, flycapture, flandmark, libfreenect, libdc1394) built but not tested. For flycapture, you need to download the arm SDK (currently flycapture.2.9.3.13_armhf) and make these .so libs available, either in your path, or setting up a /usr/include/flycapture directory and moving them there.
Now all the dependencies are setup, the build can be started (assuming you've done a git clone of javacv, javacpp and javacpp-presets all to the same folder)
$ cd javacpp
$ mvn install
$ cd ..
$ cd javacpp-presets
$ ./cppbuild.sh -platform linux-armhf install
$ mvn install -Djavacpp.platform=linux-armhf -Djavacpp.platform.compiler=arm-linux-gnueabihf-g++
$ cd platform
$ mvn install -Djavacpp.platform=linux-armhf
Hopefully that all runs OK, and then in ./javacpp-presets/platform/target/ you should find there are platform specific (opencv-linux-armhf.jar, ffmpeg-linux-armhf.jar, etc) files built.
If you want to try alternative flags, you need to modify in javacpp, ./src/main/resources/org/bytedeco/javacpp/properties/linux-armhf.properties and then in javacpp-presets any project cppbuild.sh file where you want to update too (e.g. ./opencv/cppbuild.sh linux-armhf section). For newer Pis on arm7 it does look like there are potential performance gains in armv7 and neon flags, and using a newer compiler rather than the bcm2708 build used here may further improve things (some earlier builds specific for armv7 did look faster). Also if you are using onboard picam devices, make sure you load the module with "modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944" - this way if you test just using OpenCV or FFMPEG grabber you should get at least 30fps as a start point. The more computation you then do on each frame, the more this will drop.
Mac OS X (x86_64)
-----------------
OS X Mavericks (10.9) is the first version of Mac OS X to support C++11 fully and properly, so to preserve your sanity, we do not recommend trying to build or use the JavaCPP Presets on any older versions of Mac OS X.
### Preparations
1. Install [Xcode](https://developer.apple.com/xcode/) and [Homebrew](http://brew.sh/)
2. Ensure the command line tools for Xcode are installed
```bash
$ xcode-select --install
```
3. Run the following commands to install the JDK, among other things Apple left out of Xcode:
```bash
$ brew install caskroom/cask/brew-cask
$ brew cask install cuda java
$ brew install gcc5 swig bazel cmake libusb maven nasm yasm xz pkg-config
```
After which the following commands can be used to start the build inside the `javacpp-presets` directory:
```bash
$ bash cppbuild.sh install
$ mvn clean install
```
Windows (x86 and x86_64)
------------------------
Visual Studio Community 2013 is the first free version to have been decently bundled with support for C++11, OpenMP, the Windows SDK, and everything else from Microsoft, so we recommend installing that version of Visual Studio, which consequently requires Windows 7. Still, to run the bash scripts and compile some things that the Microsoft C/C++ Compiler does not support, we need to install manually a few other things.
### Preparations
1. Install the [Java SE Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/), [Maven](https://maven.apache.org/download.cgi), [MSYS2](https://msys2.github.io/), [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx), and [CUDA](https://developer.nvidia.com/cuda-downloads)
2. Under an "MSYS2 Shell", run:
```bash
$ pacman -S base-devel tar patch make git unzip zip nasm yasm pkg-config mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw-w64-i686-gcc mingw-w64-x86_64-gcc-fortran mingw-w64-i686-gcc-fortran mingw-w64-x86_64-libwinpthread-git mingw-w64-i686-libwinpthread-git
```
3. From the "Visual Studio Tools" found inside the Start menu, open:
- "VS2013 x86 Native Tools Command Prompt" and run `c:\msys64\mingw32_shell.bat` inside
- "VS2013 x64 Native Tools Command Prompt" and run `c:\msys64\mingw64_shell.bat` inside
- Making sure the `set MSYS2_PATH_TYPE=inherit` line is *not* commented out in either of those batch files.
4. Run the "Prerequisites for all platforms" tasks inside the shell
Afterwards the following commands can be used to start the build inside the `javacpp-presets` directory:
```bash
$ bash cppbuild.sh -platform windows-xxx install
$ mvn clean install -Djavacpp.platform=windows-xxx
```
where `windows-xxx` is either `windows-x86` or `windows-x86_64`. Run the builds for `windows-x86` inside the "MINGW32" window, and the ones for `windows-x86_64` in the "MINGW64" one.

176
bbb-screenshare/README.md Executable file
View File

@ -0,0 +1,176 @@
This document contains instructions on how to build your own native libraries, screenshare webstart app, and how to deploy screenshare application.
Building your own native libraries
----------------------------------
Linux (x86 and x86_64)
----------------------
To produce native libraries that can run on the largest possible number of Linux installations out there, it is recommended to build under CentOS 7. This is because it relies on an old enough version of glibc, which nevertheless works for all the libraries found in the JavaCPP Presets, and since newer versions of glibc are backward compatible, all recent distributions of Linux should support the binaries generated. We do not actually need to install CentOS 7 though. Pretty much any recent distribution of Linux comes with a package for [Docker](https://www.docker.com/). It is also possible to map existing directories, for example `/usr/local/lib/bazel` and `/usr/local/cuda` as shown in the steps below, to reuse an existing [Bazel](http://bazel.io/docs/install.html) or [CUDA](https://developer.nvidia.com/cuda-downloads) installation as well as any other set of files for the purpose of the build.
### Preparations
1. Install Docker under, for example, Fedora and Ubuntu, respectively:
```bash
$ sudo yum install docker
$ sudo apt-get install docker.io
```
2. When using SELinux, it might also be necessary to disable temporarily the firewall, for example:
```
$ sudo systemctl stop firewalld
$ sudo systemctl start docker
```
3. Start the container for CentOS 7 (the command might be `docker.io` instead of `docker`):
```bash
$ sudo docker run --privileged -it -v /usr/local/lib/bazel:/usr/local/lib/bazel -v /usr/local/cuda:/usr/local/cuda centos:7 /bin/bash
```
4. Finally, inside the container, we need to install a bunch of things:
```bash
$ ln -s /usr/local/lib/bazel/bin/bazel /usr/local/bin/bazel
$ yum install epel-release
$ yum install clang gcc-c++ gcc-gfortran java-devel maven python numpy swig git file which wget unzip tar bzip2 gzip xz patch make cmake3 perl nasm yasm alsa-lib-devel freeglut-devel gtk2-devel libusb-devel libusb1-devel zlib-devel
$ yum install `rpm -qa | sed s/.x86_64$/.i686/`
```
5. Checkout `https://github.com/bigbluebutton/javacpp-presets` and use branch `min-build-1.2-svc2`
6. cd to `javacpp-presets/ffmpeg`
7. If you want to build SVC2 libraries, you copy `cppbuild.sh.svc2` to `cppbuild.sh`
8. After which the following commands inside the `ffmpeg` dir:
```bash
bash cppbuild.sh -platform linux-xxx install
mvn clean install -Djavacpp.platform=linux-xxx
```
where `linux-xxx` is either `linux-x86` or `linux-x86_64`.
If things go well, copy the resulting jar into `native-libs/ffmpeg-linux-xxx` and sign the jar.
Mac OS X (x86_64)
-----------------
OS X Mavericks (10.9) is the first version of Mac OS X to support C++11 fully and properly, so to preserve your sanity, we do not recommend trying to build or use the JavaCPP Presets on any older versions of Mac OS X.
### Preparations
1. Install [Xcode](https://developer.apple.com/xcode/) and [Homebrew](http://brew.sh/)
2. Ensure the command line tools for Xcode are installed
```bash
$ xcode-select --install
```
3. Run the following commands to install the JDK, among other things Apple left out of Xcode:
```bash
$ brew install caskroom/cask/brew-cask
$ brew cask install cuda java
$ brew install gcc5 swig bazel cmake libusb maven nasm yasm xz pkg-config
```
4. Checkout `https://github.com/bigbluebutton/javacpp-presets` and use branch `min-build-1.2-svc2`
5. cd to `javacpp-presets/ffmpeg`
6. If you want to build SVC2 libraries, you copy `cppbuild.sh.svc2` to `cppbuild.sh`
7. After which the following commands inside the `ffmpeg` dir:
```bash
$ bash cppbuild.sh install
$ mvn clean install
```
If things go well, copy the resulting jar into `native-libs/ffmpeg-macosx-x86_64` and sign the jar.
Windows (x86 and x86_64)
------------------------
Visual Studio Community 2013 is the first free version to have been decently bundled with support for C++11, OpenMP, the Windows SDK, and everything else from Microsoft, so we recommend installing that version of Visual Studio, which consequently requires Windows 7. Still, to run the bash scripts and compile some things that the Microsoft C/C++ Compiler does not support, we need to install manually a few other things.
### Preparations
1. Install the [Java SE Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/), [Maven](https://maven.apache.org/download.cgi), [MSYS2](https://msys2.github.io/), [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx), and [CUDA](https://developer.nvidia.com/cuda-downloads)
2. Under an "MSYS2 Shell", run:
```bash
$ pacman -S base-devel tar patch make git unzip zip nasm yasm pkg-config mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw-w64-i686-gcc mingw-w64-x86_64-gcc-fortran mingw-w64-i686-gcc-fortran mingw-w64-x86_64-libwinpthread-git mingw-w64-i686-libwinpthread-git
```
3. From the "Visual Studio Tools" found inside the Start menu, open:
- "VS2013 x86 Native Tools Command Prompt" and run `c:\msys64\mingw32_shell.bat` inside
- "VS2013 x64 Native Tools Command Prompt" and run `c:\msys64\mingw64_shell.bat` inside
- Making sure the `set MSYS2_PATH_TYPE=inherit` line is *not* commented out in either of those batch files.
4. Run the "Prerequisites for all platforms" tasks inside the shell
5. Checkout `https://github.com/bigbluebutton/javacpp-presets` and use branch `min-build-1.2-svc2`
6. cd to `javacpp-presets/ffmpeg`
7. If you want to build SVC2 libraries, you copy `cppbuild.sh.svc2` to `cppbuild.sh`
8. After which the following commands inside the `ffmpeg` dir:
```bash
$ bash cppbuild.sh -platform windows-xxx install
$ mvn clean install -Djavacpp.platform=windows-xxx
```
where `windows-xxx` is either `windows-x86` or `windows-x86_64`. Run the builds for `windows-x86` inside the "MINGW32" window, and the ones for `windows-x86_64` in the "MINGW64" one.
If things go well, copy the resulting jar into `native-libs/ffmpeg-windows-xxx` and sign the jar.
Signing the jar files
---------------------
To sign the native libraries, cd to the location of the jar. Copy tour cert into the dir and run `sign-jar.sh`. You will be prompted for
your cert file and password of your cert.
Example:
```
cd ffmpeg-linux-x86/svc2
Copy your cert into this directory
./sign-jar.sh
You will be prompted for your cert file and password.
```
The resulting signed jar file will be in `bbb-screenshare/apps/jws/lib`
Aside from the native jar files, you will need to sign the `ffmpeg.jar` found in `jws/signed-jars`. Follow the README doc in that directory.
Building screenshare webstart application
-----------------------------------------
1. Go to `jws/webstart` directory.
2. Copy your cert into the dir.
3. Run `build.sh` and you will be prompted for your cert file and cert password in order to sign the jar file.
Deploying and testing the screenshare application
-------------------------------------------------
1. Go to `app` directory.
2. Edit `src/main/webapp/WEB-INF/screenshare.properties` to point to your server's IP address.
3. Run `deploy.sh` to build the whole application and deploy to your local red5 server.

View File

@ -34,6 +34,7 @@ cd lib
sudo cp -r ~/dev/bigbluebutton/bbb-screenshare/app/jws/lib/* .
cd ..
sudo cp ~/dev/bigbluebutton/bbb-screenshare/app/jws/screenshare.jnlp .
sudo cp ~/dev/bigbluebutton/bbb-screenshare/app/jws/screenshare.jnlp.h264 .
sudo chmod -R 777 /usr/share/red5/webapps/screenshare
sudo chown -R red5:red5 /usr/share/red5/webapps/screenshare

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -16,23 +16,23 @@
</resources>
<resources os="Windows" arch="amd64">
<nativelib href="ffmpeg-windows-x86_64.jar"/>
<nativelib href="ffmpeg-win-x86_64-svc2.jar"/>
</resources>
<resources os="Windows" arch="x86">
<nativelib href="$$jnlpUrl/lib/ffmpeg-windows-x86.jar"/>
<nativelib href="ffmpeg-win-x86-svc2.jar"/>
</resources>
<resources os="Linux" arch="x86_64 amd64">
<nativelib href="ffmpeg-linux-x86_64.jar"/>
<nativelib href="ffmpeg-linux-x86_64-svc2.jar"/>
</resources>
<resources os="Linux" arch="x86 i386 i486 i586 i686">
<nativelib href="ffmpeg-linux-x86.jar"/>
<nativelib href="ffmpeg-linux-x86-svc2.jar"/>
</resources>
<resources os="Mac OS X">
<nativelib href="ffmpeg-macosx-x86_64.jar"/>
<nativelib href="ffmpeg-macosx-x86_64-svc2.jar"/>
</resources>
<application-desc
@ -45,6 +45,7 @@
<argument>$$fullScreen</argument>
<argument>$$codecOptions</argument>
<argument>$$session</argument>
<argument>$$useH264</argument>
<argument>$$errorMessage</argument>
</application-desc>
<security><all-permissions/></security>

View File

@ -16,23 +16,23 @@
</resources>
<resources os="Windows" arch="amd64">
<nativelib href="ffmpeg-windows-x86_64.jar"/>
<nativelib href="ffmpeg-win-x86_64-h264.jar"/>
</resources>
<resources os="Windows" arch="x86">
<nativelib href="$$jnlpUrl/lib/ffmpeg-windows-x86.jar"/>
<nativelib href="ffmpeg-win-x86-h264.jar"/>
</resources>
<resources os="Linux" arch="x86_64 amd64">
<nativelib href="ffmpeg-linux-x86_64.jar"/>
<nativelib href="ffmpeg-linux-x86_64-h264.jar"/>
</resources>
<resources os="Linux" arch="x86 i386 i486 i586 i686">
<nativelib href="ffmpeg-linux-x86.jar"/>
<nativelib href="ffmpeg-linux-x86-h264.jar"/>
</resources>
<resources os="Mac OS X">
<nativelib href="ffmpeg-macosx-x86_64.jar"/>
<nativelib href="ffmpeg-macosx-x86_64-h264.jar"/>
</resources>
<application-desc
@ -45,6 +45,7 @@
<argument>$$fullScreen</argument>
<argument>$$codecOptions</argument>
<argument>$$session</argument>
<argument>$$useH264</argument>
<argument>$$errorMessage</argument>
</application-desc>
<security><all-permissions/></security>

View File

@ -41,6 +41,8 @@ import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.parsers.*;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.xml.sax.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
@ -314,12 +316,23 @@ public class JnlpFileHandler {
if (sInfo == null) {
errorMessage = "ERROR_GETTING_INFO_USING_TOKEN";
} else {
publishUrl = sInfo.publishUrl;
System.out.println("********* URL=" + sInfo.publishUrl);
if (sInfo.tunnel) {
publishUrl = sInfo.publishUrl.replaceFirst("rtmp","rtmpt");
} else {
publishUrl = sInfo.publishUrl;
}
streamId = sInfo.streamId;
session = sInfo.session;
}
System.out.println("********* URL=" + publishUrl);
String jnlpUrl = configurator.getJnlpUrl();
Boolean useH264 = configurator.isUseH264();
String codecOptions = configurator.getCodecOptions();
log.debug("Codec Options = [" + codecOptions + "]");
@ -337,6 +350,7 @@ public class JnlpFileHandler {
jnlpTemplate = substitute(jnlpTemplate, "$$session", session);
jnlpTemplate = substitute(jnlpTemplate, "$$streamId", streamId);
jnlpTemplate = substitute(jnlpTemplate, "$$codecOptions", codecOptions);
jnlpTemplate = substitute(jnlpTemplate, "$$useH264", useH264.toString());
jnlpTemplate = substitute(jnlpTemplate, "$$errorMessage", errorMessage);
// fix for 5039951: Add $$hostname macro
jnlpTemplate = substitute(jnlpTemplate, "$$hostname", request.getServerName());

View File

@ -11,7 +11,7 @@ public interface IScreenShareApplication {
Boolean recordStream(String meetingId, String streamId);
void isScreenSharing(String meetingId, String userId);
void requestShareToken(String meetingId, String userId, Boolean record);
void requestShareToken(String meetingId, String userId, Boolean record, Boolean tunnel);
void startShareRequest(String meetingId, String userId, String session);
void pauseShareRequest(String meetingId, String userId, String streamId);
void restartShareRequest(String meetingId, String userId);

View File

@ -5,10 +5,12 @@ public class ScreenShareInfo {
public final String session;
public final String streamId;
public final String publishUrl;
public final Boolean tunnel;
public ScreenShareInfo(String session, String publishUrl, String streamId) {
public ScreenShareInfo(String session, String publishUrl, String streamId, Boolean tunnel) {
this.session = session;
this.streamId = streamId;
this.publishUrl = publishUrl;
this.tunnel = tunnel;
}
}

View File

@ -34,8 +34,8 @@ public class Red5AppHandler {
app.isScreenSharing(meetingId, userId);
}
public void requestShareToken(String meetingId, String userId, Boolean record) {
app.requestShareToken(meetingId, userId, record);
public void requestShareToken(String meetingId, String userId, Boolean record, Boolean tunnel) {
app.requestShareToken(meetingId, userId, record, tunnel);
}
public void startShareRequest(String meetingId, String userId, String session) {

View File

@ -133,12 +133,13 @@ public class Red5AppService {
Boolean record = (Boolean) msg.get("record");
String meetingId = Red5.getConnectionLocal().getScope().getName();
String userId = (String) Red5.getConnectionLocal().getAttribute("USERID");
Boolean tunnel = (Boolean) msg.get("tunnel");
if (log.isDebugEnabled()) {
log.debug("Received startShareRequest for meetingId=" + meetingId + " from user=" + userId);
}
handler.requestShareToken(meetingId, userId, record);
handler.requestShareToken(meetingId, userId, record, tunnel);
}
public void stopShareRequest(Map<String, Object> msg) {

Some files were not shown because too many files have changed in this diff Show More