Merge remote-tracking branch 'upstream/bbb-2x-mconf' into improve-client-annotation-edit

This commit is contained in:
Chad Pilkey 2017-08-22 19:11:16 -04:00
commit c90c6491d9
152 changed files with 14553 additions and 1599 deletions

View File

@ -15,7 +15,7 @@ trait ClientToServerLatencyTracerMsgHdlr {
val envelope = BbbCoreEnvelope(ServerToClientLatencyTracerMsg.NAME, routing)
val header = BbbClientMsgHeader(ServerToClientLatencyTracerMsg.NAME, props.meetingProp.intId, msg.header.userId)
val body = ServerToClientLatencyTracerMsgBody(msg.body.timestamp, msg.body.prettyTimestamp, msg.body.tzOffset, msg.body.senderId)
val body = ServerToClientLatencyTracerMsgBody(msg.body.timestampUTC, msg.body.rtt, msg.body.senderId)
val event = ServerToClientLatencyTracerMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)

View File

@ -12,8 +12,16 @@ object Webcams {
def findAll(webcams: Webcams): Vector[WebcamStream] = webcams.toVector
def addWebcamBroadcastStream(webcams: Webcams, webcamStream: WebcamStream): Option[WebcamStream] = {
webcams.save(webcamStream)
Some(webcamStream)
findWithStreamId(webcams, webcamStream.streamId) match {
case Some(p) => {
None
}
case None => {
webcams.save(webcamStream)
Some(webcamStream)
}
}
}
def removeWebcamBroadcastStream(webcams: Webcams, streamId: String): Option[WebcamStream] = {

View File

@ -125,6 +125,8 @@ class ReceivedJsonMsgHandlerActor(
routeVoiceMsg[UserConnectedToGlobalAudioMsg](envelope, jsonNode)
case UserDisconnectedFromGlobalAudioMsg.NAME =>
routeVoiceMsg[UserDisconnectedFromGlobalAudioMsg](envelope, jsonNode)
case MuteMeetingCmdMsg.NAME =>
routeGenericMsg[MuteMeetingCmdMsg](envelope, jsonNode)
// Breakout rooms
case BreakoutRoomsListMsg.NAME =>

View File

@ -129,6 +129,8 @@ class MeetingActor(
var state = new MeetingState2x(None, inactivityTracker, expiryTracker)
var lastRttTestSentOn = System.currentTimeMillis()
/*******************************************************************/
//object FakeTestData extends FakeTestData
//FakeTestData.createFakeUsers(liveMeeting)
@ -357,6 +359,29 @@ class MeetingActor(
val (newState2, expireReason2) = ExpiryTrackerHelper.processMeetingExpiryAudit(outGW, eventBus, liveMeeting, state)
state = newState2
expireReason2 foreach (reason => log.info("Meeting {} expired with reason {}", props.meetingProp.intId, reason))
sendRttTraceTest()
}
def sendRttTraceTest(): Unit = {
val now = System.currentTimeMillis()
def buildDoLatencyTracerMsg(meetingId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(DoLatencyTracerMsg.NAME, routing)
val body = DoLatencyTracerMsgBody(now)
val header = BbbClientMsgHeader(DoLatencyTracerMsg.NAME, meetingId, "not-used")
val event = DoLatencyTracerMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
if (now - lastRttTestSentOn > 60000) {
lastRttTestSentOn = now
val event = buildDoLatencyTracerMsg(liveMeeting.props.meetingProp.intId)
outGW.send(event)
}
}
def handleExtendMeetingDuration(msg: ExtendMeetingDuration) {

View File

@ -27,8 +27,8 @@ trait MuteAllExceptPresentersCmdMsgHdlr {
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
if (!vu.listenOnly) {
Users2x.findWithIntId(liveMeeting.users2x, vu.intId) match {
case Some(u) => if (!u.presenter) muteUserInVoiceConf(vu)
case None => muteUserInVoiceConf(vu)
case Some(u) => if (!u.presenter) muteUserInVoiceConf(vu, muted)
case None => muteUserInVoiceConf(vu, muted)
}
}
}
@ -50,12 +50,12 @@ trait MuteAllExceptPresentersCmdMsgHdlr {
BbbCommonEnvCoreMsg(envelope, event)
}
def muteUserInVoiceConf(vu: VoiceUserState): Unit = {
def muteUserInVoiceConf(vu: VoiceUserState, mute: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, vu.intId)
val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, props.meetingProp.intId)
val body = MuteUserInVoiceConfSysMsgBody(props.voiceProp.voiceConf, vu.voiceUserId, true)
val body = MuteUserInVoiceConfSysMsgBody(props.voiceProp.voiceConf, vu.voiceUserId, mute)
val event = MuteUserInVoiceConfSysMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)

View File

@ -24,12 +24,12 @@ trait MuteMeetingCmdMsgHdlr {
BbbCommonEnvCoreMsg(envelope, event)
}
def muteUserInVoiceConf(vu: VoiceUserState): Unit = {
def muteUserInVoiceConf(vu: VoiceUserState, mute: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, vu.intId)
val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, props.meetingProp.intId)
val body = MuteUserInVoiceConfSysMsgBody(props.voiceProp.voiceConf, vu.voiceUserId, true)
val body = MuteUserInVoiceConfSysMsgBody(props.voiceProp.voiceConf, vu.voiceUserId, mute)
val event = MuteUserInVoiceConfSysMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
@ -48,13 +48,9 @@ trait MuteMeetingCmdMsgHdlr {
outGW.send(meetingMutedEvent)
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { u =>
muteUserInVoiceConf(u)
}
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
if (!vu.listenOnly) {
muteUserInVoiceConf(vu)
muteUserInVoiceConf(vu, muted)
}
}
}

View File

@ -53,7 +53,7 @@ libraryDependencies ++= {
libraryDependencies += "org.bigbluebutton" % "bbb-common-message_2.12" % "0.0.19-SNAPSHOT"
libraryDependencies += "org.bigbluebutton" % "bbb-fsesl-client" % "0.0.4"
libraryDependencies += "org.bigbluebutton" % "bbb-fsesl-client" % "0.0.5"
// https://mvnrepository.com/artifact/org.scala-lang/scala-library
libraryDependencies += "org.scala-lang" % "scala-library" % "2.12.2"

View File

@ -5,12 +5,15 @@ case class AnnotationVO(id: String, status: String, annotationType: String,
object ClientToServerLatencyTracerMsg { val NAME = "ClientToServerLatencyTracerMsg" }
case class ClientToServerLatencyTracerMsg(header: BbbClientMsgHeader, body: ClientToServerLatencyTracerMsgBody) extends StandardMsg
case class ClientToServerLatencyTracerMsgBody(timestamp: Long, prettyTimestamp: String, tzOffset: Long, senderId: String)
case class ClientToServerLatencyTracerMsgBody(timestampUTC: Long, rtt: Long, senderId: String)
object ServerToClientLatencyTracerMsg { val NAME = "ServerToClientLatencyTracerMsg" }
case class ServerToClientLatencyTracerMsg(header: BbbClientMsgHeader, body: ServerToClientLatencyTracerMsgBody) extends BbbCoreMsg
case class ServerToClientLatencyTracerMsgBody(timestamp: Long, prettyTimestamp: String, tzOffset: Long, senderId: String)
case class ServerToClientLatencyTracerMsgBody(timestampUTC: Long, rtt: Long, senderId: String)
object DoLatencyTracerMsg { val NAME = "DoLatencyTracerMsg" }
case class DoLatencyTracerMsg(header: BbbClientMsgHeader, body: DoLatencyTracerMsgBody) extends BbbCoreMsg
case class DoLatencyTracerMsgBody(timestampUTC: Long)
object ClearWhiteboardEvtMsg {
val NAME = "ClearWhiteboardEvtMsg"

View File

@ -57,11 +57,22 @@ libraryDependencies += "commons-io" % "commons-io" % "2.4"
libraryDependencies += "org.apache.commons" % "commons-pool2" % "2.3"
libraryDependencies += "commons-io" % "commons-io" % "2.4"
libraryDependencies += "com.zaxxer" % "nuprocess" % "1.1.0"
libraryDependencies += "com.artofsolving" % "jodconverter" % "2.2.1"
libraryDependencies += "org.openoffice" % "unoil" % "3.2.1"
libraryDependencies += "org.openoffice" % "ridl" % "3.2.1"
libraryDependencies += "org.openoffice" % "juh" % "3.2.1"
libraryDependencies += "org.openoffice" % "jurt" % "3.2.1"
// https://mvnrepository.com/artifact/org.jodconverter/jodconverter-core
libraryDependencies += "org.jodconverter" % "jodconverter-core" % "4.0.0-RELEASE"
// https://mvnrepository.com/artifact/org.libreoffice/unoil
libraryDependencies += "org.libreoffice" % "unoil" % "5.3.2"
// https://mvnrepository.com/artifact/org.libreoffice/ridl
libraryDependencies += "org.libreoffice" % "ridl" % "5.3.2"
// https://mvnrepository.com/artifact/org.libreoffice/juh
libraryDependencies += "org.libreoffice" % "juh" % "5.3.2"
// https://mvnrepository.com/artifact/org.libreoffice/jurt
libraryDependencies += "org.libreoffice" % "jurt" % "5.3.2"
libraryDependencies += "org.apache.poi" % "poi-ooxml" % "3.15"

View File

@ -24,23 +24,18 @@ import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
import org.bigbluebutton.presentation.PageConverter;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.artofsolving.jodconverter.*;
import com.artofsolving.jodconverter.openoffice.connection.*;
import com.artofsolving.jodconverter.openoffice.converter.*;
import org.jodconverter.OfficeDocumentConverter;
public class Office2PdfPageConverter implements PageConverter {
public class Office2PdfPageConverter {
private static Logger log = LoggerFactory.getLogger(Office2PdfPageConverter.class);
public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){
SocketOpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres,
final OfficeDocumentConverter converter){
try {
connection.connect();
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
@ -50,18 +45,9 @@ public class Office2PdfPageConverter implements PageConverter {
String logStr = gson.toJson(logData);
log.info("-- analytics -- " + logStr);
DefaultDocumentFormatRegistry registry = new DefaultDocumentFormatRegistry();
OpenOfficeDocumentConverter converter = new OpenOfficeDocumentConverter(connection, registry);
DocumentFormat pdf = registry.getFormatByFileExtension("pdf");
Map<String, Object> pdfOptions = new HashMap<String, Object>();
pdfOptions.put("ReduceImageResolution", Boolean.TRUE);
pdfOptions.put("MaxImageResolution", Integer.valueOf(300));
pdf.setExportOption(DocumentFamily.TEXT, "FilterData", pdfOptions);
converter.convert(presentationFile, output, pdf);
connection.disconnect();
final long startTime = System.currentTimeMillis();
converter.convert(presentationFile, output);
if (output.exists()) {
return true;
} else {
@ -76,8 +62,7 @@ public class Office2PdfPageConverter implements PageConverter {
return false;
}
} catch(Exception e) {
} catch (Exception e) {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());

View File

@ -31,10 +31,24 @@ import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jodconverter.OfficeDocumentConverter;
import org.jodconverter.office.DefaultOfficeManagerBuilder;
import org.jodconverter.office.OfficeException;
import org.jodconverter.office.OfficeManager;
public class OfficeToPdfConversionService {
private static Logger log = LoggerFactory.getLogger(OfficeToPdfConversionService.class);
private OfficeDocumentValidator officeDocumentValidator;
private final OfficeManager officeManager;
private final OfficeDocumentConverter documentConverter;
public OfficeToPdfConversionService() {
final DefaultOfficeManagerBuilder configuration = new DefaultOfficeManagerBuilder();
configuration.setPortNumber(8100);
officeManager = configuration.build();
documentConverter = new OfficeDocumentConverter(officeManager);
}
/*
* Convert the Office document to PDF. If successful, update
@ -97,8 +111,8 @@ public class OfficeToPdfConversionService {
private boolean convertOfficeDocToPdf(UploadedPresentation pres,
File pdfOutput) {
PageConverter converter = new Office2PdfPageConverter();
return converter.convert(pres.getUploadedFile(), pdfOutput, 0, pres);
Office2PdfPageConverter converter = new Office2PdfPageConverter();
return converter.convert(pres.getUploadedFile(), pdfOutput, 0, pres, documentConverter);
}
private void makePdfTheUploadedFileAndSetStepAsSuccess(UploadedPresentation pres, File pdf) {
@ -109,4 +123,22 @@ public class OfficeToPdfConversionService {
public void setOfficeDocumentValidator(OfficeDocumentValidator v) {
officeDocumentValidator = v;
}
public void start() {
try {
officeManager.start();
} catch (OfficeException e) {
log.error(e.getMessage());
}
}
public void stop() {
try {
officeManager.stop();
} catch (OfficeException e) {
log.error(e.getMessage());
}
}
}

View File

@ -6,7 +6,7 @@ description := "BigBlueButton custom FS-ESL client built on top of FS-ESL Java l
organization := "org.bigbluebutton"
version := "0.0.4"
version := "0.0.5"
// We want to have our jar files in lib_managed dir.
// This way we'll have the right path when we import

View File

@ -500,6 +500,9 @@ public class Client
} else if (eventFunc.equals("conference_loop_input")) {
listener.conferenceEventAction(uniqueId, confName, confSize, eventHeaders.get("Action"), event);
return;
} else if (eventFunc.equals("stop_talking_handler")) {
listener.conferenceEventAction(uniqueId, confName, confSize, eventHeaders.get("Action"), event);
return;
} else {
/* StringBuilder sb = new StringBuilder("");
sb.append("\n");

View File

@ -199,7 +199,7 @@ class Screenshare(val sessionManager: ScreenshareManager,
}
private def trimUserId(userId: String):Option[String] = {
private def trimUserId2(userId: String):Option[String] = {
// A userId has the format "abc123_1" where "_1" refers to the number
// of times the user rejoins due to disconnect. We strip off that number
// to get the real userId so we can map the screen sharing session to the
@ -217,19 +217,17 @@ class Screenshare(val sessionManager: ScreenshareManager,
private def handleUserDisconnected(msg: UserDisconnected) {
if (log.isDebugEnabled) {
log.debug("Received UserDisconnected for meetingId=[" + msg.meetingId +
"] userId=[" + trimUserId(msg.userId) + "], sss=" + screenShareSession + ",curPres=" + currentPresenterId)
"] userId=[" + msg.userId + "], sss=" + screenShareSession + ",curPres=" + currentPresenterId)
}
for {
sss <- screenShareSession
presenterId <- currentPresenterId
curPresenterId <- trimUserId(presenterId)
userId <- trimUserId(msg.userId)
curPresenterId = currentPresenterId.get
} yield {
if (log.isDebugEnabled) {
log.debug("Received UserDisconnected for curPresenterId=[" + curPresenterId + "] userId=[" + userId + "]")
log.debug("Received UserDisconnected for curPresenterId=[" + curPresenterId + "] userId=[" + msg.userId + "]")
}
if (userId == curPresenterId) {
if (msg.userId == curPresenterId) {
log.info("STOPPING UserDisconnected for curPresenterId=[" + curPresenterId + "] session=[" + sss + "]")
stopScreenSharing(sss, PRESENTER_DISCONNECTED_REASON)
}
@ -243,11 +241,9 @@ class Screenshare(val sessionManager: ScreenshareManager,
for {
sss <- screenShareSession
presenterId <- currentPresenterId
curPresenterId <- trimUserId(presenterId)
userId <- trimUserId(msg.userId)
curPresenterId = currentPresenterId.get
} yield {
if (userId == curPresenterId) {
if (msg.userId == curPresenterId) {
log.info("STOPPING. User auto-reconnected for curPresenterId=[" + curPresenterId + "], session=[" + sss + "]")
stopScreenSharing(sss, PRESENTER_AUTO_RECONNECTED_REASON)
}
@ -448,7 +444,7 @@ class Screenshare(val sessionManager: ScreenshareManager,
val streamId = generateStreamId(sessionToken)
val token = streamId
val userId = trimUserId(msg.userId).getOrElse(msg.userId)
val userId = msg.userId
val session = ActiveSession(this, bus, meetingId, streamId, token, record, userId, tunnel)
activeSession = Some(session)
sessionStartedTimestamp = TimeUtil.currentMonoTimeInSeconds()
@ -475,7 +471,7 @@ class Screenshare(val sessionManager: ScreenshareManager,
val streamId = generateStreamId(sessionToken)
val token = streamId
val userId = trimUserId(msg.userId).getOrElse(msg.userId)
val userId = msg.userId
tunnel = msg.tunnel
val session = ActiveSession(this, bus, meetingId, streamId, token, msg.record, userId, tunnel)

View File

@ -971,9 +971,8 @@ mx|Panel {
//------------------------------
*/
presentation|UploadedPresentationRenderer {
iconSave : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Save_File");
iconSaveWhite : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Save_File_White");
.presentationUploadDownloadButtonStyle {
icon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Save_File");
}
.presentationWindowControlsStyle {
@ -993,7 +992,6 @@ presentation|UploadedPresentationRenderer {
textRollOverColor : #3F3F41;
textSelectedColor : #3F3F41;
borderColor : #B9BABC;
cornerRadius : 5;
icon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Trash");
}
@ -1066,12 +1064,7 @@ presentation|UploadedPresentationRenderer {
disabledIcon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Go_Disabled");
}
.presentationNameLabelOver {
color : #FFFFFF;
fontSize : 14;
}
.presentationNameLabelUp {
.presentationNameLabel {
color : #05172A;
fontSize : 14;
}
@ -1229,6 +1222,20 @@ sharednotes|SharedNotesRichTextEditor {
icon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Format_Text");
}
/*
//------------------------------
// Slider
//------------------------------
*/
mx|Slider {
dataTipStyleName : "sliderDataTipStyleName";
}
.sliderDataTipStyleName {
color : #05172A;
}
/*
//------------------------------
// SuperTabNavigator
@ -1516,6 +1523,7 @@ whiteboard|WhiteboardToolbar {
backgroundColor : #CFD4DB;
borderColor : #CFD4DB;
borderStyle : solid;
horizontalAlign : center;
borderThickness : 1;
cornerRadius : 4;
paddingBottom : 0;

View File

@ -279,7 +279,7 @@ bbb.fileupload.okCancelBtn.toolTip = Close the File Upload dialog box
bbb.fileupload.genThumbText = Generating thumbnails..
bbb.fileupload.progBarLbl = Progress:
bbb.fileupload.fileFormatHint = Upload any office document or Portable Document Format (PDF) file. For best results upload PDF.
bbb.fileupload.letUserDownload = Let the users download the presentation
bbb.fileupload.letUserDownload = Enable download of presentation
bbb.fileupload.letUserDownload.tooltip = Check here if you want the other users to download your presentation
bbb.filedownload.title = Download the Presentations
bbb.filedownload.fileLbl = Choose File to Download:
@ -517,6 +517,9 @@ bbb.notes.cmpColorPicker.toolTip = Text Color
bbb.notes.saveBtn = Save
bbb.notes.saveBtn.toolTip = Save Note
bbb.sharedNotes.title = Shared notes
bbb.sharedNotes.title.1 = Shared notes 1
bbb.sharedNotes.title.2 = Shared notes 2
bbb.sharedNotes.title.3 = Shared notes 3
bbb.sharedNotes.quickLink.label = Shared notes Window
bbb.sharedNotes.name = Note name
bbb.sharedNotes.save.toolTip = Save notes to file
@ -530,6 +533,7 @@ bbb.sharedNotes.redo.toolTip = Redo modification
bbb.sharedNotes.toolbar.toolTip = Text formatting toolbar
bbb.sharedNotes.additionalNotes.closeWarning.title = Closing shared notes
bbb.sharedNotes.additionalNotes.closeWarning.message = This action will destroy the notes on this window for everyone, and there's no way to undo. Are you sure you want to close these notes?
bbb.sharedNotes.pastewarning.title = Shared Notes Paste Warning
bbb.settings.deskshare.instructions = Choose Allow on the prompt that pops up to check that desktop sharing is working properly for you
bbb.settings.deskshare.start = Check Desktop Sharing
bbb.settings.voice.volume = Microphone Activity
@ -599,7 +603,7 @@ bbb.accessibility.chat.chatBox.navigatedLatest = You have navigated to the lates
bbb.accessibility.chat.chatBox.navigatedLatestRead = You have navigated to the most recent message you have read.
bbb.accessibility.chat.chatwindow.input = Chat input
bbb.accessibility.chat.chatwindow.audibleChatNotification = Audible Chat Notification
bbb.accessibility.chat.chatwindow.publicChatOptions = Public Chat Options
bbb.accessibility.chat.initialDescription = Please use the arrow keys to navigate through chat messages.
bbb.accessibility.notes.notesview.input = Notes input

View File

@ -1,6 +1,7 @@
<?xml version="1.0" ?>
<config>
<localeversion suppressWarning="false">0.9.0</localeversion>
<version>VERSION</version>
<help url="http://HOST/help.html"/>
<javaTest url="http://HOST/testjava.html"/>
<porttest host="HOST" application="video/portTest" timeout="10000"/>
@ -8,7 +9,7 @@
<application uri="rtmp://HOST/bigbluebutton" host="http://HOST/bigbluebutton/api/enter"/>
<language userSelectionEnabled="true" />
<skinning enabled="true" url="http://HOST/client/branding/css/V2Theme.css.swf?v=VERSION" />
<branding logo="logo.swf" copyright="&#169; 2017 &lt;u&gt;&lt;a href=&quot;http://www.bigbluebutton.org&quot; target=&quot;_blank&quot;&gt;http://www.bigbluebutton.org&lt;/a&gt;&lt;/u&gt;" background="" toolbarColor="" />
<branding logo="logo.swf" copyright="&#169; 2017 &lt;u&gt;&lt;a href=&quot;http://HOST/home.html&quot; target=&quot;_blank&quot;&gt;BigBlueButton Inc.&lt;/a&gt;&lt;/u&gt; (build {0})" background="" toolbarColor="" />
<shortcutKeys showButton="true" />
<browserVersions chrome="CHROME_VERSION" firefox="FIREFOX_VERSION" flash="FLASH_VERSION" java="1.7.0_51" />
<layout showLogButton="false" defaultLayout="bbb.layout.name.defaultlayout"
@ -17,7 +18,7 @@
showRecordingNotification="true" logoutOnStopRecording="false"/>
<meeting muteOnStart="false" />
<breakoutRooms enabled="true" record="false" />
<logging enabled="true" target="trace" level="info" format="{dateUTC} {time} :: {name} :: [{logLevel}] {message}" uri="http://HOST/log" logPattern=".*"/>
<logging enabled="true" logTarget="trace" level="info" format="{dateUTC} {time} :: {name} :: [{logLevel}] {message}" uri="http://HOST/log" logPattern=".*"/>
<lock disableCam="false" disableMic="false" disablePrivateChat="false"
disablePublicChat="false" lockedLayout="false" lockOnJoin="true" lockOnJoinConfigurable="false"/>
@ -129,7 +130,9 @@
enableMultipleNotes="true"
toolbarVisibleByDefault="false"
showToolbarButton="true"
fontSize="12"
fontSize="14"
maxPasteLength="1024"
maxNoteLength="5120"
/>
<!--

View File

@ -98,7 +98,7 @@
var fillContent = function(){
var content = document.getElementById("content");
if (content) {
content.innerHTML = '<object type="application/x-shockwave-flash" id="BigBlueButton" name="BigBlueButton" tabindex="0" data="BigBlueButton.swf?v=VERSION" style="position: relative; top: 0.5px;" width="100%" height="100%" align="middle"><param name="quality" value="high"><param name="bgcolor" value="#FFFFFF"><param name="allowfullscreen" value="true"><param name="wmode" value="window"><param name="allowscriptaccess" value="true"><param name="seamlesstabbing" value="true"></object>';
content.innerHTML = '<object type="application/x-shockwave-flash" id="BigBlueButton" name="BigBlueButton" tabindex="0" data="BigBlueButton.swf?v=VERSION" style="position: relative; top: 0.5px;" width="100%" height="100%" align="middle"><param name="quality" value="high"><param name="bgcolor" value="#FFFFFF"><param name="allowfullscreen" value="true"><param name="allowfullscreeninteractive" value="true"><param name="wmode" value="window"><param name="allowscriptaccess" value="true"><param name="seamlesstabbing" value="true"></object>';
}
};
} else {

View File

@ -1,20 +1,20 @@
<?xml version="1.0"?>
<layouts>
<layout name="bbb.layout.name.defaultlayout" default="true">
<window name="UsersWindow" width="0.25" height="0.539560439560439" x="0.015625" y="0.028571428571429" />
<window name="VideoDock" width="0.25" height="0.373385989010990" x="0.015625" y="0.598042582417582" />
<window name="PresentationWindow" width="0.4375" height="0.942857142857143" x="0.28125" y="0.028571428571429" />
<window name="ChatWindow" width="0.25" height="0.942857142857143" x="0.734375" y="0.028571428571429" />
<window name="UsersWindow" width="0.19" height="0.539560439560439" x="0.0078125" y="0.014285714285714" />
<window name="VideoDock" width="0.19" height="0.409769917582418" x="0.0078125" y="0.575944368131867" />
<window name="PresentationWindow" width="0.5575" height="0.971428571428571" x="0.205625" y="0.014285714285714" />
<window name="ChatWindow" width="0.22125" height="0.971428571428571" x="0.7709375" y="0.014285714285714" />
<window name="SharedNotesWindow" hidden="true" />
<window name="BroadcastWindow" hidden="true" />
<window name="CaptionWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.closedcaption">
<window name="UsersWindow" width="0.25" height="0.539560439560439" x="0.015625" y="0.028571428571429" />
<window name="VideoDock" width="0.25" height="0.373385989010990" x="0.015625" y="0.598042582417582" />
<window name="PresentationWindow" width="0.4375" height="0.539560439560439" x="0.28125" y="0.028571428571429" />
<window name="CaptionWindow" width="0.4375" height="0.373385989010990" x="0.28125" y="0.598042582417582" />
<window name="ChatWindow" width="0.25" height="0.942857142857143" x="0.734375" y="0.028571428571429" />
<window name="UsersWindow" width="0.19" height="0.539560439560439" x="0.0078125" y="0.014285714285714" />
<window name="VideoDock" width="0.19" height="0.409769917582418" x="0.0078125" y="0.575944368131867" />
<window name="PresentationWindow" width="0.5575" height="0.539560439560439" x="0.205625" y="0.014285714285714" />
<window name="CaptionWindow" width="0.5575" height="0.409769917582418" x="0.205625" y="0.575944368131867" />
<window name="ChatWindow" width="0.22125" height="0.971428571428571" x="0.7709375" y="0.014285714285714" />
<window name="SharedNotesWindow" hidden="true" />
<window name="BroadcastWindow" hidden="true" />
</layout>
@ -28,38 +28,38 @@
<window name="CaptionWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.webcamsfocus">
<window name="VideoDock" width="0.641393813314037" height="0.942857142857143" x="0.015625" y="0.028571428571429" />
<window name="ChatWindow" width="0.311731186685963" height="0.457142857142857" x="0.672643813314037" y="0.028571428571429" />
<window name="PresentationWindow" width="0.311731186685963" height="0.457142857142857" x="0.672643813314037" y="0.514285714285714" />
<window name="VideoDock" width="0.649206313314037" height="0.971428571428571" x="0.0078125" y="0.014285714285714" />
<window name="ChatWindow" width="0.335168686685963" height="0.478571428571429" x="0.664831313314037" y="0.014285714285714" />
<window name="PresentationWindow" width="0.335168686685963" height="0.478571428571429" x="0.664831313314037" y="0.507142857142857" />
<window name="CaptionWindow" hidden="true" />
<window name="BroadcastWindow" hidden="true" />
<window name="UsersWindow" hidden="true" />
<window name="SharedNotesWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.presentfocus">
<window name="PresentationWindow" width="0.641393813314037" height="0.942857142857143" x="0.015625" y="0.028571428571429" />
<window name="ChatWindow" width="0.311731186685963" height="0.457142857142857" x="0.672643813314037" y="0.028571428571429" />
<window name="VideoDock" width="0.311731186685963" height="0.457142857142857" x="0.672643813314037" y="0.514285714285714" />
<window name="PresentationWindow" width="0.649206313314037" height="0.971428571428571" x="0.0078125" y="0.014285714285714" />
<window name="ChatWindow" width="0.335168686685963" height="0.478571428571429" x="0.664831313314037" y="0.014285714285714" />
<window name="VideoDock" width="0.335168686685963" height="0.478571428571429" x="0.664831313314037" y="0.507142857142857" />
<window name="CaptionWindow" hidden="true" />
<window name="SharedNotesWindow" hidden="true" />
<window name="BroadcastWindow" hidden="true" />
<window name="UsersWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.lectureassistant">
<window name="UsersWindow" width="0.25" height="0.942857142857143" x="0.015625" y="0.028571428571429" />
<window name="ChatWindow" width="0.4375" height="0.942857142857143" x="0.28125" y="0.028571428571429" />
<window name="PresentationWindow" width="0.25" height="0.457142857142857" x="0.7343758" y="0.028571428571429" />
<window name="VideoDock" width="0.25" height="0.457142857142857" x="0.734375" y="0.514285714285714" />
<window name="UsersWindow" width="0.19" height="0.971428571428571" x="0.0078125" y="0.014285714285714" />
<window name="ChatWindow" width="0.5575" height="0.971428571428571" x="0.205625" y="0.014285714285714" />
<window name="PresentationWindow" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.014285714285714" />
<window name="VideoDock" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.507142857142857" />
<window name="SharedNotesWindow" hidden="true" />
<window name="BroadcastWindow" hidden="true" />
<window name="CaptionWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.lecture">
<window name="UsersWindow" width="0.25" height="0.6780487804878049" x="0" y="0" order="3"/>
<window name="SharedNotesWindow" width="0.25" height="0.3073170731707317" x="0" y="0.6861788617886179" order="4"/>
<window name="PresentationWindow" width="0.4888030888030888" height="0.9934959349593496" x="0.21853281853281853" y="0" order="1"/>
<window name="ChatWindow" width="0.25" height="0.5756097560975609" x="0.7104247104247104" y="0" order="0"/>
<window name="VideoDock" width="0.25" height="0.4113821138211382" x="0.7104247104247104" y="0.5804878048780487" order="2"/>
<window name="UsersWindow" width="0.19" height="0.539560439560439" x="0.0078125" y="0.014285714285714" order="3"/>
<window name="SharedNotesWindow" width="0.19" height="0.409769917582418" x="0.0078125" y="0.575944368131867" order="4"/>
<window name="PresentationWindow" width="0.5575" height="0.971428571428571" x="0.205625" y="0.014285714285714" />
<window name="ChatWindow" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.014285714285714" order="2"/>
<window name="VideoDock" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.507142857142857" order="0"/>
<window name="BroadcastWindow" hidden="true"/>
<window name="CaptionWindow" hidden="true"/>
</layout>
@ -73,20 +73,20 @@
<window name="CaptionWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.lecture" role="moderator">
<window name="UsersWindow" width="0.25" height="0.942857142857143" x="0.015625" y="0.028571428571429" />
<window name="ChatWindow" width="0.4375" height="0.942857142857143" x="0.28125" y="0.028571428571429" />
<window name="PresentationWindow" width="0.25" height="0.457142857142857" x="0.7343758" y="0.028571428571429" />
<window name="VideoDock" width="0.25" height="0.457142857142857" x="0.734375" y="0.514285714285714" />
<window name="UsersWindow" width="0.19" height="0.971428571428571" x="0.0078125" y="0.014285714285714" />
<window name="ChatWindow" width="0.5575" height="0.971428571428571" x="0.205625" y="0.014285714285714" />
<window name="PresentationWindow" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.014285714285714" />
<window name="VideoDock" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.507142857142857" />
<window name="SharedNotesWindow" hidden="true" />
<window name="BroadcastWindow" hidden="true" />
<window name="CaptionWindow" hidden="true" />
</layout>
<layout name="bbb.layout.name.sharednotes">
<window name="UsersWindow" width="0.25" height="0.539560439560439" x="0.015625" y="0.028571428571429" order="3"/>
<window name="SharedNotesWindow" width="0.25" height="0.373385989010990" x="0.015625" y="0.598042582417582" order="4"/>
<window name="PresentationWindow" width="0.4375" height="0.942857142857143" x="0.28125" y="0.028571428571429" />
<window name="ChatWindow" width="0.25" height="0.457142857142857" x="0.7343758" y="0.028571428571429" order="0"/>
<window name="VideoDock" width="0.25" height="0.457142857142857" x="0.734375" y="0.514285714285714" order="2"/>
<window name="UsersWindow" width="0.19" height="0.539560439560439" x="0.0078125" y="0.014285714285714" order="3"/>
<window name="SharedNotesWindow" width="0.19" height="0.409769917582418" x="0.0078125" y="0.575944368131867" order="4"/>
<window name="PresentationWindow" width="0.5575" height="0.971428571428571" x="0.205625" y="0.014285714285714" />
<window name="ChatWindow" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.014285714285714" order="2"/>
<window name="VideoDock" width="0.22125" height="0.478571428571429" x="0.7709375" y="0.507142857142857" order="0"/>
<window name="BroadcastWindow" hidden="true"/>
<window name="CaptionWindow" hidden="true"/>
</layout>

View File

@ -89,7 +89,7 @@ package org.bigbluebutton.common {
} else {
var loggingOptions:LoggingOptions = Options.getOptions(LoggingOptions) as LoggingOptions;
loggingEnabled = loggingOptions.enabled;
loggingTargetName = loggingOptions.target;
loggingTargetName = loggingOptions.logTarget;
logLevel = loggingOptions.level;
logFormat = loggingOptions.format;
logPattern = loggingOptions.logPattern;

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.core.events
{
import flash.events.Event;
public class PerformRttTraceEvent extends Event
{
public static const PERFORM_RTT_TRACE: String = "perform rtt trace event";
public function PerformRttTraceEvent()
{
super(PERFORM_RTT_TRACE, false, false);
}
}
}

View File

@ -3,11 +3,25 @@ package org.bigbluebutton.core.model
public class SharedNotes
{
[Bindable]
public var numAdditionalSharedNotes:Number = 0;
private var _numActiveSharedNotes:Number = 1;
public function SharedNotes()
{
}
public function addNewSharedNote(): void {
_numActiveSharedNotes++;
}
public function removeSharedNote(): void {
_numActiveSharedNotes--
}
public function get numActiveSharedNotes(): Number {
return _numActiveSharedNotes;
}
}
}

View File

@ -1,11 +1,12 @@
package org.bigbluebutton.core.model.users
{
import flash.events.EventDispatcher;
public class User2x {
public class User2x extends EventDispatcher {
public var intId: String;
public var extId: String;
public var name: String;
public var role: String;
[Bindable] public var role: String;
public var guest: Boolean;
public var authed: Boolean;
public var waitingForAcceptance: Boolean;

View File

@ -25,7 +25,7 @@ package org.bigbluebutton.main.model.options {
public var enabled:Boolean = true;
[Bindable]
public var target:String = "trace";
public var logTarget:String = "trace";
[Bindable]
public var level:String = "info";

View File

@ -167,8 +167,10 @@ package org.bigbluebutton.main.model.users
}
public function disconnect(onUserAction:Boolean):void {
_connectionManager.disconnect(onUserAction);
if (_connectionManager) {
_connectionManager.disconnect(onUserAction);
}
}
public function activityResponse():void {
sender.activityResponse();

View File

@ -440,9 +440,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
receivedConfigLocaleVer = true;
appVersion = event.appVersion;
localeVersion = event.localeVersion;
updateCopyrightText();
logData.localeVersion = localeVersion;
logData.tags = ["locale"];
logData.appVersion = appVersion;
logData.message = "Received locale version from config.xml";
LOGGER.info(JSON.stringify(logData));
@ -549,8 +551,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
public function openShortcutHelpWindow(e:Event = null):void{
if (scWindow == null) {
scWindow = new ShortcutHelpWindow();
scWindow.width = 300;
scWindow.height = 300;
}
if (scWindow.minimized)
@ -560,7 +560,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
scWindow.restore();
mdiCanvas.windowManager.add(scWindow);
mdiCanvas.windowManager.absPos(scWindow, mdiCanvas.width/2 - 150, mdiCanvas.height/2 - 150);
mdiCanvas.windowManager.absPos(scWindow, mdiCanvas.width/2 - 250, mdiCanvas.height/2 - 250);
scWindow.focusHead();
}
@ -740,7 +740,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function handleRoundTripLatencyReceivedEvent(event: RoundTripLatencyReceivedEvent): void {
rttLabel.text = "RTT = " + LiveMeeting.inst().whiteboardModel.latencyInSec + "s";
// rttLabel.text = "RTT = " + LiveMeeting.inst().whiteboardModel.latencyInSec/1000 + "s";
}
private function handleWebRTCMediaRequestEvent(event:WebRTCMediaEvent):void {
@ -1060,11 +1060,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
id="copyrightLabel2" truncateToFit="true"
selectable="true" paddingRight="10"/>
<mx:Spacer width="100%" id="spacer" />
<mx:Label
<!--mx:Label
id="rttLabel"
text="RTT = 0ms"
visible="true"
includeInLayout="true" />
includeInLayout="true" /-->
<mx:Label
id="lblWebRTC"
text="{'[ '+ResourceUtil.getInstance().getString('bbb.mainshell.notification.webrtc')+' ]'}"
@ -1093,6 +1093,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
styleName="logsButton"
click="openLogWindow()"/>
<mx:HBox id="addedBtns" />
<mx:Button width="30" height="30" toolTip="{ResourceUtil.getInstance().getString('bbb.mainshell.fullscreenBtn.toolTip')}" id="fullScreenBtn" styleName="fullScreenButton" click="toggleFullScreen()" />
<mx:Button id="fullScreenBtn"
width="30"
height="30"
toolTip="{ResourceUtil.getInstance().getString('bbb.mainshell.fullscreenBtn.toolTip')}"
styleName="fullScreenButton"
click="toggleFullScreen()"/>
</mx:ControlBar>
</mx:VBox>

View File

@ -199,8 +199,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
if (y < 0) y = 1;
break;
case DESKTOP_SHARING_PUBLISH:
x = centerWindowWidth - win.width / 2;
y = centerWindowHeight - win.height / 2;
x = 100;
y = this.height - win.height - 50;
break;
case DESKTOP_SHARING_VIEW:
x = centerWindowWidth - win.width / 2;

View File

@ -205,7 +205,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
var meetingTitle:String = LiveMeeting.inst().meeting.name;
if (meetingTitle != null) {
meetingNameLbl.text = meetingTitle;
meetingNameLbl.toolTip = meetingTitle;
}
}
logFlashPlayerCapabilities();
@ -287,7 +286,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
var logoutWindow:LogoutWindow;
logoutWindow = LogoutWindow(PopUpManager.createPopUp(FlexGlobals.topLevelApplication as DisplayObject, LogoutWindow, true));
var newX:Number = btnLogout.x + btnLogout.width - logoutWindow.width;
var newX:Number = this.width - logoutWindow.width;
var newY:Number = btnLogout.y + btnLogout.height + 5;
PopUpManager.centerPopUp(logoutWindow);

View File

@ -29,7 +29,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
resizable="false"
showCloseButton="true"
implements="org.bigbluebutton.common.IBbbModuleWindow"
width="210" height="220" minWidth="0"
width="210" height="224" minWidth="0"
resize="onResize()">
<fx:Script>

View File

@ -91,7 +91,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
// Confirm logout using built-in alert
_confirmationAlert = Alert.show(message, ResourceUtil.getInstance().getString('bbb.mainToolbar.recordBtn.confirm.title'), Alert.YES | Alert.NO, this, onCloseConfirmationDialog, null, Alert.YES);
var newX:Number = this.x;
// Reach out to MainAppShell to set position of alert window.
var newX:Number = this.parent.parent.width * 0.7;
var newY:Number = this.y + this.height + 5;
_confirmationAlert.validateNow();
@ -181,7 +182,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
alert.setStyle("modalTransparencyDuration", 250);
alert.titleIcon = getStyle('iconRecordReminder');
var newX:Number = this.x;
// Reach out to MainAppShell to set position of alert window.
var newX:Number = this.parent.parent.width * 0.65;
var newY:Number = this.y + this.height + 5;
alert.validateNow();

View File

@ -25,8 +25,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:common="org.bigbluebutton.common.*"
showCloseButton="true"
height="350"
width="430"
height="550"
width="500"
initialize="init()"
creationComplete="onCreationComplete()"
xmlns:mate="http://mate.asfusion.com/"

View File

@ -26,7 +26,7 @@ package org.bigbluebutton.modules.chat.model {
public var privateEnabled:Boolean = true;
[Bindable]
public var fontSize:String = "12";
public var fontSize:String = "14";
[Bindable]
public var baseTabIndex:int = 801;

View File

@ -39,6 +39,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import com.asfusion.mate.events.Dispatcher;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.CloseEvent;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
@ -53,6 +55,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.main.model.users.events.ChangeMyRole;
import org.bigbluebutton.modules.chat.events.ChatNoiseEnabledEvent;
import org.bigbluebutton.modules.chat.events.ChatOptionsEvent;
import org.bigbluebutton.modules.chat.events.ChatToolbarButtonEvent;
import org.bigbluebutton.modules.chat.model.ChatOptions;
import org.bigbluebutton.util.i18n.ResourceUtil;
@ -63,7 +66,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[Bindable] private var fontSizes:Array = ['8', '10', '12', '14', '16', '18'];
[Bindable] public var chatOptions:ChatOptions;
[Bindable] private var clrBtnVisible:Boolean = false;
private var globalDispatcher:Dispatcher = new Dispatcher();
private var handler: ChatWindowEventHandler = new ChatWindowEventHandler();
private function handleUserLeftEvent(event: UserLeftEvent): void {
@ -76,6 +82,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function onCreationComplete():void{
clrBtnVisible = UsersUtil.amIModerator();
handler.populateAllUsers()
users = handler.users;
chatOptions = Options.getOptions(ChatOptions) as ChatOptions;
@ -151,6 +159,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function refreshRole(e:ChangeMyRole):void {
clearBtn.visible = clearBtn.enabled = clearBtn.includeInLayout = clrBtnVisible = UsersUtil.amIModerator();
refreshListStatus();
}
@ -161,6 +170,28 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
usersList.enabled = ! LiveMeeting.inst().me.disableMyPrivateChat;
}
public function sendSaveEvent():void{
var saveEvent:ChatToolbarButtonEvent = new ChatToolbarButtonEvent(ChatToolbarButtonEvent.SAVE_CHAT_TOOLBAR_EVENT);
globalDispatcher.dispatchEvent(saveEvent);
}
public function sendCopyEvent():void{
var copyEvent:ChatToolbarButtonEvent = new ChatToolbarButtonEvent(ChatToolbarButtonEvent.COPY_CHAT_TOOLBAR_EVENT);
globalDispatcher.dispatchEvent(copyEvent);
}
public function sendClearEvent():void{
var clearChatHistoryAlert:Alert = Alert.show(ResourceUtil.getInstance().getString('bbb.chat.clearBtn.alert.text'),
ResourceUtil.getInstance().getString('bbb.chat.clearBtn.alert.title'),
Alert.YES | Alert.NO, null, handleClearChatHistoryAlert, null, Alert.YES);
}
private function handleClearChatHistoryAlert(e:CloseEvent):void {
if (e.detail == Alert.YES) {
var clearEvent:ChatToolbarButtonEvent = new ChatToolbarButtonEvent(ChatToolbarButtonEvent.CLEAR_PUBLIC_CHAT_TOOLBAR_EVENT);
globalDispatcher.dispatchEvent(clearEvent);
}
}
]]>
</fx:Script>
@ -186,7 +217,41 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
selectedIndex="1" toolTip="{ResourceUtil.getInstance().getString('bbb.chat.cmbFontSize.toolTip')}" />
</mx:HBox>
<mx:HBox width="100%">
<mx:Label styleName="chatOptionsLabel" text="{ResourceUtil.getInstance().getString('bbb.accessibility.chat.chatwindow.audibleChatNotification')}" width="100%"/>
<mx:Label styleName="chatOptionsLabel"
text="{ResourceUtil.getInstance().getString('bbb.accessibility.chat.chatwindow.audibleChatNotification')}"
width="100%"/>
<mx:CheckBox id="chatNoiseCheckBox" change="changeChatNoise()" />
</mx:HBox>
<mx:HBox width="100%">
<mx:Label styleName="chatOptionsLabel"
text="{ResourceUtil.getInstance().getString('bbb.accessibility.chat.chatwindow.publicChatOptions')}"
width="100%"/>
<mx:Button id="saveBtn"
styleName="chatSaveButtonStyle"
width="22"
height="22"
toolTip="{ResourceUtil.getInstance().getString('bbb.chat.saveBtn.toolTip')}"
click="sendSaveEvent()"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.chat.saveBtn.accessibilityName')}"/>
<mx:Button id="copyBtn"
styleName="chatCopyButtonStyle"
width="22"
height="22"
toolTip="{ResourceUtil.getInstance().getString('bbb.chat.copyBtn.toolTip')}"
click="sendCopyEvent()"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.chat.copyBtn.accessibilityName')}"/>
<mx:Button id="clearBtn"
styleName="chatClearButtonStyle"
width="22"
height="22"
visible = "{clrBtnVisible}"
enabled = "{clrBtnVisible}"
includeInLayout = "{clrBtnVisible}"
toolTip="{ResourceUtil.getInstance().getString('bbb.chat.clearBtn.toolTip')}"
click="sendClearEvent()"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.chat.clearBtn.accessibilityName')}"/>
</mx:HBox>
</mx:VBox>

View File

@ -159,7 +159,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
LOGGER.debug(" onCreationComplete. Apply lock settings");
applyLockSettings();
chatToolbar.registerListeners(chatMessagesList);
// chatToolbar.registerListeners(chatMessagesList);
chatMessagesList.addEventListener(ChatEvent.RESIZE_CHAT_TOOLBAR, adjustToolbarWidthAccordingToScrollBar);
}
@ -611,12 +611,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
const paddingHeight:int = 5;
const paddingWidth:int = 5;
chatToolbar.x = (chatMessagesCanvas.width - chatToolbar.width) - 20;
chatToolbar.y = 10;
// chatToolbar.x = (chatMessagesCanvas.width - chatToolbar.width) - 20;
// chatToolbar.y = 10;
if (!publicChat) {
chatToolbar.publicChat = false;
}
// if (!publicChat) {
// chatToolbar.publicChat = false;
// }
}
]]>
@ -633,7 +633,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
dataProvider="{chatMessages.messages}"
styleName="chatMessageListStyle"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.chat.messageList')}" />
<chat:ChatToolbar id="chatToolbar" />
<!--chat:ChatToolbar id="chatToolbar" /-->
</mx:Canvas>
</mx:VBox>
<mx:HBox id="chatCtrlBar" width="100%" height="60" styleName="chatControlBarStyle" verticalScrollPolicy="off"

View File

@ -120,8 +120,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
return chatBox;
}
private function getPublicChatBox():ChatBox {
return publicBox;
}
private function dispatchSaveChatEvent(e:Event):void {
var chatBox:ChatBox = getVisibleChatBox();
var chatBox:ChatBox = getPublicChatBox();
var saveEvent:ChatSaveEvent = new ChatSaveEvent(ChatSaveEvent.SAVE_CHAT_EVENT);
if (chatBox.chatWithUsername == null || chatBox.chatWithUsername == "") {
@ -135,7 +139,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function dispatchCopyChatEvent(e:Event):void {
var chatBox:ChatBox = getVisibleChatBox();
var chatBox:ChatBox = getPublicChatBox();
var copyEvent:ChatCopyEvent = new ChatCopyEvent(ChatCopyEvent.COPY_CHAT_EVENT);
copyEvent.chatMessages = chatBox.getChatMessages();

View File

@ -24,8 +24,6 @@ package org.bigbluebutton.modules.present.events {
public static const PRESENTATION_ROLL_OUT:String = "PresentationRollOut";
public static const PRESENTATION_SELECT:String = "PresentationSelect";
public var presentationId:String;
public function PresentationRollEvent(type:String, p:String) {

View File

@ -341,14 +341,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
dispatcher.dispatchEvent(rollEvent);
}
protected function onItemClick(event:ListEvent):void
{
var item:IListItemRenderer = event.itemRenderer;
var presentation:Presentation = item.data as Presentation;
var rollEvent:PresentationRollEvent = new PresentationRollEvent(PresentationRollEvent.PRESENTATION_SELECT, presentation.id);
dispatcher.dispatchEvent(rollEvent);
}
]]>
</fx:Script>
@ -386,7 +378,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mx:Canvas width="100%" height="145" verticalScrollPolicy="off">
<mx:List height="142" left="5" top="5" right="5" bottom="5" id="uploadedFilesList" allowMultipleSelection="false"
itemRenderer="org.bigbluebutton.modules.present.ui.views.UploadedPresentationRenderer"
itemRollOver="onItemRollOver(event)" itemRollOut="onItemRollOut(event)" itemClick="onItemClick(event)"
itemRollOver="onItemRollOver(event)" itemRollOut="onItemRollOut(event)"
dragEnabled="false" dataProvider="{presentationNamesAC}">
</mx:List>
</mx:Canvas>

View File

@ -326,7 +326,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function setupPresenter(isPresenter:Boolean):void {
uploadPres.visible = isPresenter;
uploadPres.visible = isPresenter;
downloadPres.visible = !isPresenter;
var page:Page = PresentationModel.getInstance().getCurrentPage();
if (page != null) {
displaySlideNumber(page.num);
@ -435,8 +437,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
dispatchEvent(new GoToNextPageCommand());
}
private function removeDecimalFromDataTip(val:String):String {
return val;
private function showPercentageInDataTip(val:String):String {
return val + "%";
}
private function showThumbnails():void{
@ -719,7 +721,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
pollVoteBox.includeInLayout = true;
downloadPres.visible = false;
} else if (state == "presenter" && UsersUtil.amIPresenter()) {
downloadPres.visible = true;
downloadPres.visible = false;
pollStartBtn.visible = true;
presenterControls.visible = true;
presenterControls.includeInLayout = true;
@ -762,7 +764,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
var downloadablePresentations:ArrayCollection = PresentationModel.getInstance().getDownloadablePresentations();
if (presentOptions.enableDownload && downloadablePresentations.length > 0) {
if (presentOptions.enableDownload && downloadablePresentations.length > 0 && !UsersUtil.amIPresenter()) {
LOGGER.debug("Enabling download presentation button. There are {0} presentations available for downloading.", [downloadablePresentations.length]);
downloadPres.visible = true;
} else {
@ -806,7 +808,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mx:ControlBar id="presCtrlBar" name="presCtrlBar" width="100%" verticalAlign="top" styleName="presentationWindowControlsStyle" paddingTop="6" paddingBottom="6">
<!-- Presentation Actions -->
<mx:HBox width="40%" height="100%" horizontalScrollPolicy="off" verticalAlign="bottom" horizontalAlign="left">
<mx:Button id="pollStartBtn" visible="false" height="30" styleName="pollStartButtonStyle"
<mx:Button id="uploadPres" visible="false" height="30" styleName="presentationUploadButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.uploadPresBtn.toolTip')}"
click="onUploadButtonClicked()"/>
<mx:Button id="pollStartBtn" visible="false" height="30" styleName="pollStartButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.polling.startButton.tooltip')}"
click="onPollStartButtonClicked()" includeInLayout="{pollStartBtn.visible}"/>
<poll:QuickPollButton id="quickPollBtn" height="30"
@ -817,9 +822,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
styleName="presentationDownloadButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.downloadPresBtn.toolTip')}"
click="onDownloadButtonClicked()" creationComplete="updateDownloadBtn()"/>
<mx:Button id="uploadPres" visible="false" height="30" styleName="presentationUploadButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.uploadPresBtn.toolTip')}"
click="onUploadButtonClicked()"/>
</mx:HBox>
<mx:HBox id="presenterControls" width="30%" height="100%" paddingTop="0" visible="false" includeInLayout="false" horizontalAlign="center" verticalAlign="middle">
@ -832,9 +835,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</mx:HBox>
<mx:HBox width="40%" height="100%" horizontalScrollPolicy="off" verticalAlign="middle" horizontalAlign="right">
<mx:HSlider id="zoomSlider" visible="false" value="{slideView.zoomPercentage}" styleName="presentationZoomSliderStyle"
minimum="100" maximum="400" dataTipPlacement="top" labels="['100%','400%']"
minimum="100" maximum="400" dataTipPlacement="top"
useHandCursor="true" snapInterval="5" allowTrackClick="true" liveDragging="true"
dataTipFormatFunction="removeDecimalFromDataTip" change="onSliderZoom()" width="100"
dataTipFormatFunction="showPercentageInDataTip" change="onSliderZoom()" width="100"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.presentation.slider')}"/>
<mx:Button id="btnFitToWidth" visible="false" height="30" styleName="presentationFitToWidthButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToWidth.toolTip')}"

View File

@ -15,8 +15,6 @@
method="onRollOver" />
<mate:Listener type="{PresentationRollEvent.PRESENTATION_ROLL_OUT}"
method="onRollOut" />
<mate:Listener type="{PresentationRollEvent.PRESENTATION_SELECT}"
method="onSelect" />
</fx:Declarations>
<fx:Script>
@ -26,11 +24,13 @@
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.modules.present.commands.ChangePresentationCommand;
import org.bigbluebutton.modules.present.events.DownloadEvent;
import org.bigbluebutton.modules.present.events.PresentationRollEvent;
import org.bigbluebutton.modules.present.events.RemovePresentationEvent;
import org.bigbluebutton.modules.present.events.UploadEvent;
import org.bigbluebutton.util.i18n.ResourceUtil;
private static const LOGGER:ILogger = getClassLogger(UploadedPresentationRenderer);
private var globalDispatch:Dispatcher = new Dispatcher();
@ -38,10 +38,6 @@
[Bindable]
private var rolledOver:Boolean = false;
[Bindable]
private var selected:Boolean = false;
private function showPresentation():void {
var changePresCommand:ChangePresentationCommand = new ChangePresentationCommand(data.id);
globalDispatch.dispatchEvent(changePresCommand);
@ -69,33 +65,33 @@
rolledOver = false;
}
private function onSelect(e:PresentationRollEvent):void {
if (e.presentationId == data.id) {
selected = true;
} else {
selected = false;
}
private function downloadPresentation():void {
var downloadEvent:DownloadEvent = new DownloadEvent(DownloadEvent.DOWNLOAD_PRESENTATION);
downloadEvent.fileNameToDownload = data.id as String;
globalDispatch.dispatchEvent(downloadEvent);
}
]]>
</fx:Script>
<mx:Label id="presentationNameLabel"
width="{this.width-isDownloadable.width-showBtn.width-deleteBtn.width-30}"
text="{data.name as String}"
styleName="{rolledOver || selected ? 'presentationNameLabelOver' : 'presentationNameLabelUp'}" />
<mx:Image id="isDownloadable"
visible="{data.downloadable as Boolean}"
source="{rolledOver || selected ? getStyle('iconSaveWhite') : getStyle('iconSave')}"
toolTip="{ResourceUtil.getInstance().getString('bbb.filedownload.thisFileIsDownloadable')}"
verticalAlign="middle" />
width="{this.width-downloadButton.width-showBtn.width-deleteBtn.width-50}"
truncateToFit="true"
styleName="presentationNameLabel"
text="{data.name as String}" />
<mx:Button id="downloadButton"
height="32"
visible="{data.downloadable as Boolean}"
styleName="presentationUploadDownloadButtonStyle"
click="downloadPresentation()"
toolTip="{ResourceUtil.getInstance().getString('bbb.filedownload.thisFileIsDownloadable')}" />
<mx:Button id="showBtn"
height="32"
label="{ResourceUtil.getInstance().getString('bbb.fileupload.showBtn')}"
toolTip="{ResourceUtil.getInstance().getString('bbb.fileupload.showBtn.toolTip')}"
styleName="presentationUploadShowButtonStyle"
height="26"
click="showPresentation()"
enabled="{!data.current}" />
<mx:Button id="deleteBtn"
label=""
height="32"
toolTip="{ResourceUtil.getInstance().getString('bbb.fileupload.deleteBtn.toolTip')}"
styleName="presentationUploadDeleteButtonStyle"
click="deletePresentation()"

View File

@ -214,9 +214,9 @@ package org.bigbluebutton.modules.screenshare.services.red5 {
message["timestamp"] = timestamp;
sendMessage("screenshare.screenShareClientPongMessage", function(result:String):void { // On successful result
LOGGER.debug(result);
// LOGGER.debug(result);
}, function(status:String):void { // status - On error occurred
LOGGER.error(status);
// LOGGER.error(status);
}, message);
}

View File

@ -35,7 +35,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<fx:Declarations>
<mate:Listener type="{StartShareRequestSuccessEvent.START_SHARE_REQUEST_SUCCESS}" method="handleStartShareRequestSuccessEvent" />
<!--mate:Listener type="{ScreenSharePausedEvent.SCREENSHARE_PAUSED}" method="handleScreenSharePausedEvent" /-->
<mate:Listener type="{ScreenSharePausedEvent.SCREENSHARE_PAUSED}" method="handleScreenSharePausedEvent" />
<mate:Listener type="{ShareStoppedEvent.SHARE_STOPPED}" method="handleScreenShareShareStoppedEvent" />
<mate:Listener type="{ViewStreamEvent.START}" method="handleStartViewStreamEvent" />
<mate:Listener type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" method="onChangedPresenter" />

View File

@ -16,7 +16,13 @@ package org.bigbluebutton.modules.sharednotes {
public var showToolbarButton:Boolean = false;
[Bindable]
public var fontSize:int = 10;
public var fontSize:int = 14;
[Bindable]
public var maxPasteLength:int = 1024;
[Bindable]
public var maxNoteLength:int = 5120;
public function SharedNotesOptions() {
name = "SharedNotesModule";

View File

@ -21,17 +21,18 @@ package org.bigbluebutton.modules.sharednotes.services
import flash.events.IEventDispatcher;
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.main.model.users.IMessageListener;
import org.bigbluebutton.modules.sharednotes.events.CurrentDocumentEvent;
import org.bigbluebutton.modules.sharednotes.events.SharedNotesEvent;
import org.bigbluebutton.modules.sharednotes.events.ReceivePatchEvent;
import org.bigbluebutton.modules.sharednotes.events.SharedNotesEvent;
public class MessageReceiver implements IMessageListener {
private static const LOGGER:ILogger = getClassLogger(MessageReceiver);
@ -111,6 +112,7 @@ package org.bigbluebutton.modules.sharednotes.services
}
private function handleCreateSharedNoteRespMsg(msg: Object):void {
LiveMeeting.inst().sharedNotes.addNewSharedNote();
var e:SharedNotesEvent = new SharedNotesEvent(SharedNotesEvent.CREATE_ADDITIONAL_NOTES_REPLY_EVENT);
e.payload.notesId = msg.body.noteId as String;
e.payload.noteName = msg.body.noteName as String;
@ -119,6 +121,7 @@ package org.bigbluebutton.modules.sharednotes.services
}
private function handleDestroySharedNoteRespMsg(msg: Object):void {
LiveMeeting.inst().sharedNotes.removeSharedNote();
var e:SharedNotesEvent = new SharedNotesEvent(SharedNotesEvent.DESTROY_ADDITIONAL_NOTES_REPLY_EVENT);
e.payload.notesId = msg.body.noteId as String;
e.payload.isNotesLimit = msg.body.isNotesLimit as Boolean;

View File

@ -25,11 +25,9 @@ package org.bigbluebutton.modules.sharednotes.views
_noteId = n;
_windowName = "AdditionalSharedNotesWindow_" + noteId;
showCloseButton = UsersUtil.amIModerator();
//showCloseButton = UsersUtil.amIModerator();
width = 240;
height = 240;
closeBtn.addEventListener(MouseEvent.CLICK, onCloseBtnClick);
}
public function get windowName():String {
@ -46,6 +44,7 @@ package org.bigbluebutton.modules.sharednotes.views
LOGGER.debug("AdditionalSharedNotesWindow: [2] in-constructor additional notes " + noteId);
btnNew.visible = btnNew.includeInLayout = false;
closeBtn.addEventListener(MouseEvent.CLICK, onCloseBtnClick);
}
private function onCloseBtnClick(e:MouseEvent):void {

View File

@ -35,12 +35,16 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<fx:Script>
<![CDATA[
import com.asfusion.mate.events.Dispatcher;
import mx.managers.PopUpManager;
import org.bigbluebutton.util.i18n.ResourceUtil;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.modules.sharednotes.events.SharedNotesEvent;
import org.bigbluebutton.util.i18n.ResourceUtil;
[Bindable]
private var sharedNoteTitle: String = "";
private function btnNew_clickHandler(event:MouseEvent):void {
btnNew.enabled = false;
var e:SharedNotesEvent = new SharedNotesEvent(SharedNotesEvent.CREATE_ADDITIONAL_NOTES_REQUEST_EVENT);
@ -55,6 +59,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function onCreationComplete():void {
var titleKey = "bbb.sharedNotes.title." + LiveMeeting.inst().sharedNotes.numActiveSharedNotes;
sharedNoteTitle = ResourceUtil.getInstance().getString(titleKey);
textInput.setFocus();
}
@ -69,7 +76,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
restrict="a-zA-Z0-9 "
maxChars="20"
width="100%"
text="{ResourceUtil.getInstance().getString('bbb.sharedNotes.title')}"/>
text="{sharedNoteTitle}"/>
<mx:Button id="btnNew"
click="btnNew_clickHandler(event)"
styleName="sharedNotesNewButtonStyle"

View File

@ -145,13 +145,15 @@
richTextEditor.bulletButton.visible = false;
richTextEditor.bulletButton.height = 0;
richTextEditor.bulletButton.width = 0;
richTextEditor.textArea.maxChars = options.maxNoteLength;
}
private function updateRoleDependentProperties(role:String):void {
if(noteId == "MAIN_NOTE"){
btnNew.visible = btnNew.includeInLayout = options.enableMultipleNotes && role == Role.MODERATOR;
} else {
showCloseButton = role == Role.MODERATOR;
// showCloseButton = role == Role.MODERATOR;
}
}
@ -207,10 +209,11 @@
_document = result;
// LOGGER.debug("SharedNotes: patching local with server modifications");
richTextEditor.patch(e.patch);
/* This code is forcing resync and causing more problems then resolving
if (possiblePatchError()) {
syncNote();
return;
}
}*/
}
_lastPatch = e.patchId;
updateUndoRedoButtons(e.undo, e.redo);
@ -422,12 +425,16 @@
return true;
}
public function throwPasteWarning(pasteLength:int):void {
Alert.show(ResourceUtil.getInstance().getString("bbb.caption.transcript.pastewarning.text", [options.maxPasteLength, pasteLength]), ResourceUtil.getInstance().getString("bbb.sharedNotes.pastewarning.title"), Alert.OK);
}
protected function btnToolbar_clickHandler(event:MouseEvent):void {
richTextEditor.showControlBar = !richTextEditor.showControlBar;
}
public function getPrefferedPosition():String {
return MainCanvas.BOTTOM_LEFT;
return MainCanvas.POPUP;
}
override protected function resourcesChanged():void {
@ -491,6 +498,10 @@
enabled="false" visible="true"/>
</mx:HBox>
<mx:HBox width="100%" horizontalAlign="right" paddingTop="0">
<mx:Label id="charRemain"
includeInLayout="true"
visible="{(options.maxNoteLength - richTextEditor.text.length) &lt; (options.maxNoteLength / 8)}"
text="{options.maxNoteLength - richTextEditor.text.length}"/>
<mx:Button id="btnNew"
styleName="sharedNotesNewButtonStyle"
width="26"

View File

@ -991,6 +991,17 @@
private var shiftPressed:Boolean = false;
private var ctrlPressed:Boolean = false;
private var preventTextInput:Boolean = false;
private var _maxPasteLength:int = 1024;
public function set maxPasteLength(value:int):void
{
_maxPasteLength = value;
}
public function get maxPasteLength():int
{
return _maxPasteLength;
}
public function restoreVerticalScroll(oldVerticalScroll:Number):void
{
@ -1216,6 +1227,12 @@
{
if (e.text.length > 0) refreshSelection = true;
if (e.text.length > maxPasteLength)
{
e.preventDefault();
parentDocument.throwPasteWarning(e.text.length);
}
if (preventTextInput) e.preventDefault();
}

View File

@ -611,19 +611,7 @@ package org.bigbluebutton.modules.users.services
LiveMeeting.inst().voiceUsers.setListenOnlyForUser(userId, listenOnly);
}
private function userTalk(userId:String, talking:Boolean):void {
LiveMeeting.inst().voiceUsers.setMutedForUser(userId, talking);
var event:CoreEvent = new CoreEvent(EventConstants.USER_TALKING);
event.message.userID = userId;
event.message.talking = talking;
globalDispatcher.dispatchEvent(event);
}
/**
* This meeting is in the process of ending by the server
*/

View File

@ -29,9 +29,10 @@
[Bindable]
private var breakoutRoomsReady: Boolean = false;
protected function onCreationCompleteHandler(event:FlexEvent):void {
moderator = UsersUtil.amIModerator();
breakoutRoomsReady = LiveMeeting.inst().breakoutRooms.breakoutRoomsReady;
this.addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler);
}
@ -59,7 +60,7 @@
protected function requestBreakoutJoinUrl(event:MouseEvent):void {
var e:BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.REQUEST_BREAKOUT_JOIN_URL);
e.breakoutMeetingId = data.externalMeetingId as String;
e.breakoutMeetingId = data.meetingId as String;
e.userId = UsersUtil.getMyUserID();
globalDispatch.dispatchEvent(e);
}

View File

@ -23,6 +23,7 @@ package org.bigbluebutton.modules.whiteboard
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.modules.whiteboard.business.shapes.GraphicObject;
import org.bigbluebutton.modules.whiteboard.business.shapes.ShapeFactory;
import org.bigbluebutton.modules.whiteboard.business.shapes.TextObject;
@ -154,24 +155,30 @@ package org.bigbluebutton.modules.whiteboard
}
public function drawCursor(userId:String, xPercent:Number, yPercent:Number):void {
var showName: Boolean = LiveMeeting.inst().whiteboardModel.multiUser;
if (!_cursors.hasOwnProperty(userId)) {
var userName:String = UsersUtil.getUserName(userId);
if (userName) {
var newCursor:WhiteboardCursor = new WhiteboardCursor(userId, userName, xPercent, yPercent, shapeFactory.parentWidth, shapeFactory.parentHeight, presenterId == userId);
var newCursor:WhiteboardCursor = new WhiteboardCursor(userId, userName,
xPercent, yPercent, shapeFactory.parentWidth,
shapeFactory.parentHeight, presenterId == userId, showName);
wbCanvas.addCursor(newCursor);
_cursors[userId] = newCursor;
}
} else {
(_cursors[userId] as WhiteboardCursor).updatePosition(xPercent, yPercent);
(_cursors[userId] as WhiteboardCursor).updatePosition(xPercent, yPercent, showName);
}
}
public function presenterChange(amIPresenter:Boolean, presenterId:String):void {
this.presenterId = presenterId;
var showName: Boolean = LiveMeeting.inst().whiteboardModel.multiUser;
for(var j:String in _cursors) {
(_cursors[j] as WhiteboardCursor).updatePresenter(j == presenterId);
(_cursors[j] as WhiteboardCursor).updatePresenter(j == presenterId, showName);
}
}

View File

@ -26,20 +26,20 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
xmlns:mate="org.bigbluebutton.common.mate.*">
<fx:Script>
<![CDATA[
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.modules.present.events.PresentationEvent;
import org.bigbluebutton.modules.whiteboard.commands.GetWhiteboardAccessCommand;
import org.bigbluebutton.modules.whiteboard.commands.GetWhiteboardShapesCommand;
import org.bigbluebutton.modules.whiteboard.events.RequestNewCanvasEvent;
import org.bigbluebutton.modules.whiteboard.events.StartWhiteboardModuleEvent;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardAccessEvent;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardCursorEvent;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardDrawEvent;
import org.bigbluebutton.modules.whiteboard.managers.WhiteboardManager;
import org.bigbluebutton.modules.whiteboard.models.WhiteboardModel;
import org.bigbluebutton.modules.whiteboard.services.MessageReceiver;
import org.bigbluebutton.modules.whiteboard.services.MessageSender;
import org.bigbluebutton.modules.whiteboard.services.WhiteboardService;
import org.bigbluebutton.core.events.PerformRttTraceEvent;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.modules.present.events.PresentationEvent;
import org.bigbluebutton.modules.whiteboard.commands.GetWhiteboardAccessCommand;
import org.bigbluebutton.modules.whiteboard.commands.GetWhiteboardShapesCommand;
import org.bigbluebutton.modules.whiteboard.events.RequestNewCanvasEvent;
import org.bigbluebutton.modules.whiteboard.events.StartWhiteboardModuleEvent;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardAccessEvent;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardCursorEvent;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardDrawEvent;
import org.bigbluebutton.modules.whiteboard.managers.WhiteboardManager;
import org.bigbluebutton.modules.whiteboard.services.MessageReceiver;
import org.bigbluebutton.modules.whiteboard.services.MessageSender;
import org.bigbluebutton.modules.whiteboard.services.WhiteboardService;
]]>
</fx:Script>
@ -51,6 +51,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<EventHandlers type="{GetWhiteboardAccessCommand.GET_ACCESS}" >
<MethodInvoker generator="{WhiteboardService}" method="getWhiteboardAccess"/>
</EventHandlers>
<EventHandlers type="{PerformRttTraceEvent.PERFORM_RTT_TRACE}" >
<MethodInvoker generator="{WhiteboardService}" method="handlePerformRttTraceEvent"/>
</EventHandlers>
<EventHandlers type="{PresentationEvent.PRESENTATION_LOADED}" >
<MethodInvoker generator="{WhiteboardService}" method="setActivePresentation" arguments="{event}" />

View File

@ -57,18 +57,20 @@ package org.bigbluebutton.modules.whiteboard.models
public function set lastTraceReceivedTimestamp(ts: Number): void {
var tsDate: Date = new Date(ts);
var now: Date = new Date();
_roundTripTime = (now.time - tsDate.time) / 1000;
_roundTripTime = now.time - tsDate.time;
_dispatcher.dispatchEvent(new RoundTripLatencyReceivedEvent());
}
public function get latencyInSec(): Number {
if (_lastTraceReceivedOn.time < _lastTraceSentOn.time) {
var now: Date = new Date();
return (now.time - _lastTraceSentOn.time) / 1000;
} else {
return (_lastTraceReceivedOn.time - _lastTraceSentOn.time) / 1000;
}
return _roundTripTime;
//if (_lastTraceReceivedOn.time < _lastTraceSentOn.time) {
// var now: Date = new Date();
// return (now.time - _lastTraceSentOn.time) / 1000;
//} else {
// return (_lastTraceReceivedOn.time - _lastTraceSentOn.time) / 1000;
//}
}

View File

@ -18,9 +18,12 @@
*/
package org.bigbluebutton.modules.whiteboard.services
{
import com.asfusion.mate.events.Dispatcher;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.events.PerformRttTraceEvent;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.main.model.users.IMessageListener;
import org.bigbluebutton.modules.whiteboard.models.Annotation;
@ -29,6 +32,8 @@ package org.bigbluebutton.modules.whiteboard.services
{
private static const LOGGER:ILogger = getClassLogger(MessageReceiver);
private var _dispatcher:Dispatcher = new Dispatcher();
public function MessageReceiver() {
BBB.initConnectionManager().addMessageListener(this);
}
@ -61,6 +66,9 @@ package org.bigbluebutton.modules.whiteboard.services
case "ServerToClientLatencyTracerMsg":
handleServerToClientLatencyTracerMsg(message);
break;
case "DoLatencyTracerMsg":
handleDoLatencyTracerMsg(message);
break;
default:
// LogUtil.warn("Cannot handle message [" + messageName + "]");
}
@ -122,9 +130,13 @@ package org.bigbluebutton.modules.whiteboard.services
private function handleServerToClientLatencyTracerMsg(message:Object):void {
var userId:String = message.body.senderId as String;
var timestamp:Number = message.body.timestamp as Number;
var timestamp:Number = message.body.timestampUTC as Number;
LiveMeeting.inst().whiteboardModel.lastTraceReceivedTimestamp = timestamp;
}
private function handleDoLatencyTracerMsg(message:Object):void {
_dispatcher.dispatchEvent(new PerformRttTraceEvent());
}
}
}

View File

@ -177,36 +177,30 @@ package org.bigbluebutton.modules.whiteboard.services
JSON.stringify(message)
);
sendClientToServerLatencyTracerMsg();
}
public function sendClientToServerLatencyTracerMsg():void {
var lastTraceSentOn: Date = LiveMeeting.inst().whiteboardModel.lastTraceSentOn;
var now: Date = new Date();
if (now.time - lastTraceSentOn.time > 60000) {
var prettyDate: String = now.toString();
var ts: Number = now.time;
var tzOffset: Number = now.timezoneOffset;
LiveMeeting.inst().whiteboardModel.sentLastTrace(now);
var message:Object = {
header: {name: "ClientToServerLatencyTracerMsg", meetingId: UsersUtil.getInternalMeetingID(), userId: UsersUtil.getMyUserID()},
body: {timestamp: ts, prettyTimestamp: prettyDate, tzOffset: tzOffset, senderId: UsersUtil.getMyUserID()}
};
var _nc:ConnectionManager = BBB.initConnectionManager();
_nc.sendMessage2x(
function(result:String):void { // On successful result
//LOGGER.debug(result);
},
function(status:String):void { // status - On error occurred
//LOGGER.error(status);
},
JSON.stringify(message)
);
}
var rtt: Number = LiveMeeting.inst().whiteboardModel.latencyInSec;
LiveMeeting.inst().whiteboardModel.sentLastTrace(now);
var message:Object = {
header: {name: "ClientToServerLatencyTracerMsg", meetingId: UsersUtil.getInternalMeetingID(), userId: UsersUtil.getMyUserID()},
body: {timestampUTC: now.time, rtt: rtt, senderId: UsersUtil.getMyUserID()}
};
var _nc:ConnectionManager = BBB.initConnectionManager();
_nc.sendMessage2x(
function(result:String):void { // On successful result
//LOGGER.debug(result);
},
function(status:String):void { // status - On error occurred
//LOGGER.error(status);
},
JSON.stringify(message)
);
}
}
}

View File

@ -64,5 +64,9 @@ package org.bigbluebutton.modules.whiteboard.services
public function sendCursorPosition(e:WhiteboardCursorEvent):void {
sender.sendCursorPosition(e.xPercent, e.yPercent);
}
public function handlePerformRttTraceEvent():void {
sender.sendClientToServerLatencyTracerMsg();
}
}
}

View File

@ -36,7 +36,12 @@ package org.bigbluebutton.modules.whiteboard.views {
private var _nameTextField:TextField;
public function WhiteboardCursor(userId:String, userName:String, x:Number, y:Number, parentWidth:Number, parentHeight:Number, isPresenter:Boolean) {
private var _showUser: Boolean = false;
public function WhiteboardCursor(userId:String, userName:String, x:Number,
y:Number, parentWidth:Number,
parentHeight:Number,
isPresenter:Boolean,
showUser: Boolean) {
_userId = userId;
_userName = userName;
_origX = x;
@ -66,14 +71,15 @@ package org.bigbluebutton.modules.whiteboard.views {
addChild(_nameTextField);
_showUser = showUser;
drawCursor();
setPosition();
}
public function updatePosition(x:Number, y:Number):void {
public function updatePosition(x:Number, y:Number, showUser: Boolean):void {
_origX = x;
_origY = y;
_showUser = showUser;
setPosition();
}
@ -84,9 +90,9 @@ package org.bigbluebutton.modules.whiteboard.views {
setPosition();
}
public function updatePresenter(isPresenter:Boolean):void {
public function updatePresenter(isPresenter:Boolean, showUser: Boolean):void {
_isPresenter = isPresenter;
_showUser = showUser;
drawCursor();
}
@ -98,7 +104,8 @@ package org.bigbluebutton.modules.whiteboard.views {
hideCursor()
} else {
showCursor();
}
}
_nameTextField.visible = _showUser;
}
private function showCursor():void {
@ -117,8 +124,11 @@ package org.bigbluebutton.modules.whiteboard.views {
private function drawCursor():void {
var cursorColor:uint = (_isPresenter ? PRESENTER_COLOR : OTHER_COLOR);
_nameTextField.textColor = cursorColor;
_nameTextField.borderColor = cursorColor;
_nameTextField.textColor = cursorColor;
_nameTextField.borderColor = cursorColor;
_nameTextField.visible = _showUser;
graphics.clear();
graphics.lineStyle(6, cursorColor, 0.6);

View File

@ -67,7 +67,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[Bindable] private var showWhiteboardToolbar:Boolean = false;
private static const LOGGER:ILogger = getClassLogger(WhiteboardToolbar);
private static const LOGGER:ILogger = getClassLogger(WhiteboardToolbar);
private static const BUTTON_SIZE : int = 36;
private var mousedOver:Boolean = false;
private var whiteboardIdExists:Boolean = false;
@ -303,15 +305,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
that identifies the "category" of the tool (ex. shape vs text), and the other specifies the
tool itself (ex. line tool vs triangle tool, even though both are "shapes")
-->
<wbBtns:PanZoomButton id="panzoomBtn"/>
<wbBtns:ScribbleButton id="scribbleBtn"/>
<wbBtns:RectangleButton id="rectangleBtn"/>
<wbBtns:CircleButton id="circleBtn"/>
<wbBtns:TriangleButton id="triangleBtn"/>
<wbBtns:LineButton id="lineBtn"/>
<wbBtns:TextButton id="textBtn"/>
<wbBtns:ClearButton id="clearBtn"/>
<wbBtns:UndoButton id="undoBtn"/>
<wbBtns:PanZoomButton id="panzoomBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:ScribbleButton id="scribbleBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:RectangleButton id="rectangleBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:CircleButton id="circleBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:TriangleButton id="triangleBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:LineButton id="lineBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:TextButton id="textBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:ClearButton id="clearBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<wbBtns:UndoButton id="undoBtn" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"/>
<!--
Properties that were removed from original color picker:
@ -325,17 +327,21 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
the "fill" color that is used only if "fill" is enabled in WhiteboardCanvasModel
-->
<mx:ColorPicker change="changeColor(event)" id="cpik"
selectedColor="0x000000" width="30" dataProvider="{colorPickerColours}" swatchPanelStyleName="colorPickerStyle"
selectedColor="0x000000" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}"
dataProvider="{colorPickerColours}" swatchPanelStyleName="colorPickerStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.color')}"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.color')}"/>
<mx:Spacer height="3"/>
<mx:Image source="{getStyle('iconWhiteboardThick')}" horizontalAlign="center" width="40"/>
<mx:VSlider height="50" id="sld" change="changeThickness(event)"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.thickness.accessibilityName')}"
minimum="1" maximum="30"
useHandCursor="true" value="3" showDataTip="true" snapInterval="1" dataTipOffset="0" labelOffset="0"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.thickness.accessibilityName')}" />
<mx:Image source="{getStyle('iconWhiteboardThin')}" horizontalAlign="center" width="40"/>
<mx:Button id="multiUserBtn" enabled="false" visible="false" click="changeMultiUser()" width="40" height="40" styleName="{multiUser ? 'multiUserWhiteboardOnButtonStyle' : 'multiUserWhiteboardOffButtonStyle'}"/>
<!--mx:Spacer height="2"/-->
<mx:VBox backgroundColor="#FFFFFF" horizontalAlign="center">
<mx:Image source="{getStyle('iconWhiteboardThick')}" horizontalAlign="center" width="40"/>
<mx:VSlider height="50" id="sld" change="changeThickness(event)"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.thickness.accessibilityName')}"
minimum="1" maximum="30"
useHandCursor="true" value="3" showDataTip="true" snapInterval="1" dataTipOffset="0" labelOffset="0"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.thickness.accessibilityName')}" />
<mx:Image source="{getStyle('iconWhiteboardThin')}" horizontalAlign="center" width="40"/>
</mx:VBox>
<mx:Button id="multiUserBtn" enabled="false" visible="false" click="changeMultiUser()" width="{BUTTON_SIZE}" height="{BUTTON_SIZE}" styleName="{multiUser ? 'multiUserWhiteboardOnButtonStyle' : 'multiUserWhiteboardOffButtonStyle'}"/>
</mx:VBox>

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardCircleButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.ellipse')}"
toggle="true"

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardClearButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.clear')}"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.clear.accessibilityName')}">

View File

@ -20,13 +20,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardLineButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.line')}" toggle="true"
accessibilityName="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.line.accessibilityName')}">
<fx:Script>
<![CDATA[
import org.bigbluebutton.modules.whiteboard.business.shapes.DrawObject;
import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants;
import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent;
import org.bigbluebutton.modules.whiteboard.models.AnnotationType;

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardPanZoomButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.panzoom')}" toggle="true"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.panzoom.accessibilityName')}">

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardRectangleButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.rectangle')}" toggle="true"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.rectangle.accessibilityName')}">

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardScribbleButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.pencil')}"
toggle="true" selected="true"

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardTextButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.text')}" toggle="true"
accessibilityName="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.text.accessibilityName')}">

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardTriangleButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.triangle')}"
toggle="true"

View File

@ -20,7 +20,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Button xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fx="http://ns.adobe.com/mxml/2009"
width="40" height="40"
click="onClick()" styleName="whiteboardUndoButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.undo')}"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.undo.accessibilityName')}">

View File

@ -80,9 +80,8 @@ FREESWITCH_PID=/opt/freeswitch/run/freeswitch.pid
FREESWITCH_EVENT_SOCKET=/opt/freeswitch/conf/autoload_configs/event_socket.conf.xml
if [ -f /etc/redhat-release ]; then
echo "###"
DISTRIB_ID=centos
SERVLET_LOGS=/usr/share/$SERVLET_CONTAINER/logs
SERVLET_LOGS=/usr/share/tomcat/logs
FREESWITCH=freeswitch
FREESWITCH_INIT_D="/etc/init.d/freeswitch"
TOMCAT_USER=tomcat
@ -90,7 +89,7 @@ if [ -f /etc/redhat-release ]; then
REDIS_SERVICE=redis.service
else
. /etc/lsb-release # Get value for DISTRIB_ID
SERVLET_LOGS=/var/lib/$SERVLET_CONTAINER/logs
SERVLET_LOGS=/var/lib/tomcat7/logs
FREESWITCH=freeswitch
FREESWITCH_INIT_D="/etc/init.d/freeswitch"
TOMCAT_USER=tomcat7
@ -154,6 +153,12 @@ else
IP=$(echo "$(LANG=c ifconfig | awk -v RS="" '{gsub (/\n[ ]*inet /," ")}1' | grep ^et.* | grep addr: | head -n1 | sed 's/.*addr://g' | sed 's/ .*//g')$(LANG=c ifconfig | awk -v RS="" '{gsub (/\n[ ]*inet /," ")}1' | grep ^en.* | grep addr: | head -n1 | sed 's/.*addr://g' | sed 's/ .*//g')$(LANG=c ifconfig | awk -v RS="" '{gsub (/\n[ ]*inet /," ")}1' | grep ^wl.* | grep addr: | head -n1 | sed 's/.*addr://g' | sed 's/ .*//g')$(LANG=c ifconfig | awk -v RS="" '{gsub (/\n[ ]*inet /," ")}1' | grep ^bo.* | grep addr: | head -n1 | sed 's/.*addr://g' | sed 's/ .*//g')" | head -n1)
fi
if [ -z "$IP" ]; then
if [ -f /etc/redhat-release ]; then
IP=$(hostname -I | sed 's/ .*//g')
fi
fi
#
# Calculate total memory on this server
#
@ -359,8 +364,8 @@ start_bigbluebutton () {
echo -n "Waiting for FreeSWITCH to start: "
if [ ! -z $FREESWITCH_ESL_IP ]; then
while ! nc -z -w 1 $FREESWITCH_ESL_IP 8021 > /dev/null; do
if [[ ! -z $FREESWITCH_ESL_IP && $DISTRIB_ID != "centos" ]]; then
while ! nc -w 1 $FREESWITCH_ESL_IP 8021 > /dev/null; do
echo -n "."
sleep 1
done
@ -1278,8 +1283,8 @@ check_state() {
#
# Check that BigBlueButton can connect to port 1935
#
if [ ! -z $NGINX_IP ]; then
if ! nc -z -w 3 $NGINX_IP 1935 > /dev/null; then
if [[ ! -z $NGINX_IP && $DISTRIB_ID != "centos" ]]; then
if ! nc -w 3 $NGINX_IP 1935 > /dev/null; then
echo "# Error: Unable to connect to port 1935 (RTMP) on $NGINX_IP"
echo
fi

View File

@ -0,0 +1,8 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Redirecting to bigbluebutton.org</title>
<meta http-equiv="REFRESH" content="0;url=http://www.bigbluebutton.org/"></HEAD>
<BODY>
</BODY>
</HTML>

View File

@ -1,91 +0,0 @@
# Acceptance Testing in HTML Client. Getting Started
The test suite for HTML5 client is currently under active development. The following instructions will help you install all the necessary tools and libraries to run the exiting specs and start writing your own tests.
## Run Selenium Server
Assuming that you have the BigBlueButton repository in `/home/firstuser/dev`, navigate to the directory containing acceptance tests:
```sh
$ cd /home/firstuser/dev/bigbluebutton/bigbluebutton-html5/tests/webdriverio
```
Now, you should navigate to the `tools` directory inside `webdriverio`. This folder will store all the Selenium- and browser-related third-party files that you need to download.
Download Selenium jar file:
```sh
$ curl -O http://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar
```
and browser-specific WebDriver server.
Firefox WebDriver (GeckoDriver):
```sh
$ curl -L https://github.com/mozilla/geckodriver/releases/download/v0.16.1/geckodriver-v0.16.1-linux64.tar.gz | tar xz
```
Chrome WebDriver (ChromeDriver):
```sh
$ wget https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
$ unzip chromedriver_linux64.zip
```
Along with the WebDriver, you need to install the browser itself.
How to install Chrome:
```sh
$ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
$ sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
$ sudo apt-get update
$ sudo apt-get install google-chrome-stable
```
How to install Firefox:
```sh
$ wget sourceforge.net/projects/ubuntuzilla/files/mozilla/apt/pool/main/f/firefox-mozilla-build/firefox-mozilla-build_53.0.3-0ubuntu1_amd64.deb
$ sudo dpkg -i firefox-mozilla-build_53.0.3-0ubuntu1_amd64.deb
```
In order to run headless browser, we will use Xvfb display server:
```sh
$ sudo apt-get install xvfb
```
At this point, you can run the Selenium server (replace `./geckodriver` with `./chromedriver` if you use Chrome):
```sh
$ xvfb-run java -jar selenium-server-standalone-3.4.0.jar
```
If you get an error `Xvfb failed to start`, run it with an `-a` option (Xvfb will use another display if the current one is already in use):
```sh
$ xvfb-run -a java -jar selenium-server-standalone-3.4.0.jar
```
Congratulations! You have Selenium server up and running. It is ready to handle your test cases. Now, keep the `xvfb-run` process running and continue in a new terminal session.
## Run the test specs
We use WebdriverIO interface to write the acceptance test cases. In order to execute the existing tests, you need to use `wdio` test runner. By default, it will look into any `*.spec.js` file inside the `/home/firstuser/dev/bigbluebutton/bigbluebutton-html5/tests/webdriverio/specs` directory. You can change the location of the test specs by modifying the `wdio` config file: `wdio.conf.js` (inside the `webdriverio` directory).
Before proceeding any further, make sure HTML5 client is up and running.
Node.js installation is also required:
```sh
$ sudo apt-get install nodejs-legacy
```
You can run all of the existing test specs with a single npm command:
```sh
$ cd /home/firstuser/dev/bigbluebutton/bigbluebutton-html5
$ npm run test
```
### Test suites
To make it easier to run a single specific set of tests, we group the specs into test suits. All the suits are defined in `wdio.conf.js`.
To run a single test suite, you need to pass its name to the npm script:
```sh
$ npm run test -- --suite login
```

View File

@ -2,6 +2,7 @@
let Page = require('./page');
let pageObject = new Page();
let chai = require('chai');
class LandingPage extends Page {
open() {
@ -13,28 +14,48 @@ class LandingPage extends Page {
}
get url() {
return 'http://localhost:8080/demo/demoHTML5.jsp';
return `${browser.baseUrl}/demo/demoHTML5.jsp`;
}
get username() {
return $('input[name=username]');
// Username input field on the HTML5 client's landing page:
get usernameInputSelector() {
return 'input[name=username]';
}
get joinButton() {
return $('input[type=submit]');
get usernameInputElement() {
return $(this.usernameInputSelector);
}
// Submit button on the HTML5 client's landing page:
get joinButtonSelector() {
return 'input[type=submit]';
}
get joinButtonElement() {
return $(this.joinButtonSelector);
}
// Home page:
get loadedHomePageSelector() {
return '#app';
}
get loadedHomePageElement() {
return $('#app');
}
//////////
joinWithButtonClick() {
this.joinButton.click();
this.joinButtonElement.click();
}
joinWithEnterKey() {
pageObject.pressEnter();
}
get loadedHomePage() {
return $('#app');
}
}
// To use in the future tests that will require login

View File

@ -6,7 +6,7 @@ class Page {
}
pressEnter() {
browser.keys('Enter');
chromeBrowser.keys('Enter');
}
isFirefox() {

View File

@ -2,20 +2,28 @@
let LandingPage = require('../pageobjects/landing.page');
let chai = require('chai');
let utils = require('../utils');
describe('Landing page', function () {
beforeEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; // default value is 10000
});
it('should have correct title', function () {
LandingPage.open();
chai.expect(browser.getTitle()).to.equal(LandingPage.title);
utils.assertTitle(LandingPage.title);
});
it('should allow user to login if the username is specified and the Join button is clicked',
function () {
LandingPage.open();
LandingPage.username.waitForExist();
LandingPage.username.setValue('Maxim');
chromeBrowser.setValue(LandingPage.usernameInputSelector, 'Maxim');
firefoxBrowser.setValue(LandingPage.usernameInputSelector, 'Anton');
LandingPage.joinWithButtonClick();
LandingPage.loadedHomePage.waitForExist(5000);
LandingPage.loadedHomePageElement.waitForExist(5000);
});
it('should not allow user to login if the username is not specified (login using a button)',
@ -29,29 +37,27 @@ describe('Landing page', function () {
browser.pause(5000); // amount of time we usually wait for the home page to appear
// verify that we are still on the landing page
chai.expect(browser.getUrl()).to.equal(LandingPage.url);
utils.assertUrl(LandingPage.url);
});
if (!LandingPage.isFirefox()) {
it('should allow user to login if the username is specified and then Enter key is pressed',
function () {
function () { // Chrome-only
LandingPage.open();
LandingPage.username.waitForExist();
LandingPage.username.setValue('Maxim');
chromeBrowser.setValue(LandingPage.usernameInputSelector, 'Maxim');
LandingPage.joinWithEnterKey();
LandingPage.loadedHomePage.waitForExist(5000);
chromeBrowser.waitForExist(LandingPage.loadedHomePageSelector, 5000);
});
it('should not allow user to login if the username is not specified (login using Enter key)',
function () {
function () { // Chrome-only
LandingPage.open();
// we intentionally don't enter username here
LandingPage.joinWithEnterKey();
browser.pause(5000); // amount of time we usually wait for the gome page to appear
chai.expect(browser.getUrl()).to.equal(LandingPage.url);
chromeBrowser.pause(5000); // amount of time we usually wait for the gome page to appear
chai.expect(browser.getUrl().chromeBrowser).to.equal(LandingPage.url);
});
}
});

View File

@ -0,0 +1,19 @@
'use strict';
let chai = require('chai');
class Utils {
assertTitle(title) {
browser.remotes.forEach(function(browserName) {
chai.expect(browser.select(browserName).getTitle()).to.equal(title);
});
}
assertUrl(url) {
browser.remotes.forEach(function(browserName) {
chai.expect(browser.getUrl()[browserName]).to.equal(url);
});
}
}
module.exports = new Utils();

View File

@ -1,10 +1,17 @@
exports.config = {
specs: ['tests/webdriverio/specs/**/*.spec.js'],
capabilities: [
{
browserName: 'chrome',
capabilities: {
chromeBrowser: {
desiredCapabilities: {
browserName: 'chrome',
}
},
],
firefoxBrowser: {
desiredCapabilities: {
browserName: 'firefox'
}
}
},
baseUrl: 'http://localhost:8080',
framework: 'jasmine',
reporters: ['spec', 'junit'],
@ -19,5 +26,10 @@ exports.config = {
'tests/webdriverio/specs/login.spec.js',
],
},
before: function() {
// make the properties that browsers share and the list of browserNames available:
browser.remotes = Object.keys(exports.config.capabilities);
browser.baseUrl = exports.config.baseUrl;
},
};

View File

@ -104,7 +104,7 @@ defaultGuestPolicy=ASK_MODERATOR
#
# native2ascii -encoding UTF8 bigbluebutton.properties bigbluebutton.properties
#
defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.<br>
defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the phone button (top center of screen). Use a headset to avoid causing background noise for others.<br>
defaultWelcomeMessageFooter=This server is running <a href="http://docs.bigbluebutton.org/" target="_blank"><u>BigBlueButton</u></a>.
# Default maximum number of users a meeting can have.

View File

@ -32,7 +32,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<bean id="officeDocumentValidator" class="org.bigbluebutton.presentation.imp.OfficeDocumentValidator"/>
<bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService">
<bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService"
init-method="start" destroy-method="stop">
<property name="officeDocumentValidator" ref="officeDocumentValidator"/>
</bean>

View File

@ -33,7 +33,6 @@ require 'recordandplayback/generators/audio'
require 'recordandplayback/generators/video'
require 'recordandplayback/generators/audio_processor'
require 'recordandplayback/generators/presentation'
require 'custom_hash'
require 'open4'
require 'pp'
require 'absolute_time'

View File

@ -44,7 +44,7 @@ module BigBlueButton
end
ffmpeg_cmd += ['-i', audio]
end
ffmpeg_cmd += [*pass, lastoutput]
ffmpeg_cmd += [*pass, '-passlogfile', output_basename, lastoutput]
Dir.chdir(File.dirname(output)) do
exitstatus = BigBlueButton.exec_ret(*ffmpeg_cmd)
raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0

View File

@ -24,8 +24,8 @@ module BigBlueButton
module EDL
module Video
FFMPEG_WF_CODEC = 'mpeg2video'
FFMPEG_WF_FRAMERATE = '24'
FFMPEG_WF_ARGS = ['-an', '-codec', FFMPEG_WF_CODEC, '-q:v', '2', '-g', '240', '-pix_fmt', 'yuv420p', '-r', FFMPEG_WF_FRAMERATE, '-f', 'mpegts']
FFMPEG_WF_FRAMERATE = 24
FFMPEG_WF_ARGS = ['-an', '-codec', FFMPEG_WF_CODEC.to_s, '-q:v', '2', '-g', (FFMPEG_WF_FRAMERATE * 10).to_s, '-pix_fmt', 'yuv420p', '-r', FFMPEG_WF_FRAMERATE.to_s, '-f', 'mpegts']
WF_EXT = 'ts'
def self.dump(edl)
@ -37,7 +37,7 @@ module BigBlueButton
entry[:areas].each do |name, videos|
BigBlueButton.logger.debug " #{name}"
videos.each do |video|
BigBlueButton.logger.debug " #{video[:filename]} at #{video[:timestamp]}"
BigBlueButton.logger.debug " #{video[:filename]} at #{video[:timestamp]} (original duration: #{video[:original_duration]})"
end
end
end
@ -124,7 +124,8 @@ module BigBlueButton
merged_entry[:areas][area] = videos.map do |video|
{
:filename => video[:filename],
:timestamp => video[:timestamp] + merged_entry[:timestamp] - last_entry[:timestamp]
:timestamp => video[:timestamp] + merged_entry[:timestamp] - last_entry[:timestamp],
:original_duration => video[:original_duration]
}
end
end
@ -189,10 +190,13 @@ module BigBlueButton
BigBlueButton.logger.debug " #{videofile}"
info = video_info(videofile)
BigBlueButton.logger.debug " width: #{info[:width]}, height: #{info[:height]}, duration: #{info[:duration]}"
if !info[:video]
BigBlueButton.logger.warn " This video file is corrupt! It will be removed from the output."
corrupt_videos << videofile
else
if info[:video][:deskshare_timestamp_bug]
BigBlueButton.logger.debug(" has early 1.1 deskshare timestamp bug")
end
end
videoinfo[videofile] = info
@ -207,49 +211,6 @@ module BigBlueButton
end
end
BigBlueButton.logger.info "Generating missing video end events"
videoinfo.each do |filename, info|
edl.each_with_index do |event, index|
new_entry = { :areas => {} }
add_new_entry = false
event[:areas].each do |area, videos|
videos.each do |video|
if video[:filename] == filename
if video[:timestamp] > info[:duration]
videos.delete(video)
# Note that I'm using a 5-second fuzz factor here.
# If there's a stop event within 5 seconds of the video ending, don't bother to generate
# an extra event.
elsif video[:timestamp] + (event[:next_timestamp] - event[:timestamp]) > info[:duration] + 5000
BigBlueButton.logger.warn "Over-long video #{video[:filename]}, synthesizing stop event"
new_entry[:timestamp] = event[:timestamp] + info[:duration] - video[:timestamp]
new_entry[:next_timestamp] = event[:next_timestamp]
event[:next_timestamp] = new_entry[:timestamp]
add_new_entry = true
end
end
end
end
if add_new_entry
event[:areas].each do |area, videos|
new_entry[:areas][area] = videos.select do |video|
video[:filename] != filename
end.map do |video|
{
:filename => video[:filename],
:timestamp => video[:timestamp] + new_entry[:timestamp] - event[:timestamp]
}
end
end
edl.insert(index + 1, new_entry)
end
end
end
dump(edl)
BigBlueButton.logger.info "Compositing cuts"
@ -296,6 +257,10 @@ module BigBlueButton
info[:aspect_ratio] = Rational(info[:width], info[:height])
end
if info[:format][:format_name] == 'flv' and info[:video][:codec_name] == 'h264'
info[:video][:deskshare_timestamp_bug] = self.check_deskshare_timestamp_bug(filename)
end
# Convert the duration to milliseconds
info[:duration] = (info[:format][:duration].to_r * 1000).to_i
@ -304,6 +269,31 @@ module BigBlueButton
{}
end
def self.check_deskshare_timestamp_bug(filename)
IO.popen([*FFPROBE, '-select_streams', 'v:0', '-show_frames', '-read_intervals', '%+#10', filename]) do |probe|
info = JSON.parse(probe.read, symbolize_names: true)
return false if !info
if !info[:frames]
return false
end
# First frame in broken stream always has pts=1
if info[:frames][0][:pkt_pts] != 1
return false
end
# Remaining frames start at 200, and go up by exactly 200 each frame
for i in 1...info[:frames].length
if info[:frames][i][:pkt_pts] != i * 200
return false
end
end
return true
end
end
def self.ms_to_s(timestamp)
s = timestamp / 1000
ms = timestamp % 1000
@ -312,29 +302,30 @@ module BigBlueButton
def self.aspect_scale(old_width, old_height, new_width, new_height)
if old_width.to_f / old_height > new_width.to_f / new_height
[new_width, old_height * new_width / old_width]
new_height = (2 * (old_height.to_f * new_width / old_width / 2).round).to_i
else
[old_width * new_height / old_height, new_height]
new_width = (2 * (old_width.to_f * new_height / old_height / 2).round).to_i
end
[new_width, new_height]
end
def self.pad_offset(video_width, video_height, area_width, area_height)
[(area_width - video_width) / 2, (area_height - video_height) / 2]
pad_x = (2 * ((area_width - video_width).to_f / 4).round).to_i
pad_y = (2 * ((area_height - video_height).to_f / 4).round).to_i
[pad_x, pad_y]
end
def self.composite_cut(output, cut, layout, videoinfo)
duration = cut[:next_timestamp] - cut[:timestamp]
BigBlueButton.logger.info " Cut start time #{cut[:timestamp]}, duration #{duration}"
ffmpeg_inputs = []
ffmpeg_filter = "color=c=white:s=#{layout[:width]}x#{layout[:height]}:r=24"
index = 0
layout[:areas].each do |layout_area|
area = cut[:areas][layout_area[:name]]
video_count = area.length
BigBlueButton.logger.debug " Laying out #{video_count} videos in #{layout_area[:name]}"
next if video_count == 0
tile_offset_x = layout_area[:x]
tile_offset_y = layout_area[:y]
@ -348,8 +339,8 @@ module BigBlueButton
# Do an exhaustive search to maximize video areas
for tmp_tiles_v in 1..video_count
tmp_tiles_h = (video_count / tmp_tiles_v.to_f).ceil
tmp_tile_width = layout_area[:width] / tmp_tiles_h
tmp_tile_height = layout_area[:height] / tmp_tiles_v
tmp_tile_width = (2 * (layout_area[:width].to_f / tmp_tiles_h / 2).floor).to_i
tmp_tile_height = (2 * (layout_area[:height].to_f / tmp_tiles_v / 2).floor).to_i
next if tmp_tile_width <= 0 or tmp_tile_height <= 0
tmp_total_area = 0
@ -374,9 +365,10 @@ module BigBlueButton
BigBlueButton.logger.debug " Tiling in a #{tiles_h}x#{tiles_v} grid"
ffmpeg_filter << "[#{layout_area[:name]}_in];"
area.each do |video|
BigBlueButton.logger.debug " clip ##{index}"
BigBlueButton.logger.debug " tile location (#{tile_x}, #{tile_y})"
BigBlueButton.logger.debug " tile location (#{tile_x}, #{tile_y})"
video_width = videoinfo[video[:filename]][:width]
video_height = videoinfo[video[:filename]][:height]
BigBlueButton.logger.debug " original size: #{video_width}x#{video_height}"
@ -385,18 +377,17 @@ module BigBlueButton
BigBlueButton.logger.debug " scaled size: #{scale_width}x#{scale_height}"
offset_x, offset_y = pad_offset(scale_width, scale_height, tile_width, tile_height)
offset_x += tile_offset_x + (tile_x * tile_width)
offset_y += tile_offset_y + (tile_y * tile_height)
BigBlueButton.logger.debug " offset: left: #{offset_x}, top: #{offset_y}"
BigBlueButton.logger.debug " start timestamp: #{video[:timestamp]}"
BigBlueButton.logger.debug(" codec: #{videoinfo[video[:filename]][:video][:codec_name].inspect}")
BigBlueButton.logger.debug(" duration: #{videoinfo[video[:filename]][:duration]}, original duration: #{video[:original_duration]}")
if videoinfo[video[:filename]][:video][:codec_name] == "flashsv2"
# Desktop sharing videos in flashsv2 do not have regular
# keyframes, so seeking in them doesn't really work.
# To make processing more reliable, always decode them from the
# start in each cut.
# start in each cut. (Slow!)
seek = 0
else
# Webcam videos are variable, low fps; it might be that there's
@ -406,33 +397,66 @@ module BigBlueButton
seek = 0 if seek < 0
end
ffmpeg_inputs << {
:filename => video[:filename],
:seek => seek
}
ffmpeg_filter << "[in#{index}]; [#{index}]fps=24,trim=start=#{ms_to_s(video[:timestamp])},setpts=PTS-STARTPTS,scale=#{scale_width}:#{scale_height}"
if layout_area[:pad]
ffmpeg_filter << ",pad=w=#{tile_width}:h=#{tile_height}:x=#{offset_x}:y=#{offset_y}:color=white"
offset_x = 0
offset_y = 0
# Workaround early 1.1 deskshare timestamp bug
# It resulted in video files that were too short. To workaround, we
# assume that the framerate was constant throughout (it might not
# actually be...) and scale the video length.
scale = nil
if !video[:original_duration].nil? and
videoinfo[video[:filename]][:video][:deskshare_timestamp_bug]
scale = video[:original_duration].to_f / videoinfo[video[:filename]][:duration]
# Rather than attempt to recalculate seek...
seek = 0
BigBlueButton.logger.debug(" Early 1.1 deskshare timestamp bug: scaling video length by #{scale}")
end
ffmpeg_filter << "[mv#{index}]; [in#{index}][mv#{index}] overlay=#{offset_x}:#{offset_y}"
pad_name = "#{layout_area[:name]}_x#{tile_x}_y#{tile_y}"
ffmpeg_filter << "movie=#{video[:filename]}:sp=#{ms_to_s(seek)}"
if !scale.nil?
ffmpeg_filter << ",setpts=PTS*#{scale}"
end
ffmpeg_filter << ",fps=#{FFMPEG_WF_FRAMERATE}:start_time=#{ms_to_s(video[:timestamp])}"
ffmpeg_filter << ",setpts=PTS-STARTPTS,scale=#{scale_width}:#{scale_height}"
ffmpeg_filter << ",pad=w=#{tile_width}:h=#{tile_height}:x=#{offset_x}:y=#{offset_y}:color=white"
ffmpeg_filter << "[#{pad_name}];"
tile_x += 1
if tile_x >= tiles_h
tile_x = 0
tile_y += 1
end
index += 1
end
remaining = video_count
(0...tiles_v).each do |tile_y|
this_tiles_h = [tiles_h, remaining].min
remaining -= this_tiles_h
(0...this_tiles_h).each do |tile_x|
ffmpeg_filter << "[#{layout_area[:name]}_x#{tile_x}_y#{tile_y}]"
end
if this_tiles_h > 1
ffmpeg_filter << "hstack=inputs=#{this_tiles_h},"
end
ffmpeg_filter << "pad=w=#{layout_area[:width]}:h=#{tile_height}:color=white"
ffmpeg_filter << "[#{layout_area[:name]}_y#{tile_y}];"
end
(0...tiles_v).each do |tile_y|
ffmpeg_filter << "[#{layout_area[:name]}_y#{tile_y}]"
end
if tiles_v > 1
ffmpeg_filter << "vstack=inputs=#{tiles_v},"
end
ffmpeg_filter << "pad=w=#{layout_area[:width]}:h=#{layout_area[:height]}:color=white"
ffmpeg_filter << "[#{layout_area[:name]}];"
ffmpeg_filter << "[#{layout_area[:name]}_in][#{layout_area[:name]}]overlay=x=#{layout_area[:x]}:y=#{layout_area[:y]}"
end
ffmpeg_filter << ",trim=end=#{ms_to_s(duration)}"
ffmpeg_cmd = [*FFMPEG]
ffmpeg_inputs.each do |input|
ffmpeg_cmd += ['-ss', ms_to_s(input[:seek]), '-itsoffset', ms_to_s(input[:seek]), '-i', input[:filename]]
end
ffmpeg_cmd += ['-filter_complex', ffmpeg_filter, *FFMPEG_WF_ARGS, '-']
File.open(output, 'a') do |outio|

View File

@ -292,6 +292,24 @@ module BigBlueButton
}
}
when 'DeskshareStoppedEvent'
# Fill in the original/expected video duration when available
duration = event.at_xpath('duration')
if !duration.nil?
duration = duration.text.to_i
filename = event.at_xpath('file').text
filename = "#{archive_dir}/deskshare/#{File.basename(filename)}"
deskshare_edl.each do |entry|
if !entry[:areas][:deskshare].nil?
entry[:areas][:deskshare].each do |file|
if file[:filename] == filename
file[:original_duration] = duration * 1000
end
end
end
end
end
# Terminating entry
deskshare_edl << {
:timestamp => timestamp,
:areas => { :deskshare => [] }
@ -336,7 +354,8 @@ module BigBlueButton
videos.each do |video|
new_entry[:areas][area] << {
:filename => video[:filename],
:timestamp => video[:timestamp] + offset
:timestamp => video[:timestamp] + offset,
:original_duration => video[:original_duration]
}
end
end

View File

@ -21,23 +21,11 @@
require 'rubygems'
require 'streamio-ffmpeg'
require File.expand_path('../../edl', __FILE__)
module BigBlueButton
def self.get_video_height(video)
FFMPEG::Movie.new(video).height
end
def self.get_video_width(video)
FFMPEG::Movie.new(video).width
end
def self.is_video_valid?(video)
FFMPEG::Movie.new(video).valid?
end
def BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, output_width, output_height, audio_offset, processed_audio_file)
BigBlueButton.logger.info("Processing webcam videos")

View File

@ -22,6 +22,7 @@ require '../lib/recordandplayback'
require 'rubygems'
require 'yaml'
require 'fileutils'
require 'custom_hash'
def publish_processed_meetings(recording_dir)
processed_done_files = Dir.glob("#{recording_dir}/status/processed/*.done")

View File

@ -246,31 +246,34 @@ function runPopcorn() {
var xmlDoc = xmlhttp.responseXML;
console.log("** Processing shapes_svg");
var shapeelement = xmlDoc.getElementsByTagName("svg")[0];
// Get an array of the elements for each "shape" in the drawing
var shapesArray = xmlDoc.querySelectorAll('g[class="shape"]');
//getting all the event tags
var shapeelements = xmlDoc.getElementsByTagName("svg");
// To assist in finding the version of a shape shown at a particular time
// (while being drawn, during updates), provide a lookup from time to id
// Also save the id of the last version of each shape as its main id
//get the array of values for the first shape (getDataPoints(0) is the first shape).
var shapesArray = $(shapeelements[0]).find("g").filter(function(){ //get all the lines from the svg file
return $(this).attr('class') == 'shape';
});
//create a map from timestamp to id list
var timestampToId = {};
var timestampToIdKeys = [];
var mainShapeIds = {};
for (var j = 0; j < shapesArray.length; j++) {
shape = shapesArray[j];
var id = shape.getAttribute('id');
var shape_i = shape.getAttribute('shape');
var time = (parseFloat(shape.getAttribute('timestamp')) * 10) | 0;
shapeTime = shapesArray[j].getAttribute("timestamp");
shapeId = shapesArray[j].getAttribute("id");
if (timestampToId[time] == undefined) {
timestampToId[time] = [];
timestampToIdKeys.push(time);
if (timestampToId[shapeTime] == undefined) {
timestampToId[shapeTime] = new Array(0);
}
timestampToId[time].push({id: id, shape: shape_i})
mainShapeIds[shape_i] = id;
timestampToId[shapeTime].push(shapeId);
}
//fill the times array with the times of the svg images.
for (var j = 0; j < shapesArray.length; j++) {
times[j] = shapesArray[j].getAttribute("timestamp");
}
var times_length = times.length; //get the length of the times array.
// PROCESS PANZOOMS.XML
console.log("** Getting panzooms.xml");
xmlhttp.open("GET", events_xml, false);
@ -338,40 +341,36 @@ function runPopcorn() {
svgobj.style.left = document.getElementById("slide").offsetLeft + "px";
svgobj.style.top = "0px";
var next_shape;
var shape;
for (var j = 0; j < shapesArray.length - 1; j++) { //iterate through all the shapes and pick out the main ones
var time = shapesArray[j].getAttribute("timestamp");
shape = shapesArray[j].getAttribute("shape");
next_shape = shapesArray[j+1].getAttribute("shape");
// Find the key in the timestampToId object of the last entry at or before the provided timestamp
var timestampToIdLookup = function(t) {
"use strict";
t = (t * 10) | 0; // keys are in deciseconds as integers
var minIndex = 0;
var maxIndex = timestampToIdKeys.length - 1;
var curIndex;
var curElement;
// I can't think of any better way to do this than just binary search.
while (minIndex <= maxIndex) {
curIndex = (minIndex + maxIndex) / 2 | 0;
curElement = timestampToIdKeys[curIndex];
if (curElement < t) {
minIndex = curIndex + 1;
} else if (curElement > t) {
maxIndex = curIndex - 1;
} else {
return curElement;
}
if(shape !== next_shape) {
main_shapes_ids.push(shapesArray[j].getAttribute("id"));
}
return timestampToIdKeys[maxIndex];
}
if (shapesArray.length !== 0) {
main_shapes_ids.push(shapesArray[shapesArray.length-1].getAttribute("id")); //put last value into this array always!
}
var get_shapes_in_time = function(t, shapes) {
"use strict";
var timestampToIdIndex = timestampToIdLookup(t);
var shapes_in_time = timestampToId[timestampToIdIndex];
var get_shapes_in_time = function(t) {
// console.log("** Getting shapes in time");
var shapes_in_time = timestampToId[t];
var shapes = [];
if (shapes_in_time != undefined) {
var shape = null;
for (var i = 0; i < shapes_in_time.length; i++) {
var s = shapes_in_time[i];
shapes[s.shape] = s.id;
var id = shapes_in_time[i];
if(svgobj.contentDocument) shape = svgobj.contentDocument.getElementById(id);
else shape = svgobj.getSVGDocument('svgfile').getElementById(id);
if (shape !== null) { //if there is actually a new shape to be displayed
shape = shape.getAttribute("shape"); //get actual shape tag for this specific time of playback
shapes.push(shape);
}
}
}
return shapes;
@ -383,41 +382,55 @@ function runPopcorn() {
start: 1, // start time
end: p.duration(),
onFrame: function(options) {
"use strict";
var currentTime = p.currentTime();
if ( (!p.paused() || p.seeking()) && (Math.abs(currentTime - lastFrameTime) >= 0.1) ) {
lastFrameTime = currentTime;
var t = currentTime.toFixed(1); //get the time and round to 1 decimal place
// Create an object referencing the main versions of all the shapes
var current_shapes = Object.create(mainShapeIds);
// And update it with current state of currently being drawn shapes
get_shapes_in_time(t, current_shapes);
current_shapes = get_shapes_in_time(t);
// Update shape visibility status
//redraw everything (only way to make everything elegant)
for (var i = 0; i < shapesArray.length; i++) {
var a_shape = shapesArray[i];
var time = parseFloat(a_shape.getAttribute('timestamp'));
var shapeId = a_shape.getAttribute('id');
var shape_i = a_shape.getAttribute('shape');
var undo = parseFloat(a_shape.getAttribute('undo'));
var time_s = shapesArray[i].getAttribute("timestamp");
var time_f = parseFloat(time_s);
var shape = null;
if (svgobj.contentDocument) shape = svgobj.contentDocument.getElementById(shapesArray[i].getAttribute("id"));
if(svgobj.contentDocument) shape = svgobj.contentDocument.getElementById(shapesArray[i].getAttribute("id"));
else shape = svgobj.getSVGDocument('svgfile').getElementById(shapesArray[i].getAttribute("id"));
if (shape != null) {
if (
// It's not the current version of the shape
(shapeId != current_shapes[shape_i]) ||
// It's in the future
(time > t) ||
// It's in the past (undo or clear)
((undo != -1) && (undo < currentTime))) {
shape.style.visibility = 'hidden';
} else {
shape.style.visibility = 'visible';
}
if(shape != null) {
var shape_i = shape.getAttribute("shape");
if (time_f < t) {
if(current_shapes.indexOf(shape_i) > -1) { //currently drawing the same shape so don't draw the older steps
shape.style.visibility = "hidden"; //hide older steps to shape
} else if(main_shapes_ids.indexOf(shape.getAttribute("id")) > -1) { //as long as it is a main shape, it can be drawn... no intermediate steps.
if(parseFloat(shape.getAttribute("undo")) === -1) { //As long as the undo event hasn't happened yet...
shape.style.visibility = "visible";
} else if (parseFloat(shape.getAttribute("undo")) > t) {
shape.style.visibility = "visible";
} else {
shape.style.visibility = "hidden";
}
} else {
shape.style.visibility = "hidden";
}
} else if(time_s === t) { //for the shapes with the time specific to the current time
// only makes visible the last drawing of a given shape
var idx = current_shapes.indexOf(shape_i);
if (idx > -1) {
current_shapes.splice(idx, 1);
idx = current_shapes.indexOf(shape_i);
if (idx > -1) {
shape.style.visibility = "hidden";
} else {
shape.style.visibility = "visible";
}
} else {
// this is an inconsistent state, since current_shapes should have at least one drawing of this shape
shape.style.visibility = "hidden";
}
} else { //for shapes that shouldn't be drawn yet (larger time than current time), don't draw them.
shape.style.visibility = "hidden";
}
}
}
@ -605,6 +618,9 @@ function defineStartTime() {
var lastFrameTime = 0.0;
var shape;
var current_shapes = [];
var deskshare_image = null;
var current_image = "image0";
var previous_image = null;
@ -614,12 +630,20 @@ var next_image;
var next_pgid;
var curr_pgid;
var svgfile;
//current time
var t;
var len;
var meetingDuration;
//coordinates for x and y for each second
var panAndZoomTimes = [];
var viewBoxes = [];
var coords = [];
var times = [];
// timestamp and id for drawings
var shapeTime;
var shapeId;
var clearTimes = [];
var main_shapes_ids = [];
var vboxValues = {};
var slideAspectValues = {};
var currentSlideAspect = 0;

View File

@ -0,0 +1,222 @@
/*
* Acorn Media Player - jQuery plugin 1.0
*
* Copyright (C) 2010 Cristian I. Colceriu
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* www.ghinda.net
* contact@ghinda.net
*
* Base stylesheet
*
*/
/* Main elements */
.acorn-player, .acorn-controls {
position: relative;
}
.acorn-timer {
cursor: default;
}
.acorn-buffer {
width: 0px;
}
/* <video> */
.acorn-player video {
background-color: #000;
}
/* <audio> */
.acorn-player.audio-player {
width: 500px;
}
.acorn-player.audio-player audio {
display: none;
}
/* Captions and Transcript */
.acorn-transcript {
clear: both;
display: none;
overflow: auto;
height: 15em;
}
.acorn-transcript-button {
display: none;
}
/*
* Show the timings in square brackets before each "subtitle" in the transcript.
* Borrowed and adapted from Bruce Lawson's Accessible HTML5 Video with JavaScripted captions
* http://dev.opera.com/articles/view/accessible-html5-video-with-javascripted-captions/
*/
.acorn-transcript span {
display: block;
float: left;
width: 100%;
line-height: 1.5em;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
.acorn-transcript span:hover {
background-color: #cadde7 !important;
font-weight: bold;
}
.acorn-transcript span:nth-of-type(even) {
background-color: #efefef;
}
.acorn-transcript [data-begin]:before {
display: block;
float: left;
content: " [" attr(data-begin) "s-" attr(data-end)"s] ";
width: 15%;
padding: 0.2em 1.5em 0.2em 0.2em;
}
.acorn-caption {
display: none;
position: absolute;
bottom: 75px;
width: 100%;
text-align: center;
}
.acorn-caption-button {
display: none;
}
.acorn-caption-selector {
position: absolute;
display: none;
width: 170px;
padding: 5px;
height: 75px;
margin-bottom: 10px;
overflow: auto;
background-color: #000;
border: 3px solid #fff;
z-index: 3;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-box-shadow: 0px 1px 5px #000;
-webkit-box-shadow: 0px 1px 5px #000;
box-shadow: 0px 1px 5px #000;
}
.acorn-caption-selector label {
display: block;
font-weight: bold;
color: #fff;
}
.acorn-caption-selector ul, .acorn-caption-selector li {
list-style-type: none;
margin: 0px;
padding: 0px;
}
/* Fullscreen Mode */
.fullscreen-video {
position: fixed !important;
top: 0px;
left: 0px;
z-index: 99999 !important;
background-color: #000;
}
.acorn-controls.fullscreen-controls {
position: fixed !important;
z-index: 100000 !important;
}
/* Loading */
.show-loading .loading-media {
visibility: visible;
}
.loading-media {
visibility: hidden;
position: absolute;
left: 25%;
top: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-lefT: -10px;
background-color: #000;
border: 5px solid #fff;
border-top: 5px solid rgba(0,0,0,0);
border-left: 5px solid rgba(0,0,0,0);
border-radius: 20px;
animation: spin 1s infinite linear;
-o-animation: spin 1s infinite linear;
-moz-animation: spin 1s infinite linear;
-webkit-animation: spin 1s infinite linear;
}
@-o-keyframes spin {
0% { -o-transform:rotate(0deg); }
100% { -o-transform:rotate(360deg); }
}
@-ms-keyframes spin {
0% { -ms-transform:rotate(0deg); }
100% { -ms-transform:rotate(360deg); }
}
@-moz-keyframes spin {
0% { -moz-transform:rotate(0deg); }
100% { -moz-transform:rotate(360deg); }
}
@-webkit-keyframes spin {
0% { -webkit-transform:rotate(0deg); }
100% { -webkit-transform:rotate(360deg); }
}
@keyframes spin {
0% { transform:rotate(0deg); }
100% { transform:rotate(360deg); }
}
/* Controls overlay while loading */
.show-loading .acorn-controls:after {
content: '';
position: absolute;
top: -2px; /* Slider handle goes above */
padding-bottom: 2px;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
background: #000;
opacity: 0.9;
}
/* Styles needed for the jQuery UI slider
* We're declaring these so we don't have to use jQuery UI's stylesheet
*/
a.ui-slider-handle {
position: absolute;
display: block;
margin-left: -0.6em;
z-index: 2;
cursor: default;
outline: none;
}
.ui-slider {
position: relative;
}
.ui-slider-range {
position: absolute;
display: block;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
border: none;
z-index: 1;
}

View File

@ -0,0 +1,962 @@
/*
* Acorn Media Player - jQuery plugin 1.6
*
* Copyright (C) 2012 Ionut Cristian Colceriu
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* www.ghinda.net
* contact@ghinda.net
*
*/
(function($) {
$.fn.acornMediaPlayer = function(options) {
/*
* Define default plugin options
*/
var defaults = {
theme: 'access',
nativeSliders: false,
volumeSlider: 'horizontal',
captionsOn: false
};
options = $.extend(defaults, options);
/*
* Function for generating a unique identifier using the current date and time
* Used for generating an ID for the media elmenet when none is available
*/
var uniqueID = function() {
var currentDate = new Date();
return currentDate.getTime();
};
/*
* Detect support for localStorage
*/
function supports_local_storage() {
try {
return 'localStorage' in window && window.localStorage !== null;
} catch(e){
return false;
}
}
/*
* Get the volume value from localStorage
* If no value is present, define as maximum
*/
var volume = (supports_local_storage) ? localStorage.getItem('acornvolume') : 1;
if(!volume) {
volume = 1;
}
/*
* Main plugin function
* It will be called on each element in the matched set
*/
var acornPlayer = function() {
// set the acorn object, will contain the needed DOM nodes and others
var acorn = {
$self: $(this)
};
var loadedMetadata; // Is the metadata loaded
var seeking; // The user is seeking the media
var wasPlaying; // Media was playing when the seeking started
var fullscreenMode; // The media is in fullscreen mode
var captionsActive; // Captions are active
/* Define all the texts used
* This makes it easier to maintain, make translations, etc.
*/
var text = {
play: 'Play',
playTitle: 'Start the playback',
pause: 'Pause',
pauseTitle: 'Pause the playback',
mute: 'Mute',
unmute: 'Unmute',
fullscreen: 'Fullscreen',
fullscreenTitle: 'Toggle fullscreen mode',
swap: 'Swap',
swapTitle: 'Toggle video and presention swap',
volumeTitle: 'Volume control',
seekTitle: 'Video seek control',
captions: 'Captions',
captionsTitle: 'Show captions',
captionsChoose: 'Choose caption',
transcript: 'Transcript',
transcriptTitle: 'Show transcript'
};
// main wrapper element
var $wrapper = $('<div class="acorn-player" role="application"></div>').addClass(options.theme);
/*
* Define attribute tabindex on the main element to make it readchable by keyboard
* Useful when "aria-describedby" is present
*
* It makes more sense for screen reader users to first reach the actual <video> or <audio> elment and read of description of it,
* than directly reach the Media Player controls, without knowing what they control.
*/
acorn.$self.attr('tabindex', '0');
/*
* Check if the main element has an ID attribute
* If not present, generate one
*/
acorn.id = acorn.$self.attr('id');
if(!acorn.id) {
acorn.id = 'acorn' + uniqueID();
acorn.$self.attr('id', acorn.id);
}
/*
* Markup for the fullscreen button
* If the element is not <video> we leave if blank, as the button if useless on <audio> elements
*/
var fullscreenBtnMarkup = (acorn.$self.is('video')) ? '<button class="acorn-fullscreen-button" title="' + text.fullscreenTitle + '" aria-controls="' + acorn.id + '" tabindex="3">' + text.fullscreen + '</button>' : '';
/*
* Markup for the swap button
* If the element is not <video> we leave if blank, as the button if useless on <audio> elements
*/
var swapBtnMarkup = (acorn.$self.is('video')) ? '<button class="acorn-swap-button" title="' + text.swapTitle + '" aria-controls="' + acorn.id + '" tabindex="4" >' + text.swap + '</button>' : '';
/*
* Complete markup
*/
var template = '<div class="acorn-controls">' +
'<button class="acorn-play-button" title="' + text.playTitle + '" aria-controls="' + acorn.id + '" tabindex="1">' + text.play + '</button>' +
'<input type="range" class="acorn-seek-slider" title="' + text.seekTitle + '" value="0" min="0" max="150" step="0.1" aria-controls="' + acorn.id + '" tabindex="2" />' +
'<span class="acorn-timer">00:00</span>' +
'<div class="acorn-volume-box">' +
'<button class="acorn-volume-button" title="' + text.mute + '" aria-controls="' + acorn.id + '" tabindex="5" >' + text.mute + '</button>' +
'<input type="range" class="acorn-volume-slider" title="' + text.volumeTitle + '" value="1" min="0" max="1" step="0.05" aria-controls="' + acorn.id + '" tabindex="6" />' +
'</div>' +
swapBtnMarkup +
fullscreenBtnMarkup +
'<button class="acorn-caption-button" title="' + text.captionsTitle + '" aria-controls="' + acorn.id + '">' + text.captions + '</button>' +
'<div class="acorn-caption-selector"></div>' +
'<button class="acorn-transcript-button" title="' + text.transcriptTitle + '">' + text.transcript + '</button>' +
'</div>';
var captionMarkup = '<div class="acorn-caption"></div>';
var transcriptMarkup = '<div class="acorn-transcript" role="region" aria-live="assertive"></div>';
/*
* Append the HTML markup
*/
// append the wrapper
acorn.$self.after($wrapper);
// For iOS support, I have to clone the node, remove the original, and get a reference to the new one.
// This is because iOS doesn't want to play videos that have just been `moved around`.
// More details on the issue: http://bugs.jquery.com/ticket/8015
$wrapper[0].appendChild( acorn.$self[0].cloneNode(true) );
acorn.$self.trigger('pause');
acorn.$self.remove();
acorn.$self = $wrapper.find('video, audio');
// append the controls and loading mask
acorn.$self.after(template).after('<div class="loading-media"></div>');
/*
* Define the newly created DOM nodes
*/
acorn.$container = acorn.$self.parent('.acorn-player');
acorn.$controls = $('.acorn-controls', acorn.$container);
acorn.$playBtn = $('.acorn-play-button', acorn.$container);
acorn.$seek = $('.acorn-seek-slider', acorn.$container);
acorn.$timer = $('.acorn-timer', acorn.$container);
acorn.$volume = $('.acorn-volume-slider', acorn.$container);
acorn.$volumeBtn = $('.acorn-volume-button', acorn.$container);
acorn.$fullscreenBtn = $('.acorn-fullscreen-button', acorn.$container);
acorn.$swapBtn = $('.acorn-swap-button', acorn.$container);
/*
* Append the markup for the Captions and Transcript
* and define newly created DOM nodes for these
*/
acorn.$controls.after(captionMarkup);
acorn.$container.after(transcriptMarkup);
acorn.$transcript = acorn.$container.next('.acorn-transcript');
acorn.$transcriptBtn = $('.acorn-transcript-button', acorn.$container);
acorn.$caption = $('.acorn-caption', acorn.$container);
acorn.$captionBtn = $('.acorn-caption-button', acorn.$container);
acorn.$captionSelector = $('.acorn-caption-selector', acorn.$container);
/*
* Use HTML5 "data-" attributes to set the original Width&Height for the <video>
* These are used when returning from Fullscreen Mode
*/
acorn.$self.attr('data-width', acorn.$self.width());
acorn.$self.attr('data-height', acorn.$self.height());
/*
* Time formatting function
* Takes the number of seconds as a parameter and return a readable format "minutes:seconds"
* Used with the number of seconds returned by "currentTime"
*/
var timeFormat = function(sec) {
var m = Math.floor(sec/60)<10?"0" + Math.floor(sec/60):Math.floor(sec/60);
var s = Math.floor(sec-(m*60))<10?"0" + Math.floor(sec-(m*60)):Math.floor(sec-(m*60));
return m + ":" + s;
};
/*
* PLAY/PAUSE Behaviour
*
* Function for the Play button
* It triggers the native Play or Pause events
*/
var playMedia = function() {
if(!acorn.$self.prop('paused')) {
acorn.$self.trigger('pause');
} else {
//acorn.$self.trigger('play');
acorn.$self[0].play();
}
// We return false to stop the followup click event on tablets
return false;
};
/*
* Functions for native playback events (Play, Pause, Ended)
* These are attached to the native media events.
*
* Even if the user is still using some form of native playback control (such as using the Context Menu)
* it will not break the behviour of our player.
*/
var startPlayback = function() {
acorn.$playBtn.text(text.pause).attr('title', text.pauseTitle);
acorn.$playBtn.addClass('acorn-paused-button');
// if the metadata is not loaded yet, add the loading class
if (!loadedMetadata) $wrapper.addClass('show-loading');
};
var stopPlayback = function() {
acorn.$playBtn.text(text.play).attr('title', text.playTitle);
acorn.$playBtn.removeClass('acorn-paused-button');
};
/*
* SEEK SLIDER Behaviour
*
* Updates the Timer and Seek Slider values
* Is called on each "timeupdate"
*/
var seekUpdate = function() {
var currenttime = acorn.$self.prop('currentTime');
acorn.$timer.text(timeFormat(currenttime));
// If the user is not manualy seeking
if(!seeking) {
// Check type of sliders (Range <input> or jQuery UI)
if(options.nativeSliders) {
acorn.$seek.attr('value', currenttime);
} else {
acorn.$seek.slider('value', currenttime);
}
}
};
/*
* Time formatting function
* Takes the number of seconds as a paramenter
*
* Used with "aria-valuetext" on the Seek Slider to provide a human readable time format to AT
* Returns "X minutes Y seconds"
*/
var ariaTimeFormat = function(sec) {
var m = Math.floor(sec/60)<10?"" + Math.floor(sec/60):Math.floor(sec/60);
var s = Math.floor(sec-(m*60))<10?"" + Math.floor(sec-(m*60)):Math.floor(sec-(m*60));
var formatedTime;
var mins = 'minutes';
var secs = 'seconds';
if(m == 1) {
min = 'minute';
}
if(s == 1) {
sec = 'second';
}
if(m === 0) {
formatedTime = s + ' ' + secs;
} else {
formatedTime = m + ' ' + mins + ' ' + s + ' ' + secs;
}
return formatedTime;
};
/*
* jQuery UI slider uses preventDefault when clicking any element
* so it stops the Blur event from being fired.
* This causes problems with the Caption Selector.
* We trigger the Blur event manually.
*/
var blurCaptionBtn = function() {
acorn.$captionBtn.trigger('blur');
};
/*
* Triggered when the user starts to seek manually
* Pauses the media during seek and changes the "currentTime" to the slider's value
*/
var startSeek = function(e, ui) {
if(!acorn.$self.attr('paused')) {
wasPlaying = true;
}
acorn.$self.trigger('pause');
seeking = true;
var seekLocation;
if(options.nativeSliders) {
seekLocation = acorn.$seek.val();
} else {
seekLocation = ui.value;
}
acorn.$self[0].currentTime = seekLocation;
// manually blur the Caption Button
blurCaptionBtn();
};
/*
* Triggered when user stoped manual seek
* If the media was playing when seek started, it triggeres the playback,
* and updates ARIA attributes
*/
var endSeek = function(e, ui) {
if(wasPlaying) {
acorn.$self.trigger('play');
wasPlaying = false;
}
seeking = false;
var sliderUI = $(ui.handle);
sliderUI.attr("aria-valuenow", parseInt(ui.value, 10));
sliderUI.attr("aria-valuetext", ariaTimeFormat(ui.value));
};
/*
* Transforms element into ARIA Slider adding attributes and "tabindex"
* Used on jQuery UI sliders
*
* Will not needed once the jQuery UI slider gets built-in ARIA
*/
var initSliderAccess = function (elem, opts) {
var accessDefaults = {
'role': 'slider',
'aria-valuenow': parseInt(opts.value, 10),
'aria-valuemin': parseInt(opts.min, 10),
'aria-valuemax': parseInt(opts.max, 10),
'aria-valuetext': opts.valuetext
};
elem.attr(accessDefaults);
};
/*
* Init jQuery UI slider
*/
var initSeek = function() {
// get existing classes
var seekClass = acorn.$seek.attr('class');
// create the new markup
var divSeek = '<div class="' + seekClass + '" title="' + text.seekTitle + '"></div>';
acorn.$seek.after(divSeek).remove();
// get the newly created DOM node
acorn.$seek = $('.' + seekClass, acorn.$container);
// create the buffer element
var bufferBar = '<div class="ui-slider-range acorn-buffer"></div>';
acorn.$seek.append(bufferBar);
// get the buffer element DOM node
acorn.$buffer = $('.acorn-buffer', acorn.$container);
// set up the slider options for the jQuery UI slider
var sliderOptions = {
value: 0,
step: 1,
orientation: 'horizontal',
range: 'min',
min: 0,
max: 100
};
// init the jQuery UI slider
acorn.$seek.slider(sliderOptions);
};
/*
* Seek slider update, after metadata is loaded
* Attach events, add the "duration" attribute and generate the jQuery UI Seek Slider
*/
var updateSeek = function() {
// Get the duration of the media
var duration = acorn.$self[0].duration;
// Check for the nativeSliders option
if(options.nativeSliders) {
acorn.$seek.attr('max', duration);
acorn.$seek.bind('change', startSeek);
acorn.$seek.bind('mousedown', startSeek);
acorn.$seek.bind('mouseup', endSeek);
} else {
// set up the slider options for the jQuery UI slider
var sliderOptions = {
value: 0,
step: 1,
orientation: 'horizontal',
range: 'min',
min: 0,
max: duration,
slide: startSeek,
stop: endSeek
};
// init the jQuery UI slider
acorn.$seek.slider('option', sliderOptions);
// add valuetext value to the slider options for better ARIA values
sliderOptions.valuetext = ariaTimeFormat(sliderOptions.value);
// accessify the slider
initSliderAccess(acorn.$seek.find('.ui-slider-handle'), sliderOptions);
// manully blur the Caption Button when clicking the handle
$('.ui-slider-handle', acorn.$seek).click(blurCaptionBtn);
// set the tab index
$('.ui-slider-handle', acorn.$seek).attr("tabindex", "2");
// show buffering progress on progress
acorn.$self.bind('progress', showBuffer);
}
$wrapper.removeClass('show-loading');
// remove the loading element
//acorn.$self.next('.loading-media').remove();
};
/*
* Show buffering progress
*/
var showBuffer = function(e) {
var max = parseInt(acorn.$self.prop('duration'), 10);
var tr = this.buffered;
if(tr && tr.length) {
var buffer = parseInt(this.buffered.end(0)-this.buffered.start(0), 10);
var bufferWidth = (buffer*100)/max;
acorn.$buffer.css('width', bufferWidth + '%');
}
};
/*
* VOLUME BUTTON and SLIDER Behaviour
*
* Change volume using the Volume Slider
* Also update ARIA attributes and set the volume value as a localStorage item
*/
var changeVolume = function(e, ui) {
// get the slider value
volume = ui.value;
// set the value as a localStorage item
localStorage.setItem('acornvolume', volume);
// check if the volume was muted before
if(acorn.$self.prop('muted')) {
acorn.$self.prop('muted', false);
acorn.$volumeBtn.removeClass('acorn-volume-mute');
acorn.$volumeBtn.text(text.mute).attr('title', text.mute);
}
// set the new volume on the media
acorn.$self.prop('volume', volume);
// set the ARIA attributes
acorn.$volume.$handle.attr("aria-valuenow", Math.round(volume*100));
acorn.$volume.$handle.attr("aria-valuetext", Math.round(volume*100) + ' percent');
// manually trigger the Blur event on the Caption Button
blurCaptionBtn();
};
/*
* Mute and Unmute volume
* Also add classes and change label on the Volume Button
*/
var muteVolume = function() {
if(acorn.$self.prop('muted') === true) {
acorn.$self.prop('muted', false);
if(options.nativeSliders) {
acorn.$volume.val(volume);
} else {
acorn.$volume.slider('value', volume);
}
acorn.$volumeBtn.removeClass('acorn-volume-mute');
acorn.$volumeBtn.text(text.mute).attr('title', text.mute);
} else {
acorn.$self.prop('muted', true);
if(options.nativeSliders) {
acorn.$volume.val('0');
} else {
acorn.$volume.slider('value', '0');
}
acorn.$volumeBtn.addClass('acorn-volume-mute');
acorn.$volumeBtn.text(text.unmute).attr('title', text.unmute);
}
};
/*
* Init the Volume Button and Slider
*
* Attach events, create the jQuery UI Slider for the Volume Slider and add ARIA support
*/
var initVolume = function() {
if(options.nativeSliders) {
acorn.$volume.bind('change', function() {
acorn.$self.prop('muted',false);
volume = acorn.$volume.val();
acorn.$self.prop('volume', volume);
});
} else {
var volumeClass = acorn.$volume.attr('class');
var divVolume = '<div class="' + volumeClass + '" title="' + text.volumeTitle + '"></div>';
acorn.$volume.after(divVolume).remove();
acorn.$volume = $('.' + volumeClass, acorn.$container);
var volumeSliderOptions = {
value: volume,
orientation: options.volumeSlider,
range: "min",
max: 1,
min: 0,
step: 0.1,
animate: false,
slide: changeVolume
};
acorn.$volume.slider(volumeSliderOptions);
acorn.$volume.$handle = acorn.$volume.find('.ui-slider-handle');
// change and add values to volumeSliderOptions for better values in the ARIA attributes
volumeSliderOptions.max = 100;
volumeSliderOptions.value = volumeSliderOptions.value * 100;
volumeSliderOptions.valuetext = volumeSliderOptions.value + ' percent';
initSliderAccess(acorn.$volume.$handle, volumeSliderOptions);
acorn.$volume.$handle.attr("tabindex", "6");
// show the volume slider when it is tabbed into
acorn.$volume.$handle.focus(function(){
if (!acorn.$volume.parent().is(":hover")) {
acorn.$volume.addClass("handle-focused");
}
});
acorn.$volume.$handle.blur(function(){
acorn.$volume.removeClass("handle-focused");
});
// manully blur the Caption Button when clicking the handle
$('.ui-slider-handle', acorn.$volume).click(blurCaptionBtn);
}
acorn.$volumeBtn.click(muteVolume);
};
/*
* FULLSCREEN Behviour
*
* Resize the video while in Fullscreen Mode
* Attached to window.resize
*/
var resizeFullscreenVideo = function() {
acorn.$self.attr({
'width': $(window).width(),
'height': $(window).height()
});
};
/*
* Enter and exit Fullscreen Mode
*
* Resizes the Width & Height of the <video> element
* and add classes to the controls and wrapper
*/
var goFullscreen = function() {
if(fullscreenMode) {
if(acorn.$self[0].webkitSupportsFullscreen) {
acorn.$self[0].webkitExitFullScreen();
} else {
$('body').css('overflow', 'auto');
var w = acorn.$self.attr('data-width');
var h = acorn.$self.attr('data-height');
acorn.$self.removeClass('fullscreen-video').attr({
'width': w,
'height': h
});
$(window).unbind('resize');
acorn.$controls.removeClass('fullscreen-controls');
}
fullscreenMode = false;
} else {
if(acorn.$self[0].webkitSupportsFullscreen) {
acorn.$self[0].webkitEnterFullScreen();
} else if (acorn.$self[0].mozRequestFullScreen) {
acorn.$self[0].mozRequestFullScreen();
acorn.$self.attr('controls', 'controls');
document.addEventListener('mozfullscreenchange', function() {
console.log('screenchange event found');
if (!document.mozFullScreenElement) {
acorn.$self.removeAttr('controls');
//document.removeEventListener('mozfullscreenchange');
}
});
} else {
$('body').css('overflow', 'hidden');
acorn.$self.addClass('fullscreen-video').attr({
width: $(window).width(),
height: $(window).height()
});
$(window).resize(resizeFullscreenVideo);
acorn.$controls.addClass('fullscreen-controls');
}
fullscreenMode = true;
}
};
/*
* Swap the video and presentation areas
*
* Resizes and moves based on hard coded numbers
* Uses css to move it
*/
var goSwap = function() {
acorn.$self.trigger('swap');
}
/*
* CAPTIONS Behaviour
*
* Turning off the captions
* When selecting "None" from the Caption Selector or when the caption fails to load
*/
var captionBtnActiveClass = 'acorn-caption-active';
var captionBtnLoadingClass = 'acorn-caption-loading';
var transcriptBtnActiveClass = 'acorn-transcript-active';
var captionRadioName = 'acornCaptions' + uniqueID();
var captionOff = function() {
for (var i = 0; i < acorn.$track.length; i++) {
var track = acorn.$track[i];
track.track.mode = "disabled";
}
acorn.$captionBtn.removeClass(captionBtnActiveClass);
};
/*
* Initialize the Caption Selector
* Used when multiple <track>s are present
*/
var initCaptionSelector = function() {
// calculate the position relative to the parent controls element
var setUpCaptionSelector = function() {
var pos = acorn.$captionBtn.offset();
var top = pos.top - acorn.$captionSelector.outerHeight(true);
var left = pos.left - ((acorn.$captionSelector.outerWidth(true) - acorn.$captionBtn.outerWidth(true))/2);
var parentPos = acorn.$controls.offset();
left = left - parentPos.left;
top = top - parentPos.top;
acorn.$captionSelector.css({
'top': top,
'left': left
});
};
acorn.$fullscreenBtn.click(setUpCaptionSelector);
$(window).resize(function() {
setUpCaptionSelector();
});
setUpCaptionSelector();
/*
* Show and hide the caption selector based on focus rather than hover.
* This benefits both touchscreen and AT users.
*/
var hideSelector; // timeout for hiding the Caption Selector
var showCaptionSelector = function() {
if(hideSelector) {
clearTimeout(hideSelector);
}
acorn.$captionSelector.show();
};
var hideCaptionSelector = function() {
hideSelector = setTimeout(function() {
acorn.$captionSelector.hide();
}, 200);
};
/* Little TEMPORARY hack to focus the caption button on click
This is because Webkit does not focus the button on click */
acorn.$captionBtn.click(function() {
$(this).focus();
});
acorn.$captionBtn.bind('focus', showCaptionSelector);
acorn.$captionBtn.bind('blur', hideCaptionSelector);
$('input[name=' + captionRadioName + ']', acorn.$container).bind('focus', showCaptionSelector);
$('input[name=' + captionRadioName + ']', acorn.$container).bind('blur', hideCaptionSelector);
/*
* Make the Caption Selector focusable and attach events to it
* If we wouldn't do this, when we'd use the scroll on the Caption Selector, it would dissapear
*/
acorn.$captionSelector.attr('tabindex', '-1');
acorn.$captionSelector.bind('focus', showCaptionSelector);
acorn.$captionSelector.bind('blur', hideCaptionSelector);
};
/*
* Current caption loader
* Loads a SRT file and uses it as captions
* Takes the url as a parameter
*/
var loadCaption = function(url) {
// Iterate through the available captions, and disable all but the selected one
for (var i = 0; i < acorn.$track.length; i++) {
var track = acorn.$track[i];
if (track.getAttribute('src') == url) {
track.track.mode = "showing";
// TODO transcript markup?
// show the Transcript Button
//acorn.$transcriptBtn.show();
/*
* Generate the markup for the transcript
* Markup based on Bruce Lawson's Accessible HTML5 Video with JavaScripted captions
* http://dev.opera.com/articles/view/accessible-html5-video-with-javascripted-captions/
*/
//var transcriptText = '';
//$(captions).each(function() {
// transcriptText += '<span data-begin="' + parseInt(this.start, 10) + '" data-end=' + parseInt(this.end, 10) + '>' + this.content.replace("'","") + '</span>';
//});
// append the generated markup
//acorn.$transcript.html(transcriptText);
} else {
track.track.mode = "disabled";
}
}
captionsActive = true;
acorn.$captionBtn.addClass(captionBtnActiveClass);
};
/*
* Show or hide the Transcript based on the presence of the active class
*/
var showTranscript = function() {
if($(this).hasClass(transcriptBtnActiveClass)) {
acorn.$transcript.hide();
} else {
acorn.$transcript.show();
}
$(this).toggleClass(transcriptBtnActiveClass);
};
/*
* Caption loading and initialization
*/
var initCaption = function() {
// Check if we have browser support for captions
if (typeof(TextTrack) === "undefined") {
return;
}
// get all <track> elements
acorn.$track = $('track', acorn.$self);
// if there is at least one <track> element, show the Caption Button
if(acorn.$track.length) {
acorn.$captionBtn.show();
}
// check if there is more than one <track> element
// if there is more than one track element we'll create the Caption Selector
if(acorn.$track.length>1) {
// set a different "title" attribute
acorn.$captionBtn.attr('title', text.captionsChoose);
// markup for the Caption Selector
var captionList = '<ul><li><label><input type="radio" name="' + captionRadioName + '" checked="true" />None</label></li>';
acorn.$track.each(function() {
var tracksrc = $(this).attr('src');
captionList += '<li><label><input type="radio" name="' + captionRadioName + '" data-url="' + $(this).attr('src') + '" />' + $(this).attr('label') + '</label></li>';
});
captionList += '</ul>';
// append the generated markup
acorn.$captionSelector.html(captionList);
// change selected caption
var changeCaption = function() {
// get the original <track> "src" attribute from the custom "data-url" attribute of the radio input
var tracksrc = $(this).attr('data-url');
if(tracksrc) {
loadCaption(tracksrc);
} else {
// if there's not "data-url" attribute, turn off the caption
captionOff();
}
};
// attach event handler
$('input[name=' + captionRadioName + ']', acorn.$container).change(changeCaption);
// initialize Caption Selector
initCaptionSelector();
// load first caption if captionsOn is true
var firstCaption = acorn.$track.first().attr('src');
if(options.captionsOn) {
loadCaption(firstCaption);
$('input[name=' + captionRadioName + ']', acorn.$container).removeAttr('checked');
$('input[name=' + captionRadioName + ']:eq(1)', acorn.$container).attr('checked', 'true');
};
} else if(acorn.$track.length) {
// if there's only one <track> element
// load the specific caption when activating the Caption Button
var tracksrc = acorn.$track.attr('src');
acorn.$captionBtn.bind('click', function() {
if($(this).hasClass(captionBtnActiveClass)) {
captionOff();
} else {
loadCaption(tracksrc);
}
});
// load default caption if captionsOn is true
if(options.captionsOn) loadCaption(tracksrc);
}
// attach event to Transcript Button
acorn.$transcriptBtn.bind('click', showTranscript);
};
/*
* Initialization self-invoking function
* Runs other initialization functions, attaches events, removes native controls
*/
var init = function() {
// attach playback handlers
acorn.$playBtn.bind( 'touchstart click', playMedia);
acorn.$self.bind( 'touchstart click' , playMedia);
acorn.$self.bind('play', startPlayback);
acorn.$self.bind('pause', stopPlayback);
acorn.$self.bind('ended', stopPlayback);
// update the Seek Slider when timeupdate is triggered
acorn.$self.bind('timeupdate', seekUpdate);
// bind Fullscreen Button
acorn.$fullscreenBtn.click(goFullscreen);
// bind Swap Button
acorn.$swapBtn.click(goSwap);
// initialize volume controls
initVolume();
// add the loading class
$wrapper.addClass('');
if(!options.nativeSliders) initSeek();
// once the metadata has loaded
acorn.$self.bind('loadedmetadata', function() {
/* I use an interval to make sure the video has the right readyState
* to bypass a known webkit bug that causes loadedmetadata to be triggered
* before the duration is available
*/
var t = window.setInterval(function() {
if (acorn.$self[0].readyState > 0) {
loadedMetadata = true;
updateSeek();
clearInterval(t);
}
}, 500);
initCaption();
});
// trigger update seek manualy for the first time, for iOS support
updateSeek();
// remove the native controls
acorn.$self.removeAttr('controls');
if(acorn.$self.is('audio')) {
/*
* If the media is <audio>, we're adding the 'audio-player' class to the element.
* This is because Opera 10.62 does not allow the <audio> element to be targeted by CSS
* and this can cause problems with themeing.
*/
acorn.$container.addClass('audio-player');
}
}();
};
// iterate and reformat each matched element
return this.each(acornPlayer);
};
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

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