Merge remote-tracking branch 'upstream/bbb-2x-mconf' into improve-client-annotation-edit
@ -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)
|
||||
|
@ -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] = {
|
||||
|
@ -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 =>
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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="© 2017 <u><a href="http://www.bigbluebutton.org" target="_blank">http://www.bigbluebutton.org</a></u>" background="" toolbarColor="" />
|
||||
<branding logo="logo.swf" copyright="© 2017 <u><a href="http://HOST/home.html" target="_blank">BigBlueButton Inc.</a></u> (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"
|
||||
/>
|
||||
|
||||
<!--
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
2
bigbluebutton-client/src/org/bigbluebutton/main/model/options/LoggingOptions.as
Normal file → Executable 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";
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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/"
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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')}"
|
||||
|
@ -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()"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
2
bigbluebutton-client/src/org/bigbluebutton/modules/screenshare/view/components/ScreensharePublishWindow.mxml
Normal file → Executable 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" />
|
||||
|
@ -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";
|
||||
|
9
bigbluebutton-client/src/org/bigbluebutton/modules/sharednotes/services/MessageReceiver.as
Normal file → Executable 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;
|
||||
|
5
bigbluebutton-client/src/org/bigbluebutton/modules/sharednotes/views/AdditionalSharedNotesWindow.as
Normal file → Executable 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 {
|
||||
|
15
bigbluebutton-client/src/org/bigbluebutton/modules/sharednotes/views/SharedNotesNameWindow.mxml
Normal file → Executable 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"
|
||||
|
17
bigbluebutton-client/src/org/bigbluebutton/modules/sharednotes/views/SharedNotesWindow.mxml
Normal file → Executable 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) < (options.maxNoteLength / 8)}"
|
||||
text="{options.maxNoteLength - richTextEditor.text.length}"/>
|
||||
<mx:Button id="btnNew"
|
||||
styleName="sharedNotesNewButtonStyle"
|
||||
width="26"
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}" />
|
||||
|
@ -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;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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')}">
|
||||
|
@ -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;
|
||||
|
@ -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')}">
|
||||
|
@ -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')}">
|
||||
|
@ -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"
|
||||
|
@ -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')}">
|
||||
|
@ -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"
|
||||
|
@ -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')}">
|
||||
|
@ -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
|
||||
|
8
bigbluebutton-config/web/home.html
Normal 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>
|
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -6,7 +6,7 @@ class Page {
|
||||
}
|
||||
|
||||
pressEnter() {
|
||||
browser.keys('Enter');
|
||||
chromeBrowser.keys('Enter');
|
||||
}
|
||||
|
||||
isFirefox() {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
19
bigbluebutton-html5/tests/webdriverio/utils.js
Normal 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();
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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|
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 349 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 368 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 354 B |
After Width: | Height: | Size: 956 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 254 B |
After Width: | Height: | Size: 1011 B |
After Width: | Height: | Size: 251 B |
After Width: | Height: | Size: 1.0 KiB |