Merge branch 'development' into as3-logging

Conflicts:
	bigbluebutton-client/src/BigBlueButtonMainContainer.mxml
	bigbluebutton-client/src/org/bigbluebutton/core/services/UsersService.as
	bigbluebutton-client/src/org/bigbluebutton/main/maps/ApplicationEventMap.mxml
	bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as
	bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as
	bigbluebutton-client/src/org/bigbluebutton/main/views/MainCanvas.mxml
	bigbluebutton-client/src/org/bigbluebutton/main/views/WebRTCEchoTest.mxml
	bigbluebutton-client/src/org/bigbluebutton/modules/chat/services/MessageReceiver.as
	bigbluebutton-client/src/org/bigbluebutton/modules/deskshare/managers/PublishWindowManager.as
	bigbluebutton-client/src/org/bigbluebutton/modules/deskshare/services/red5/Connection.as
	bigbluebutton-client/src/org/bigbluebutton/modules/deskshare/view/components/DesktopPublishWindow.mxml
	bigbluebutton-client/src/org/bigbluebutton/modules/deskshare/view/components/DesktopViewWindow.mxml
	bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/ConnectionManager.as
	bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/FlashCallManager.as
	bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/WebRTCCallManager.as
	bigbluebutton-client/src/org/bigbluebutton/modules/polling/service/PollDataProcessor.as
	bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as
	bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as
	bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/UserGraphicHolder.mxml
	bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/business/shapes/PollResultObject.as
	bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/models/WhiteboardModel.as
This commit is contained in:
Ghazi Triki 2015-07-29 19:13:04 +01:00
commit 83666a7caa
87 changed files with 3195 additions and 1736 deletions

View File

@ -18,8 +18,8 @@ object PollFactory {
private def processYesNoPollType(qType: String): Question = { private def processYesNoPollType(qType: String): Question = {
val answers = new Array[Answer](2) val answers = new Array[Answer](2)
answers(0) = new Answer(0, "N", Some("No")) answers(0) = new Answer(0, "No", Some("No"))
answers(1) = new Answer(1, "Y", Some("Yes")) answers(1) = new Answer(1, "Yes", Some("Yes"))
new Question(0, PollType.YesNoPollType, false, None, answers) new Question(0, PollType.YesNoPollType, false, None, answers)
} }
@ -27,8 +27,8 @@ object PollFactory {
private def processTrueFalsePollType(qType: String): Question = { private def processTrueFalsePollType(qType: String): Question = {
val answers = new Array[Answer](2) val answers = new Array[Answer](2)
answers(0) = new Answer(0, "F", Some("False")) answers(0) = new Answer(0, "False", Some("False"))
answers(1) = new Answer(1, "T", Some("True")) answers(1) = new Answer(1, "True", Some("True"))
new Question(0, PollType.TrueFalsePollType, false, None, answers) new Question(0, PollType.TrueFalsePollType, false, None, answers)
} }

View File

@ -68,6 +68,9 @@ trait PollApp {
val shape = new scala.collection.mutable.HashMap[String, Object]() val shape = new scala.collection.mutable.HashMap[String, Object]()
shape += "num_respondents" -> new Integer(result.numRespondents) shape += "num_respondents" -> new Integer(result.numRespondents)
shape += "num_responders" -> new Integer(result.numResponders) shape += "num_responders" -> new Integer(result.numResponders)
shape += "type" -> "poll_result"
shape += "id" -> result.id
shape += "status" -> "DRAW_END"
val answers = new ArrayBuffer[java.util.HashMap[String, Object]]; val answers = new ArrayBuffer[java.util.HashMap[String, Object]];
result.answers.foreach(ans => { result.answers.foreach(ans => {
@ -81,12 +84,13 @@ trait PollApp {
val gson = new Gson() val gson = new Gson()
shape += "result" -> gson.toJson(answers.toArray) shape += "result" -> gson.toJson(answers.toArray)
// Hardcode poll result display location for now. // Hardcode poll result display location for now to display result
// in bottom-right corner.
val display = new ArrayList[Double]() val display = new ArrayList[Double]()
display.add(21.845575) display.add(66.0)
display.add(23.145401) display.add(60.0)
display.add(46.516006) display.add(34.0)
display.add(61.42433) display.add(40.0)
shape += "points" -> display shape += "points" -> display
shape.toMap shape.toMap

View File

@ -228,6 +228,7 @@ trait UsersApp {
} }
usersModel.removeUser(msg.userId) usersModel.removeUser(msg.userId)
usersModel.removeRegUser(msg.userId)
log.info("Ejecting user from meeting: mid=[" + mProps.meetingID + "]uid=[" + msg.userId + "]") log.info("Ejecting user from meeting: mid=[" + mProps.meetingID + "]uid=[" + msg.userId + "]")
outGW.send(new UserEjectedFromMeeting(mProps.meetingID, mProps.recorded, msg.userId, msg.ejectedBy)) outGW.send(new UserEjectedFromMeeting(mProps.meetingID, mProps.recorded, msg.userId, msg.ejectedBy))
@ -249,11 +250,14 @@ trait UsersApp {
def handleUserunshareWebcam(msg: UserUnshareWebcam) { def handleUserunshareWebcam(msg: UserUnshareWebcam) {
usersModel.getUser(msg.userId) foreach { user => usersModel.getUser(msg.userId) foreach { user =>
val streams = user.webcamStreams - msg.stream val streamName = user.webcamStreams find (w => w == msg.stream) foreach { streamName =>
val uvo = user.copy(hasStream = (!streams.isEmpty), webcamStreams = streams) val streams = user.webcamStreams - streamName
usersModel.addUser(uvo) val uvo = user.copy(hasStream = (!streams.isEmpty), webcamStreams = streams)
log.info("User unshared webcam: mid=[" + mProps.meetingID + "] uid=[" + uvo.userID + "] unsharedStream=[" + msg.stream + "] streams=[" + streams + "]") usersModel.addUser(uvo)
outGW.send(new UserUnsharedWebcam(mProps.meetingID, mProps.recorded, uvo.userID, msg.stream)) log.info("User unshared webcam: mid=[" + mProps.meetingID + "] uid=[" + uvo.userID + "] unsharedStream=[" + msg.stream + "] streams=[" + streams + "]")
outGW.send(new UserUnsharedWebcam(mProps.meetingID, mProps.recorded, uvo.userID, msg.stream))
}
} }
} }
@ -270,7 +274,20 @@ trait UsersApp {
def handleUserJoin(msg: UserJoining): Unit = { def handleUserJoin(msg: UserJoining): Unit = {
val regUser = usersModel.getRegisteredUserWithToken(msg.authToken) val regUser = usersModel.getRegisteredUserWithToken(msg.authToken)
regUser foreach { ru => regUser foreach { ru =>
val vu = new VoiceUser(msg.userID, msg.userID, ru.name, ru.name, false, false, false, false) // if there was a phoneUser with the same userID, reuse the VoiceUser value object
val vu = usersModel.getUser(msg.userID) match {
case Some(u) => {
if (u.voiceUser.joined) {
u.voiceUser.copy()
} else {
new VoiceUser(msg.userID, msg.userID, ru.name, ru.name, false, false, false, false)
}
}
case None => {
new VoiceUser(msg.userID, msg.userID, ru.name, ru.name, false, false, false, false)
}
}
val uvo = new UserVO(msg.userID, ru.externId, ru.name, val uvo = new UserVO(msg.userID, ru.externId, ru.name,
ru.role, raiseHand = false, presenter = false, ru.role, raiseHand = false, presenter = false,
hasStream = false, locked = getInitialLockStatus(ru.role), hasStream = false, locked = getInitialLockStatus(ru.role),
@ -286,7 +303,7 @@ trait UsersApp {
outGW.send(new MeetingState(mProps.meetingID, mProps.recorded, uvo.userID, meetingModel.getPermissions(), meetingModel.isMeetingMuted())) outGW.send(new MeetingState(mProps.meetingID, mProps.recorded, uvo.userID, meetingModel.getPermissions(), meetingModel.isMeetingMuted()))
// Become presenter if the only moderator // Become presenter if the only moderator
if (usersModel.numModerators == 1) { if ((usersModel.numModerators == 1) || (usersModel.noPresenter())) {
if (ru.role == Role.MODERATOR) { if (ru.role == Role.MODERATOR) {
assignNewPresenter(msg.userID, ru.name, msg.userID) assignNewPresenter(msg.userID, ru.name, msg.userID)
} }
@ -309,14 +326,18 @@ trait UsersApp {
/* The current presenter has left the meeting. Find a moderator and make /* The current presenter has left the meeting. Find a moderator and make
* him presenter. This way, if there is a moderator in the meeting, there * him presenter. This way, if there is a moderator in the meeting, there
* will always be a presenter. * will always be a presenter.
*/ */
val moderator = usersModel.findAModerator() val moderator = usersModel.findAModerator()
moderator.foreach { mod => moderator.foreach { mod =>
log.info("Presenter left meeting: mid=[" + mProps.meetingID + "] uid=[" + u.userID + "]. Making user=[" + mod.userID + "] presenter.") log.info("Presenter left meeting: mid=[" + mProps.meetingID + "] uid=[" + u.userID + "]. Making user=[" + mod.userID + "] presenter.")
assignNewPresenter(mod.userID, mod.name, mod.userID) assignNewPresenter(mod.userID, mod.name, mod.userID)
} }
}
// add VoiceUser again to the list as a phone user since we still didn't get the event from FreeSWITCH
val vu = u.voiceUser
if (vu.joined) {
this.context.self ! (new UserJoinedVoiceConfMessage(mProps.voiceBridge, vu.userId, msg.userID, vu.callerName, vu.callerNum, vu.muted, vu.talking));
} }
} }
@ -335,12 +356,16 @@ trait UsersApp {
log.info("Voice user=[" + msg.voiceUserId + "] is already in conf=[" + mProps.voiceBridge + "]. Must be duplicate message.") log.info("Voice user=[" + msg.voiceUserId + "] is already in conf=[" + mProps.voiceBridge + "]. Must be duplicate message.")
} }
case None => { case None => {
// No current web user. This means that the user called in through val webUserId = if (msg.userId != msg.callerIdName) {
// the phone. We need to generate a new user as we are not able msg.userId
// to match with a web user. } else {
val webUserId = usersModel.generateWebUserId // No current web user. This means that the user called in through
// the phone. We need to generate a new user as we are not able
// to match with a web user.
usersModel.generateWebUserId
}
val vu = new VoiceUser(msg.voiceUserId, webUserId, msg.callerIdName, msg.callerIdNum, val vu = new VoiceUser(msg.voiceUserId, webUserId, msg.callerIdName, msg.callerIdNum,
true, false, false, false) true, false, msg.muted, msg.talking)
val sessionId = "PHONE-" + webUserId; val sessionId = "PHONE-" + webUserId;

View File

@ -96,6 +96,10 @@ class UsersModel {
uservos.values find (u => u.role == MODERATOR) uservos.values find (u => u.role == MODERATOR)
} }
def noPresenter(): Boolean = {
!getCurrentPresenter().isDefined
}
def getCurrentPresenter(): Option[UserVO] = { def getCurrentPresenter(): Option[UserVO] = {
uservos.values find (u => u.presenter == true) uservos.values find (u => u.presenter == true)
} }
@ -127,4 +131,17 @@ class UsersModel {
def getViewers(): Array[UserVO] = { def getViewers(): Array[UserVO] = {
uservos.values filter (u => u.role == VIEWER) toArray uservos.values filter (u => u.role == VIEWER) toArray
} }
def getRegisteredUserWithUserID(userID: String): Option[RegisteredUser] = {
regUsers.values find (ru => userID contains ru.id)
}
def removeRegUser(userID: String) {
getRegisteredUserWithUserID(userID) match {
case Some(ru) => {
regUsers -= ru.authToken
}
case None =>
}
}
} }

View File

@ -42,6 +42,8 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
private ConnectionInvokerService connInvokerService; private ConnectionInvokerService connInvokerService;
private MessagePublisher red5InGW; private MessagePublisher red5InGW;
private final UserConnectionMapper userConnections = new UserConnectionMapper();
private final String APP = "BBB"; private final String APP = "BBB";
private final String CONN = "RED5-"; private final String CONN = "RED5-";
@ -166,6 +168,8 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
log.info("User joining bbb-apps: data={}", logStr); log.info("User joining bbb-apps: data={}", logStr);
userConnections.addUserConnection(userId, connId);
return super.roomConnect(connection, params); return super.roomConnect(connection, params);
} }
@ -214,10 +218,15 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
Gson gson = new Gson(); Gson gson = new Gson();
String logStr = gson.toJson(logData); String logStr = gson.toJson(logData);
log.info("User leaving bbb-apps: data={}", logStr); boolean removeUser = userConnections.userDisconnected(userId, connId);
red5InGW.userLeft(bbbSession.getRoom(), getBbbSession().getInternalUserID(), sessionId); if (removeUser) {
log.info("User leaving bbb-apps: data={}", logStr);
red5InGW.userLeft(bbbSession.getRoom(), getBbbSession().getInternalUserID(), sessionId);
} else {
log.info("User not leaving bbb-apps but just disconnected: data={}", logStr);
}
super.roomDisconnect(conn); super.roomDisconnect(conn);
} }

View File

@ -0,0 +1,70 @@
package org.bigbluebutton.red5;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* This class maintains the connections mapping of a user.
* This tracks the connections for a user to manage auto-reconnects.
* @author ralam
*
*/
public class UserConnectionMapper {
private ConcurrentMap<String, UserConnection> users = new ConcurrentHashMap<String, UserConnection>(8, 0.9f, 1);;
/**
* Adds a connection for a user.
* @param userId
* @param connId
*/
public synchronized void addUserConnection(String userId, String connId) {
if (users.containsKey(userId)) {
UserConnection user = users.get(userId);
user.add(connId);
} else {
UserConnection user = new UserConnection();
user.add(connId);
users.put(userId, user);
}
}
/**
* Removed a connection for a user. Returns true if the user doesn't have any
* connection left and thus can be removed.
* @param userId
* @param connId
* @return boolean - no more connections
*/
public synchronized boolean userDisconnected(String userId, String connId) {
if (users.containsKey(userId)) {
UserConnection user = users.get(userId);
user.remove(connId);
if (user.isEmpty()) {
users.remove(userId);
return true;
}
return false;
}
return true;
}
private class UserConnection {
private final Set<String> connections = new HashSet<String>();
public void add(String connId) {
connections.add(connId);
}
public void remove(String connId) {
connections.remove(connId);
}
public boolean isEmpty() {
return connections.isEmpty();
}
}
}

View File

@ -100,6 +100,12 @@ ToolTip {
fontFamily: Arial; fontFamily: Arial;
} }
.pollResondersLabelStyle {
color: #444444;
fontFamily: Arial;
fontSize: 12;
}
Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .cameraDisplaySettingsWindowProfileComboStyle, .cameraDisplaySettingsWindowCameraSelector, .languageSelectorStyle, .testJavaLinkButtonStyle, .recordButtonStyleNormal, .recordButtonStyleStart, .recordButtonStyleStop, .micSettingsWindowHelpButtonStyle { Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .cameraDisplaySettingsWindowProfileComboStyle, .cameraDisplaySettingsWindowCameraSelector, .languageSelectorStyle, .testJavaLinkButtonStyle, .recordButtonStyleNormal, .recordButtonStyleStart, .recordButtonStyleStop, .micSettingsWindowHelpButtonStyle {
textIndent: 0; textIndent: 0;
paddingLeft: 10; paddingLeft: 10;
@ -408,7 +414,7 @@ DataGrid {
icon: Embed('assets/images/webcam-private-chat.png'); icon: Embed('assets/images/webcam-private-chat.png');
} }
.presentationFileUploadWindowStyle { TitleWindow {
borderColor: #b9babc; borderColor: #b9babc;
borderAlpha: 1; borderAlpha: 1;
borderThicknessLeft: 10; borderThicknessLeft: 10;
@ -419,11 +425,8 @@ DataGrid {
cornerRadius: 5; cornerRadius: 5;
headerHeight: 20; headerHeight: 20;
backgroundAlpha: 1; backgroundAlpha: 1;
headerColors: #b9babc, #b9babc;
footerColors: #b9babc, #b9babc;
backgroundColor: #EFEFEF; backgroundColor: #EFEFEF;
dropShadowEnabled: true; dropShadowEnabled: true;
titleStyleName: "presentationFileUploadWindowTitleStyle";
} }
.presentationFileUploadWindowTitleStyle, .presentationUploadTitleStyle { .presentationFileUploadWindowTitleStyle, .presentationUploadTitleStyle {
@ -511,42 +514,6 @@ DataGrid {
imageSource: Embed(source='assets/images/chromePluginBlocked.png'); imageSource: Embed(source='assets/images/chromePluginBlocked.png');
} }
.cameraDisplaySettingsWindowStyle {
borderColor: #b9babc;
borderAlpha: 1;
borderThicknessLeft: 10;
borderThicknessTop: 0;
borderThicknessBottom: 10;
borderThicknessRight: 10;
roundedBottomCorners: true;
cornerRadius: 5;
headerHeight: 20;
backgroundAlpha: 1;
headerColors: #b9babc, #b9babc;
footerColors: #b9babc, #b9babc;
backgroundColor: #EFEFEF;
dropShadowEnabled: true;
titleStyleName: "webcamSettingsWindowTitleStyle";
}
.micSettingsWindowStyle {
borderColor: #b9babc;
borderAlpha: 1;
borderThicknessLeft: 10;
borderThicknessTop: 0;
borderThicknessBottom: 10;
borderThicknessRight: 10;
roundedBottomCorners: true;
cornerRadius: 5;
headerHeight: 20;
backgroundAlpha: 1;
headerColors: #b9babc, #b9babc;
footerColors: #b9babc, #b9babc;
backgroundColor: #EFEFEF;
dropShadowEnabled: true;
titleStyleName: "micSettingsWindowTitleStyle";
}
.webcamSettingsWindowTitleStyle, .micSettingsWindowTitleStyle { .webcamSettingsWindowTitleStyle, .micSettingsWindowTitleStyle {
fontFamily: Arial; fontFamily: Arial;
fontSize: 20; fontSize: 20;
@ -866,24 +833,6 @@ Alert {
fontWeight: bold; fontWeight: bold;
} }
.lockSettingsWindowStyle {
borderColor: #b9babc;
borderAlpha: 1;
borderThicknessLeft: 10;
borderThicknessTop: 0;
borderThicknessBottom: 10;
borderThicknessRight: 10;
roundedBottomCorners: true;
cornerRadius: 5;
headerHeight: 20;
backgroundAlpha: 1;
headerColors: #b9babc, #b9babc;
footerColors: #b9babc, #b9babc;
backgroundColor: #EFEFEF;
dropShadowEnabled: true;
titleStyleName: "micSettingsWindowTitleStyle";
}
.lockSettingsWindowTitleStyle { .lockSettingsWindowTitleStyle {
fontFamily: Arial; fontFamily: Arial;
fontSize: 20; fontSize: 20;
@ -906,6 +855,7 @@ Alert {
successImage: Embed(source='assets/images/status_success.png'); successImage: Embed(source='assets/images/status_success.png');
warningImage: Embed(source='assets/images/status_warning.png'); warningImage: Embed(source='assets/images/status_warning.png');
failImage: Embed(source='assets/images/status_fail.png'); failImage: Embed(source='assets/images/status_fail.png');
refreshImage: Embed(source='assets/images/status_refresh.png');
} }
.warningButtonStyle { .warningButtonStyle {
@ -922,3 +872,8 @@ Alert {
fontSize: 12; fontSize: 12;
paddingTop: 0; paddingTop: 0;
} }
.statusTimeStyle {
fontSize: 10;
paddingTop: 0;
}

View File

@ -35,4 +35,7 @@ purchase a royalty-free license.
I'm unavailable for custom icon design work. But your I'm unavailable for custom icon design work. But your
suggestions are always welcome! suggestions are always welcome!
<mailto:p@yusukekamiyamane.com> <mailto:p@yusukekamiyamane.com>
==================== ====================
Some of the client icons were generated using the following online tool:
http://romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.type=clipart&foreground.space.trim=1&foreground.space.pad=0.15&foreground.clipart=res%2Fclipart%2Ficons%2Fnavigation_refresh.svg&foreColor=4b4b4b%2C0&crop=0&backgroundShape=none&backColor=ffffff%2C100&effects=none

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -235,6 +235,7 @@ bbb.chat.minimizeBtn.accessibilityName = Minimize the Chat Window
bbb.chat.maximizeRestoreBtn.accessibilityName = Maximize the Chat Window bbb.chat.maximizeRestoreBtn.accessibilityName = Maximize the Chat Window
bbb.chat.closeBtn.accessibilityName = Close the Chat Window bbb.chat.closeBtn.accessibilityName = Close the Chat Window
bbb.chat.chatTabs.accessibleNotice = New messages in this tab. bbb.chat.chatTabs.accessibleNotice = New messages in this tab.
bbb.chat.chatMessage.systemMessage = System
bbb.publishVideo.changeCameraBtn.labelText = Change Webcam bbb.publishVideo.changeCameraBtn.labelText = Change Webcam
bbb.publishVideo.changeCameraBtn.toolTip = Open the change webcam dialog box bbb.publishVideo.changeCameraBtn.toolTip = Open the change webcam dialog box
bbb.publishVideo.cmbResolution.tooltip = Select a webcam resolution bbb.publishVideo.cmbResolution.tooltip = Select a webcam resolution
@ -344,6 +345,13 @@ bbb.logout.confirm.title = Confirm Logout
bbb.logout.confirm.message = Are you sure you want to log out? bbb.logout.confirm.message = Are you sure you want to log out?
bbb.logout.confirm.yes = Yes bbb.logout.confirm.yes = Yes
bbb.logout.confirm.no = No bbb.logout.confirm.no = No
bbb.connection.failure=Network failure
bbb.connection.reconnecting=Reconnecting
bbb.connection.reestablished=Connection reestablished
bbb.connection.bigbluebutton=BigBlueButton
bbb.connection.sip=SIP
bbb.connection.video=Video
bbb.connection.deskshare=Deskshare
bbb.notes.title = Notes bbb.notes.title = Notes
bbb.notes.cmpColorPicker.toolTip = Text Color bbb.notes.cmpColorPicker.toolTip = Text Color
bbb.notes.saveBtn = Save bbb.notes.saveBtn = Save
@ -493,10 +501,13 @@ bbb.shortcutkey.chat.chatbox.debug.function = Temporary debug hotkey
bbb.polling.startButton.tooltip = Start a poll bbb.polling.startButton.tooltip = Start a poll
bbb.polling.publishButton.label = Publish bbb.polling.publishButton.label = Publish
bbb.polling.closeButton.label = Close bbb.polling.closeButton.label = Close
bbb.polling.answer.Y = Yes bbb.polling.pollModal.title = Poll
bbb.polling.answer.N = No bbb.polling.respondersLabel.novotes = No Users Responded
bbb.polling.answer.T = True bbb.polling.respondersLabel.text = {0} Users Responded
bbb.polling.answer.F = False bbb.polling.answer.Yes = Yes
bbb.polling.answer.No = No
bbb.polling.answer.True = True
bbb.polling.answer.False = False
bbb.polling.answer.A = A bbb.polling.answer.A = A
bbb.polling.answer.B = B bbb.polling.answer.B = B
bbb.polling.answer.C = C bbb.polling.answer.C = C
@ -504,6 +515,8 @@ bbb.polling.answer.D = D
bbb.polling.answer.E = E bbb.polling.answer.E = E
bbb.polling.answer.F = F bbb.polling.answer.F = F
bbb.polling.answer.G = G bbb.polling.answer.G = G
bbb.polling.results.accessible.header = Poll Results.
bbb.polling.results.accessible.answer = Answer {0} had {1} votes.
bbb.publishVideo.startPublishBtn.labelText = Start Sharing bbb.publishVideo.startPublishBtn.labelText = Start Sharing
bbb.publishVideo.changeCameraBtn.labelText = Change Webcam Settings bbb.publishVideo.changeCameraBtn.labelText = Change Webcam Settings

View File

@ -3,6 +3,9 @@ if (!window.console.log) window.console.log = function () { };
function startApplet(IP, useTLS , roomNumber, fullScreen, useSVC2) function startApplet(IP, useTLS , roomNumber, fullScreen, useSVC2)
{ {
var deskshareElement = document.getElementById("deskshare");
if (deskshareElement == null) {
console.log("Starting deskshare applet."); console.log("Starting deskshare applet.");
var div = document.createElement("div"); var div = document.createElement("div");
div.id = "deskshare"; div.id = "deskshare";
@ -21,15 +24,40 @@ function startApplet(IP, useTLS , roomNumber, fullScreen, useSVC2)
"<param name=\"permissions\" value=\"all-permissions\"/>" + "<param name=\"permissions\" value=\"all-permissions\"/>" +
"</applet>"; "</applet>";
document.body.appendChild(div); document.body.appendChild(div);
} else {
console.log("Deskshare applet element already exists.");
var div = document.getElementById("deskshare");
if (div.parentNode) {
// Just rewrite the applet tag to kick off the applet. We don't remove the applet tag
// when desktop sharing is stopped to prevent Firefox (38.0.5) from asking for user permissions
// again resulting in the page reloading. (ralam june 17, 2015)
// https://code.google.com/p/bigbluebutton/issues/detail?id=1953
div.innerHTML =
"<applet code=\"org.bigbluebutton.deskshare.client.DeskShareApplet.class\"" +
"id=\"DeskShareApplet\" width=\"100\" height=\"10\" archive=\"bbb-deskshare-applet-0.9.0.jar\">" +
"<param name=\"ROOM\" value=\"" + roomNumber + "\"/>" +
"<param name=\"IP\" value=\"" + IP + "\"/>" +
"<param name=\"PORT\" value=\"9123\"/>" +
"<param name=\"SCALE\" value=\"0.8\"/>" +
"<param name=\"FULL_SCREEN\" value=\"" + fullScreen + "\"/>" +
"<param name=\"SVC2\" value=\"" + useSVC2 + "\"/>" +
"<param name=\"JavaVersion\" value=\"1.7.0_51\"/>" +
"<param name=\"permissions\" value=\"all-permissions\"/>" +
"</applet>";
}
}
} }
function removeFrame () { function removeFrame () {
var div = document.getElementById("deskshare"); var div = document.getElementById("deskshare");
if (div.parentNode) { if (div.parentNode) {
// Need to set the innerHTML otherwise the applet will restart in IE. // Need to set the innerHTML otherwise the applet will restart in IE.
// see https://code.google.com/p/bigbluebutton/issues/detail?id=1776 // see https://code.google.com/p/bigbluebutton/issues/detail?id=1776
div.innerHTML = ""; // Do NOT remove the applet tag as it makes Firefox (38.0.5) prompt for
div.parentNode.removeChild(div); // permissions again resulting in the page reloading. (ralam june 17, 2015)
// div.innerHTML = "";
// div.parentNode.removeChild(div);
} }
} }
@ -143,4 +171,4 @@ function checkJavaVersion(minJavaVersion) {
} }
} }
} }

View File

@ -44,6 +44,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.EventBroadcaster; import org.bigbluebutton.core.EventBroadcaster;
import org.bigbluebutton.main.api.ExternalApiCallbacks; import org.bigbluebutton.main.api.ExternalApiCallbacks;
@ -71,13 +72,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
protected function init():void { protected function init():void {
setupTooltips(); setupTooltips();
setupAPI(); setupAPI();
EventBroadcaster.getInstance().addEventListener("configLoadedEvent", configLoadedEventHandler); EventBroadcaster.getInstance().addEventListener("configLoadedEvent", configLoadedEventHandler);
BBB.initConfigManager(); BBB.initConfigManager();
BBB.initVideoProfileManager(); BBB.initVideoProfileManager();
globalModifier = ExternalInterface.call("determineGlobalModifier"); globalModifier = ExternalInterface.call("determineGlobalModifier");
} }
private function configLoadedEventHandler(e:Event):void { private function configLoadedEventHandler(e:Event):void {
LogUtil.initLogging();
LOGGER.debug("***** Config Loaded ****"); LOGGER.debug("***** Config Loaded ****");
mainShell.initOptions(null); mainShell.initOptions(null);
ShortcutOptions.initialize(); ShortcutOptions.initialize();

View File

@ -0,0 +1,199 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.managers
{
import com.asfusion.mate.events.Dispatcher;
import flash.display.DisplayObject;
import flash.events.TimerEvent;
import flash.utils.Dictionary;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.core.FlexGlobals;
import mx.core.IFlexDisplayObject;
import mx.managers.PopUpManager;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ClientStatusEvent;
import org.bigbluebutton.main.events.LogoutEvent;
import org.bigbluebutton.main.model.users.AutoReconnect;
import org.bigbluebutton.main.views.ReconnectionPopup;
import org.bigbluebutton.util.i18n.ResourceUtil;
public class ReconnectionManager
{
private static const LOGGER:ILogger = getClassLogger(AutoReconnect);
public static const BIGBLUEBUTTON_CONNECTION:String = "BIGBLUEBUTTON_CONNECTION";
public static const SIP_CONNECTION:String = "SIP_CONNECTION";
public static const VIDEO_CONNECTION:String = "VIDEO_CONNECTION";
public static const DESKSHARE_CONNECTION:String = "DESKSHARE_CONNECTION";
private var _connections:Dictionary = new Dictionary();
private var _reestablished:ArrayCollection = new ArrayCollection();
private var _reconnectTimer:Timer = new Timer(10000, 1);
private var _reconnectTimeout:Timer = new Timer(5000, 1);
private var _dispatcher:Dispatcher = new Dispatcher();
private var _popup:IFlexDisplayObject = null;
private var _canceled:Boolean = false;
public function ReconnectionManager() {
_reconnectTimer.addEventListener(TimerEvent.TIMER_COMPLETE, reconnect);
_reconnectTimeout.addEventListener(TimerEvent.TIMER_COMPLETE, timeout);
}
private function reconnect(e:TimerEvent = null):void {
if (_connections.hasOwnProperty(BIGBLUEBUTTON_CONNECTION)) {
reconnectHelper(BIGBLUEBUTTON_CONNECTION);
} else {
for (var type:String in _connections) {
reconnectHelper(type);
}
}
if (!_reconnectTimeout.running)
_reconnectTimeout.start();
}
private function timeout(e:TimerEvent = null):void {
LOGGER.debug("timeout");
_dispatcher.dispatchEvent(new BBBEvent(BBBEvent.CANCEL_RECONNECTION_EVENT));
_dispatcher.dispatchEvent(new LogoutEvent(LogoutEvent.USER_LOGGED_OUT));
}
private function reconnectHelper(type:String):void {
var obj:Object = _connections[type];
obj.reconnect = new AutoReconnect();
obj.reconnect.onDisconnect(obj.callback, obj.callbackParameters);
}
public function onDisconnected(type:String, callback:Function, parameters:Array):void {
if (!_canceled) {
LOGGER.warn("onDisconnected, type={0}, parameters={1}" + [type, parameters.toString()]);
var obj:Object = new Object();
obj.callback = callback;
obj.callbackParameters = parameters;
_connections[type] = obj;
if (!_reconnectTimer.running) {
_popup = PopUpManager.createPopUp(FlexGlobals.topLevelApplication as DisplayObject, ReconnectionPopup, true);
PopUpManager.centerPopUp(_popup);
_reconnectTimer.reset();
_reconnectTimer.start();
}
}
}
public function onConnectionAttemptFailed(type:String):void {
LOGGER.warn("onConnectionAttemptFailed, type={0}", [type]);
if (_connections.hasOwnProperty(type)) {
_connections[type].reconnect.onConnectionAttemptFailed();
}
}
private function get connectionDictEmpty():Boolean {
for (var key:Object in _connections) {
return false;
}
return true;
}
private function dispatchReconnectionSucceededEvent(type:String):void {
var map:Object = {
BIGBLUEBUTTON_CONNECTION: BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT,
SIP_CONNECTION: BBBEvent.RECONNECT_SIP_SUCCEEDED_EVENT,
VIDEO_CONNECTION: BBBEvent.RECONNECT_VIDEO_SUCCEEDED_EVENT,
DESKSHARE_CONNECTION: BBBEvent.RECONNECT_DESKSHARE_SUCCEEDED_EVENT
};
if (map.hasOwnProperty(type)) {
LOGGER.debug("dispatchReconnectionSucceededEvent, type={0}", [type]);
_dispatcher.dispatchEvent(new BBBEvent(map[type]));
} else {
LOGGER.debug("dispatchReconnectionSucceededEvent, couldn't find a map value for type {0}", [type]);
}
}
public function onConnectionAttemptSucceeded(type:String):void {
LOGGER.debug("onConnectionAttemptSucceeded, type={0}", [type]);
dispatchReconnectionSucceededEvent(type);
delete _connections[type];
if (type == BIGBLUEBUTTON_CONNECTION) {
reconnect();
}
_reestablished.addItem(type);
if (connectionDictEmpty) {
var msg:String = connectionReestablishedMessage();
_dispatcher.dispatchEvent(new ClientStatusEvent(ClientStatusEvent.SUCCESS_MESSAGE_EVENT,
ResourceUtil.getInstance().getString('bbb.connection.reestablished'),
msg));
_reconnectTimeout.reset();
removePopUp();
}
}
public function onCancelReconnection():void {
_canceled = true;
for (var type:Object in _connections) delete _connections[type];
removePopUp();
}
private function removePopUp():void {
if (_popup != null) {
PopUpManager.removePopUp(_popup);
_popup = null;
}
}
private function connectionReestablishedMessage():String {
var msg:String = "";
for each (var conn:String in _reestablished) {
switch (conn) {
case BIGBLUEBUTTON_CONNECTION:
msg += ResourceUtil.getInstance().getString('bbb.connection.bigbluebutton');
break;
case SIP_CONNECTION:
msg += ResourceUtil.getInstance().getString('bbb.connection.sip');
break;
case VIDEO_CONNECTION:
msg += ResourceUtil.getInstance().getString('bbb.connection.video');
break;
case DESKSHARE_CONNECTION:
msg += ResourceUtil.getInstance().getString('bbb.connection.deskshare');
break;
default:
break;
}
msg += " ";
}
_reestablished.removeAll();
return msg;
}
}
}

View File

@ -66,7 +66,7 @@ package org.bigbluebutton.core.services
// dispatch event // dispatch event
} }
} else { } else {
LOGGER.debug("*** failed to get voice user name=[{0}] **** \n", [vu.name]); LOGGER.debug("*** failed to get user name=[{0}] **** \n", [vu.name]);
} }
} }
@ -115,4 +115,4 @@ package org.bigbluebutton.core.services
} }
} }
class UsersServiceSingletonEnforcer{} class UsersServiceSingletonEnforcer{}

View File

@ -43,6 +43,17 @@ package org.bigbluebutton.main.events {
public static const JOIN_VOICE_FOCUS_HEAD:String = "JOIN_VOICE_FOCUS_HEAD"; public static const JOIN_VOICE_FOCUS_HEAD:String = "JOIN_VOICE_FOCUS_HEAD";
public static const CHANGE_RECORDING_STATUS:String = "CHANGE_RECORDING_STATUS"; public static const CHANGE_RECORDING_STATUS:String = "CHANGE_RECORDING_STATUS";
public static const RECONNECT_DISCONNECTED_EVENT:String = "RECONNECT_ON_DISCONNECTED_EVENT";
public static const RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT:String = "RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT";
public static const RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT:String = "RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT";
public static const RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT:String = "RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT";
public static const RECONNECT_VIDEO_SUCCEEDED_EVENT:String = "RECONNECT_VIDEO_SUCCEEDED_EVENT";
public static const RECONNECT_SIP_SUCCEEDED_EVENT:String = "RECONNECT_SIP_SUCCEEDED_EVENT";
public static const RECONNECT_DESKSHARE_SUCCEEDED_EVENT:String = "RECONNECT_DESKSHARE_SUCCEEDED_EVENT";
public static const CANCEL_RECONNECTION_EVENT:String = "CANCEL_RECONNECTION_EVENT";
public var message:String; public var message:String;
public var payload:Object = new Object(); public var payload:Object = new Object();
@ -51,4 +62,4 @@ package org.bigbluebutton.main.events {
this.message = message; this.message = message;
} }
} }
} }

View File

@ -30,6 +30,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
--> -->
<ObjectBuilder generator="{ModulesProxy}" cache="global" /> <ObjectBuilder generator="{ModulesProxy}" cache="global" />
<ObjectBuilder generator="{ConfigManager}" cache="global" /> <ObjectBuilder generator="{ConfigManager}" cache="global" />
<ObjectBuilder generator="{ReconnectionManager}" cache="global" />
<!-- <!--
Disabling temporarily the stream monitor Disabling temporarily the stream monitor
--> -->
@ -69,6 +70,22 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<MethodInvoker generator="{ModulesProxy}" method="startAllModules" /> <MethodInvoker generator="{ModulesProxy}" method="startAllModules" />
</EventHandlers> </EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_DISCONNECTED_EVENT}">
<MethodInvoker generator="{ReconnectionManager}" method="onDisconnected" arguments="{[event.payload.type, event.payload.callback, event.payload.callbackParameters]}"/>
</EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT}">
<MethodInvoker generator="{ReconnectionManager}" method="onConnectionAttemptFailed" arguments="{event.payload.type}"/>
</EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT}">
<MethodInvoker generator="{ReconnectionManager}" method="onConnectionAttemptSucceeded" arguments="{event.payload.type}"/>
</EventHandlers>
<EventHandlers type="{BBBEvent.CANCEL_RECONNECTION_EVENT}">
<MethodInvoker generator="{ReconnectionManager}" method="onCancelReconnection" />
</EventHandlers>
<mx:Script> <mx:Script>
@ -76,7 +93,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import mx.events.FlexEvent; import mx.events.FlexEvent;
import org.bigbluebutton.core.managers.ConfigManager; import org.bigbluebutton.core.managers.ConfigManager;
import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.core.services.SkinningService; import org.bigbluebutton.core.services.SkinningService;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ConfigEvent; import org.bigbluebutton.main.events.ConfigEvent;
import org.bigbluebutton.main.events.LogoutEvent; import org.bigbluebutton.main.events.LogoutEvent;
import org.bigbluebutton.main.events.ModuleLoadEvent; import org.bigbluebutton.main.events.ModuleLoadEvent;

View File

@ -0,0 +1,58 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.main.model.users
{
import flash.events.TimerEvent;
import flash.utils.Timer;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
public class AutoReconnect
{
private static const LOGGER:ILogger = getClassLogger(AutoReconnect);
private var _backoff:Number = 2000;
private var _reconnectCallback:Function;
private var _reconnectParameters:Array;
public function onDisconnect(callback:Function, parameters:Array):void {
LOGGER.debug("onDisconnect, parameters={0}", [parameters.toString()]);
_reconnectCallback = callback;
_reconnectParameters = parameters;
attemptReconnect(0);
}
public function onConnectionAttemptFailed():void {
LOGGER.warn("onConnectionAttemptFailed");
attemptReconnect(_backoff);
}
private function attemptReconnect(backoff:Number):void {
LOGGER.debug("attemptReconnect backoff={0}", [backoff]);
var retryTimer:Timer = new Timer(backoff, 1);
retryTimer.addEventListener(TimerEvent.TIMER, function():void {
LOGGER.debug("Reconnecting");
_reconnectCallback.apply(null, _reconnectParameters);
});
retryTimer.start();
if (_backoff < 16000) _backoff = Math.max(backoff, 500) *2;
}
}
}

View File

@ -1,405 +1,435 @@
/** /**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* *
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
* *
* This program is free software; you can redistribute it and/or modify it under the * This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software * terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later * Foundation; either version 3.0 of the License, or (at your option) any later
* version. * version.
* *
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License along * You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package org.bigbluebutton.main.model.users package org.bigbluebutton.main.model.users
{ {
import com.asfusion.mate.events.Dispatcher; import com.asfusion.mate.events.Dispatcher;
import flash.events.AsyncErrorEvent; import flash.events.AsyncErrorEvent;
import flash.events.IOErrorEvent; import flash.events.IOErrorEvent;
import flash.events.NetStatusEvent; import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent; import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent; import flash.events.TimerEvent;
import flash.net.NetConnection; import flash.net.NetConnection;
import flash.net.Responder; import flash.net.Responder;
import flash.utils.Timer; import flash.utils.Timer;
import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.as3commons.logging.util.jsonXify; import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.services.BandwidthMonitor; import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.main.api.JSLog; import org.bigbluebutton.core.services.BandwidthMonitor;
import org.bigbluebutton.main.events.InvalidAuthTokenEvent; import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.model.ConferenceParameters; import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent; import org.bigbluebutton.main.events.InvalidAuthTokenEvent;
import org.bigbluebutton.main.model.ConferenceParameters;
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
import org.bigbluebutton.main.model.users.events.UsersConnectionEvent; import org.bigbluebutton.main.model.users.events.UsersConnectionEvent;
public class NetConnectionDelegate public class NetConnectionDelegate
{ {
private static const LOGGER:ILogger = getClassLogger(NetConnectionDelegate); private static const LOGGER:ILogger = getClassLogger(NetConnectionDelegate);
private var _netConnection:NetConnection; private var _netConnection:NetConnection;
private var connectionId:Number; private var connectionId:Number;
private var connected:Boolean = false; private var connected:Boolean = false;
private var _userid:Number = -1; private var _userid:Number = -1;
private var _role:String = "unknown"; private var _role:String = "unknown";
private var _applicationURI:String; private var _applicationURI:String;
private var _conferenceParameters:ConferenceParameters; private var _conferenceParameters:ConferenceParameters;
// These two are just placeholders. We'll get this from the server later and // These two are just placeholders. We'll get this from the server later and
// then pass to other modules. // then pass to other modules.
private var _authToken:String = "AUTHORIZED"; private var _authToken:String = "AUTHORIZED";
private var _room:String; private var _room:String;
private var tried_tunneling:Boolean = false; private var tried_tunneling:Boolean = false;
private var logoutOnUserCommand:Boolean = false; private var logoutOnUserCommand:Boolean = false;
private var backoff:Number = 2000; private var backoff:Number = 2000;
private var dispatcher:Dispatcher; private var dispatcher:Dispatcher;
private var _messageListeners:Array = new Array(); private var _messageListeners:Array = new Array();
private var authenticated: Boolean = false; private var authenticated: Boolean = false;
private var reconnecting:Boolean = false;
public function NetConnectionDelegate():void
{ public function NetConnectionDelegate():void
dispatcher = new Dispatcher(); {
dispatcher = new Dispatcher();
_netConnection = new NetConnection();
_netConnection.proxyType = "best"; _netConnection = new NetConnection();
_netConnection.client = this; _netConnection.proxyType = "best";
_netConnection.addEventListener( NetStatusEvent.NET_STATUS, netStatus ); _netConnection.client = this;
_netConnection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, netASyncError ); _netConnection.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
_netConnection.addEventListener( SecurityErrorEvent.SECURITY_ERROR, netSecurityError ); _netConnection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, netASyncError );
_netConnection.addEventListener( IOErrorEvent.IO_ERROR, netIOError ); _netConnection.addEventListener( SecurityErrorEvent.SECURITY_ERROR, netSecurityError );
} _netConnection.addEventListener( IOErrorEvent.IO_ERROR, netIOError );
}
public function setUri(uri:String):void {
_applicationURI = uri; public function setUri(uri:String):void {
} _applicationURI = uri;
}
public function get connection():NetConnection {
return _netConnection; public function get connection():NetConnection {
} return _netConnection;
}
public function addMessageListener(listener:IMessageListener):void {
_messageListeners.push(listener); public function addMessageListener(listener:IMessageListener):void {
} _messageListeners.push(listener);
}
public function removeMessageListener(listener:IMessageListener):void {
for (var ob:int=0; ob<_messageListeners.length; ob++) { public function removeMessageListener(listener:IMessageListener):void {
if (_messageListeners[ob] == listener) { for (var ob:int=0; ob<_messageListeners.length; ob++) {
_messageListeners.splice (ob,1); if (_messageListeners[ob] == listener) {
break; _messageListeners.splice (ob,1);
} break;
} }
} }
}
private function notifyListeners(messageName:String, message:Object):void {
if (messageName != null && messageName != "") { private function notifyListeners(messageName:String, message:Object):void {
for (var notify:String in _messageListeners) { if (messageName != null && messageName != "") {
_messageListeners[notify].onMessage(messageName, message); for (var notify:String in _messageListeners) {
} _messageListeners[notify].onMessage(messageName, message);
} else { }
LOGGER.debug("Message name is undefined"); } else {
} LOGGER.debug("Message name is undefined");
} }
}
public function onMessageFromServer(messageName:String, msg:Object):void {
LOGGER.debug("Got message from server [{0}] user=[{1}]", [messageName, UsersUtil.getMyUsername()]); public function onMessageFromServer(messageName:String, msg:Object):void {
if (!authenticated && (messageName == "validateAuthTokenReply")) { LOGGER.debug("Got message from server [{0}] user=[{1}]", [messageName, UsersUtil.getMyUsername()]);
handleValidateAuthTokenReply(msg) if (!authenticated && (messageName == "validateAuthTokenReply")) {
} else if (messageName == "validateAuthTokenTimedOut") { handleValidateAuthTokenReply(msg)
handleValidateAuthTokenTimedOut(msg) } else if (messageName == "validateAuthTokenTimedOut") {
} else if (authenticated) { handleValidateAuthTokenTimedOut(msg)
notifyListeners(messageName, msg); } else if (authenticated) {
} else { notifyListeners(messageName, msg);
LOGGER.debug("Ignoring message=[{0}] as our token hasn't been validated yet.", [messageName]); } else {
} LOGGER.debug("Ignoring message=[{0}] as our token hasn't been validated yet.", [messageName]);
} }
}
private function validateToken():void {
var message:Object = new Object(); private function validateToken():void {
message["userId"] = _conferenceParameters.internalUserID; var message:Object = new Object();
message["authToken"] = _conferenceParameters.authToken; message["userId"] = _conferenceParameters.internalUserID;
message["authToken"] = _conferenceParameters.authToken;
sendMessage(
"validateToken",// Remote function name sendMessage(
// result - On successful result "validateToken",// Remote function name
function(result:Object):void { // result - On successful result
LOGGER.debug("validating token for [{0}]", [_conferenceParameters.internalUserID]); function(result:Object):void {
}, LOGGER.debug("validating token for [{0}]", [_conferenceParameters.internalUserID]);
// status - On error occurred },
function(status:Object):void { // status - On error occurred
LOGGER.error("Error occurred:"); function(status:Object):void {
for (var x:Object in status) { LOGGER.error("Error occurred:");
LOGGER.error(x + " : " + status[x]); for (var x:Object in status) {
} LOGGER.error(x + " : " + status[x]);
}, }
message },
); //_netConnection.call message
} ); //_netConnection.call
}
private function handleValidateAuthTokenTimedOut(msg: Object):void {
LOGGER.debug("*** handleValidateAuthTokenTimedOut {0} **** \n", [msg.msg]); private function handleValidateAuthTokenTimedOut(msg: Object):void {
var map:Object = JSON.parse(msg.msg); LOGGER.debug("*** handleValidateAuthTokenTimedOut {0} **** \n", [msg.msg]);
var tokenValid: Boolean = map.valid as Boolean; var map:Object = JSON.parse(msg.msg);
var userId: String = map.userId as String; var tokenValid: Boolean = map.valid as Boolean;
var userId: String = map.userId as String;
var logData:Object = new Object();
logData.user = UsersUtil.getUserData(); var logData:Object = new Object();
JSLog.critical("Validate auth token timed out.", logData); logData.user = UsersUtil.getUserData();
JSLog.critical("Validate auth token timed out.", logData);
if (tokenValid) {
authenticated = true; if (tokenValid) {
LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]); authenticated = true;
} else { LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]);
LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]); } else {
dispatcher.dispatchEvent(new InvalidAuthTokenEvent()); LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]);
} dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
} }
if (reconnecting) {
private function handleValidateAuthTokenReply(msg: Object):void { onReconnect();
LOGGER.debug("*** handleValidateAuthTokenReply {0} **** \n", [msg.msg]); reconnecting = false;
var map:Object = JSON.parse(msg.msg); }
var tokenValid: Boolean = map.valid as Boolean; }
var userId: String = map.userId as String;
private function handleValidateAuthTokenReply(msg: Object):void {
if (tokenValid) { LOGGER.debug("*** handleValidateAuthTokenReply {0} **** \n", [msg.msg]);
authenticated = true; var map:Object = JSON.parse(msg.msg);
LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]); var tokenValid: Boolean = map.valid as Boolean;
} else { var userId: String = map.userId as String;
LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
dispatcher.dispatchEvent(new InvalidAuthTokenEvent()); if (tokenValid) {
} authenticated = true;
} LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
} else {
private function sendConnectionSuccessEvent(userid:String):void{ LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS); dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
e.userid = userid; }
dispatcher.dispatchEvent(e); if (reconnecting) {
onReconnect();
} reconnecting = false;
}
public function sendMessage(service:String, onSuccess:Function, onFailure:Function, message:Object=null):void { }
LOGGER.debug("SENDING [{0}]", [service]);
var responder:Responder = new Responder( private function onReconnect():void {
function(result:Object):void { // On successful result if (authenticated) {
onSuccess("Successfully sent [" + service + "]."); onReconnectSuccess();
}, } else {
function(status:Object):void { // status - On error occurred onReconnectFailed();
var errorReason:String = "Failed to send [" + service + "]:\n"; }
for (var x:Object in status) { }
errorReason += "\t" + x + " : " + status[x];
} private function onReconnectSuccess():void {
} var attemptSucceeded:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT);
); attemptSucceeded.payload.type = ReconnectionManager.BIGBLUEBUTTON_CONNECTION;
dispatcher.dispatchEvent(attemptSucceeded);
if (message == null) { }
_netConnection.call(service, responder);
} else { private function onReconnectFailed():void {
_netConnection.call(service, responder, message); sendUserLoggedOutEvent();
} }
}
private function sendConnectionSuccessEvent(userid:String):void{
/** var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS);
* Connect to the server. e.userid = userid;
* uri: The uri to the conference application. dispatcher.dispatchEvent(e);
* username: Fullname of the participant.
* role: MODERATOR/VIEWER }
* conference: The conference room
* mode: LIVE/PLAYBACK - Live:when used to collaborate, Playback:when being used to playback a recorded conference. public function sendMessage(service:String, onSuccess:Function, onFailure:Function, message:Object=null):void {
* room: Need the room number when playing back a recorded conference. When LIVE, the room is taken from the URI. LOGGER.debug("SENDING [{0}]", [service]);
*/ var responder:Responder = new Responder(
public function connect(params:ConferenceParameters, tunnel:Boolean = false):void { function(result:Object):void { // On successful result
_conferenceParameters = params; onSuccess("Successfully sent [" + service + "].");
},
tried_tunneling = tunnel; function(status:Object):void { // status - On error occurred
var errorReason:String = "Failed to send [" + service + "]:\n";
try { for (var x:Object in status) {
var uri:String = _applicationURI + "/" + _conferenceParameters.room; errorReason += "\t" + x + " : " + status[x];
}
LOGGER.debug("::Connecting to {0} [{1}]", [uri, jsonXify(_conferenceParameters)]); }
_netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role, );
_conferenceParameters.room, _conferenceParameters.voicebridge,
_conferenceParameters.record, _conferenceParameters.externUserID, if (message == null) {
_conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings); _netConnection.call(service, responder);
} catch(e:ArgumentError) { } else {
// Invalid parameters. _netConnection.call(service, responder, message);
switch (e.errorID) { }
case 2004 : }
LOGGER.debug("Error! Invalid server location: {0}", [uri]);
break; /**
default : * Connect to the server.
LOGGER.debug("UNKNOWN Error! Invalid server location: {0}", [uri]); * uri: The uri to the conference application.
break; * username: Fullname of the participant.
} * role: MODERATOR/VIEWER
} * conference: The conference room
} * mode: LIVE/PLAYBACK - Live:when used to collaborate, Playback:when being used to playback a recorded conference.
* room: Need the room number when playing back a recorded conference. When LIVE, the room is taken from the URI.
public function disconnect(logoutOnUserCommand:Boolean):void { */
this.logoutOnUserCommand = logoutOnUserCommand; public function connect(params:ConferenceParameters, tunnel:Boolean = false):void {
_netConnection.close(); _conferenceParameters = params;
}
tried_tunneling = tunnel;
public function forceClose():void { try {
_netConnection.close(); var uri:String = _applicationURI + "/" + _conferenceParameters.room;
}
LOGGER.debug("::Connecting to {0} [{1}]", [uri, jsonXify(_conferenceParameters)]);
protected function netStatus(event:NetStatusEvent):void { _netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role,
handleResult( event ); _conferenceParameters.room, _conferenceParameters.voicebridge,
} _conferenceParameters.record, _conferenceParameters.externUserID,
_conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings);
private var _bwMon:BandwidthMonitor = new BandwidthMonitor(); } catch(e:ArgumentError) {
// Invalid parameters.
private function startMonitoringBandwidth():void { switch (e.errorID) {
LOGGER.info("Start monitoring bandwidth."); case 2004 :
var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/; LOGGER.debug("Error! Invalid server location: {0}", [uri]);
var result:Array = pattern.exec(_applicationURI); break;
_bwMon.serverURL = result.server; default :
_bwMon.serverApplication = "video"; LOGGER.debug("UNKNOWN Error! Invalid server location: {0}", [uri]);
_bwMon.start(); break;
} }
}
private var autoReconnectTimer:Timer = new Timer(1000, 1); }
public function handleResult(event:Object):void { public function disconnect(logoutOnUserCommand:Boolean):void {
var info : Object = event.info; this.logoutOnUserCommand = logoutOnUserCommand;
var statusCode : String = info.code; _netConnection.close();
}
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
public function forceClose():void {
switch (statusCode) { _netConnection.close();
case "NetConnection.Connect.Success": }
LOGGER.debug("Connection to viewers application succeeded.");
JSLog.debug("Successfully connected to BBB App.", logData); protected function netStatus(event:NetStatusEvent):void {
handleResult( event );
validateToken(); }
break; private var _bwMon:BandwidthMonitor = new BandwidthMonitor();
case "NetConnection.Connect.Failed": private function startMonitoringBandwidth():void {
if (tried_tunneling) { LOGGER.info("Start monitoring bandwidth.");
LOGGER.error(":Connection to viewers application failed...even when tunneling"); var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/;
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED); var result:Array = pattern.exec(_applicationURI);
} else { _bwMon.serverURL = result.server;
disconnect(false); _bwMon.serverApplication = "video";
LOGGER.error(":Connection to viewers application failed...try tunneling"); _bwMon.start();
var rtmptRetryTimer:Timer = new Timer(1000, 1); }
rtmptRetryTimer.addEventListener("timer", rtmptRetryTimerHandler);
rtmptRetryTimer.start(); public function handleResult(event:Object):void {
} var info : Object = event.info;
break; var statusCode : String = info.code;
case "NetConnection.Connect.Closed": var logData:Object = new Object();
LOGGER.debug("Connection to viewers application closed"); logData.user = UsersUtil.getUserData();
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED);
switch (statusCode) {
break; case "NetConnection.Connect.Success":
LOGGER.debug("Connection to viewers application succeeded.");
case "NetConnection.Connect.InvalidApp": JSLog.debug("Successfully connected to BBB App.", logData);
LOGGER.debug(":viewers application not found on server");
sendConnectionFailedEvent(ConnectionFailedEvent.INVALID_APP); validateToken();
break;
break;
case "NetConnection.Connect.AppShutDown":
LOGGER.debug(":viewers application has been shutdown"); case "NetConnection.Connect.Failed":
sendConnectionFailedEvent(ConnectionFailedEvent.APP_SHUTDOWN); if (tried_tunneling) {
break; LOGGER.error(":Connection to viewers application failed...even when tunneling");
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED);
case "NetConnection.Connect.Rejected": } else {
LOGGER.debug(":Connection to the server rejected. Uri: {0}. Check if the red5 specified in the uri exists and is running", [_applicationURI]); disconnect(false);
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED); LOGGER.error(":Connection to viewers application failed...try tunneling");
break; var rtmptRetryTimer:Timer = new Timer(1000, 1);
rtmptRetryTimer.addEventListener("timer", rtmptRetryTimerHandler);
case "NetConnection.Connect.NetworkChange": rtmptRetryTimer.start();
JSLog.warn("Detected network change to BBB App", logData); }
LOGGER.debug("Detected network change. User might be on a wireless and temporarily dropped connection. Doing nothing. Just making a note."); break;
break;
case "NetConnection.Connect.Closed":
default : LOGGER.debug("Connection to viewers application closed");
LOGGER.debug(":Default status to the viewers application" ); sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
break; break;
}
} case "NetConnection.Connect.InvalidApp":
LOGGER.debug(":viewers application not found on server");
private function autoReconnectTimerHandler(event:TimerEvent):void { sendConnectionFailedEvent(ConnectionFailedEvent.INVALID_APP);
LOGGER.debug("autoReconnectTimerHandler: {0}", [event]); break;
connect(_conferenceParameters, tried_tunneling);
} case "NetConnection.Connect.AppShutDown":
LOGGER.debug(":viewers application has been shutdown");
private function rtmptRetryTimerHandler(event:TimerEvent):void { sendConnectionFailedEvent(ConnectionFailedEvent.APP_SHUTDOWN);
LOGGER.debug("rtmptRetryTimerHandler: {0}", [event]); break;
connect(_conferenceParameters, true);
} case "NetConnection.Connect.Rejected":
LOGGER.debug(":Connection to the server rejected. Uri: {0}. Check if the red5 specified in the uri exists and is running", [_applicationURI]);
protected function netSecurityError(event: SecurityErrorEvent):void { sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED);
LOGGER.error("Security error - {0}", [event.text]); break;
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
} case "NetConnection.Connect.NetworkChange":
JSLog.warn("Detected network change to BBB App", logData);
protected function netIOError(event: IOErrorEvent):void { LOGGER.debug("Detected network change. User might be on a wireless and temporarily dropped connection. Doing nothing. Just making a note.");
LOGGER.error("Input/output error - {0}", [event.text]); break;
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
} default :
LOGGER.debug(":Default status to the viewers application" );
protected function netASyncError(event: AsyncErrorEvent):void { sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]); break;
}
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]); }
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
} private function rtmptRetryTimerHandler(event:TimerEvent):void {
LOGGER.debug("rtmptRetryTimerHandler: {0}", [event]);
private function sendConnectionFailedEvent(reason:String):void{ connect(_conferenceParameters, true);
var logData:Object = new Object(); }
if (this.logoutOnUserCommand) { protected function netSecurityError(event: SecurityErrorEvent):void {
logData.reason = "User requested."; LOGGER.error("Security error - {0}", [event.text]);
logData.user = UsersUtil.getUserData(); sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
JSLog.debug("User logged out from BBB App.", logData); }
sendUserLoggedOutEvent();
} else { protected function netIOError(event: IOErrorEvent):void {
logData.reason = reason; LOGGER.error("Input/output error - {0}", [event.text]);
logData.user = UsersUtil.getUserData(); sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
JSLog.warn("User disconnected from BBB App.", logData); }
var e:ConnectionFailedEvent = new ConnectionFailedEvent(reason);
dispatcher.dispatchEvent(e); protected function netASyncError(event: AsyncErrorEvent):void {
} LOGGER.debug("Asynchronous code error - {0}", [event.toString()]);
}
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]);
private function sendUserLoggedOutEvent():void{ sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
var e:ConnectionFailedEvent = new ConnectionFailedEvent(ConnectionFailedEvent.USER_LOGGED_OUT); }
dispatcher.dispatchEvent(e);
} private function sendConnectionFailedEvent(reason:String):void{
var logData:Object = new Object();
private function attemptReconnect(backoff:Number):void{
var retryTimer:Timer = new Timer(backoff, 1); if (this.logoutOnUserCommand) {
retryTimer.addEventListener(TimerEvent.TIMER, function():void{ logData.reason = "User requested.";
connect(_conferenceParameters, tried_tunneling); logData.user = UsersUtil.getUserData();
}); JSLog.debug("User logged out from BBB App.", logData);
retryTimer.start(); sendUserLoggedOutEvent();
if (this.backoff < 16000) this.backoff = backoff *2; } else if (reason == ConnectionFailedEvent.CONNECTION_CLOSED) {
} // do not try to reconnect if the connection failed is different than CONNECTION_CLOSED
logData.reason = reason;
public function onBWCheck(... rest):Number { logData.user = UsersUtil.getUserData();
return 0; JSLog.warn("User disconnected from BBB App.", logData);
}
if (reconnecting) {
public function onBWDone(... rest):void { var attemptFailedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT);
var p_bw:Number; attemptFailedEvent.payload.type = ReconnectionManager.BIGBLUEBUTTON_CONNECTION;
if (rest.length > 0) p_bw = rest[0]; dispatcher.dispatchEvent(attemptFailedEvent);
// your application should do something here } else {
// when the bandwidth check is complete reconnecting = true;
LOGGER.debug("bandwidth = {0} Kbps.", [p_bw]); authenticated = false;
}
} var disconnectedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_DISCONNECTED_EVENT);
} disconnectedEvent.payload.type = ReconnectionManager.BIGBLUEBUTTON_CONNECTION;
disconnectedEvent.payload.callback = connect;
disconnectedEvent.payload.callbackParameters = new Array(_conferenceParameters, tried_tunneling);
dispatcher.dispatchEvent(disconnectedEvent);
}
} else {
var e:ConnectionFailedEvent = new ConnectionFailedEvent(reason);
dispatcher.dispatchEvent(e);
}
}
private function sendUserLoggedOutEvent():void{
var e:ConnectionFailedEvent = new ConnectionFailedEvent(ConnectionFailedEvent.USER_LOGGED_OUT);
dispatcher.dispatchEvent(e);
}
public function onBWCheck(... rest):Number {
return 0;
}
public function onBWDone(... rest):void {
var p_bw:Number;
if (rest.length > 0) p_bw = rest[0];
// your application should do something here
// when the bandwidth check is complete
LOGGER.debug("bandwidth = {0} Kbps.", [p_bw]);
}
}
}

View File

@ -20,7 +20,11 @@
break break
} }
titleLbl.text = value.title; titleLbl.text = value.title;
if (value.occurrences > 1) {
titleLbl.text = "(" + value.occurrences + ") " + titleLbl.text;
}
messageTxt.htmlText = value.message; messageTxt.htmlText = value.message;
timeLbl.text = value.time;
validateNow(); validateNow();
} }
@ -31,4 +35,7 @@
<mx:Label id="titleLbl" width="100%" styleName="statusTitleStyle"/> <mx:Label id="titleLbl" width="100%" styleName="statusTitleStyle"/>
<mx:Text id="messageTxt" width="100%" styleName="statusMessageStyle"/> <mx:Text id="messageTxt" width="100%" styleName="statusMessageStyle"/>
</mx:VBox> </mx:VBox>
<mx:VBox height="100%" verticalAlign="bottom">
<mx:Label id="timeLbl" width="100%" styleName="statusTimeStyle"/>
</mx:VBox>
</mx:HBox> </mx:HBox>

View File

@ -72,11 +72,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
//LogUtil.debug("Progress: " + totalProgress); //LogUtil.debug("Progress: " + totalProgress);
this.setProgress(totalProgress/numModules, 100); this.setProgress(totalProgress/numModules, 100);
} }
private function allModulesLoaded(e:ModuleLoadEvent):void{ private function allModulesLoaded(e:ModuleLoadEvent):void{
parent.removeChild(this); if (parent != null) parent.removeChild(this);
} }
private function testRTMP(e:PortTestEvent):void{ private function testRTMP(e:PortTestEvent):void{
//- Cannot get locale string this early in loading process //- Cannot get locale string this early in loading process
portUpdateStr += "." portUpdateStr += "."

View File

@ -23,7 +23,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" <mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"
title="{ResourceUtil.getInstance().getString('bbb.logout.title')}" showCloseButton="false" creationComplete="init()" title="{ResourceUtil.getInstance().getString('bbb.logout.title')}" showCloseButton="false" creationComplete="init()"
verticalScrollPolicy="off" horizontalScrollPolicy="off" verticalScrollPolicy="off" horizontalScrollPolicy="off"
x="168" y="86" layout="vertical" width="400" height="200" horizontalAlign="center"> x="168" y="86" layout="vertical" width="400" height="110" horizontalAlign="center">
<mx:Script> <mx:Script>
<![CDATA[ <![CDATA[
import mx.core.FlexGlobals; import mx.core.FlexGlobals;
@ -52,6 +52,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
request.method = URLRequestMethod.GET; request.method = URLRequestMethod.GET;
urlLoader = new URLLoader(); urlLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, handleComplete); urlLoader.addEventListener(Event.COMPLETE, handleComplete);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, handleRedirectError);
urlLoader.load(request); urlLoader.load(request);
} }
@ -77,6 +78,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
PopUpManager.removePopUp(this); PopUpManager.removePopUp(this);
} }
private function handleRedirectError(e:IOErrorEvent):void {
PopUpManager.removePopUp(this);
}
private function onUserLoggedOutWindowClose(e:Event):void { private function onUserLoggedOutWindowClose(e:Event):void {
PopUpManager.removePopUp(this); PopUpManager.removePopUp(this);
} }
@ -110,17 +115,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
break; break;
} }
} }
private function reconnect():void {
ExternalInterface.call("document.location.reload", true);
}
]]> ]]>
</mx:Script> </mx:Script>
<mx:VBox width="100%" height="100%" horizontalAlign="center"> <mx:VBox width="100%" height="100%" horizontalAlign="center">
<mx:Text text="{message}"/> <mx:Text text="{message}"/>
<mx:Button id="okBtn" label="{ResourceUtil.getInstance().getString('bbb.logout.button.label')}" click="redirect()"/> <mx:Button id="okBtn" label="{ResourceUtil.getInstance().getString('bbb.logout.button.label')}" click="redirect()"/>
<mx:HRule width="100%" />
<mx:Text width="380" textAlign="center" text="{ResourceUtil.getInstance().getString('bbb.logout.refresh.message')}" />
<mx:Button id="reconnectBtn" label="{ResourceUtil.getInstance().getString('bbb.logout.refresh.label')}" click="reconnect()" />
</mx:VBox> </mx:VBox>
</mx:TitleWindow> </mx:TitleWindow>

View File

@ -83,7 +83,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.common.IBbbModuleWindow; import org.bigbluebutton.common.IBbbModuleWindow;
import org.bigbluebutton.common.Images; import org.bigbluebutton.common.Images;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.common.events.AddUIComponentToMainCanvas; import org.bigbluebutton.common.events.AddUIComponentToMainCanvas;
import org.bigbluebutton.common.events.CloseWindowEvent; import org.bigbluebutton.common.events.CloseWindowEvent;
import org.bigbluebutton.common.events.OpenWindowEvent; import org.bigbluebutton.common.events.OpenWindowEvent;
@ -164,7 +163,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
public function initOptions(e:Event):void { public function initOptions(e:Event):void {
LogUtil.initLogging();
UserManager.getInstance().getConference().configLockSettings(); UserManager.getInstance().getConference().configLockSettings();
layoutOptions = new LayoutOptions(); layoutOptions = new LayoutOptions();
layoutOptions.parseOptions(); layoutOptions.parseOptions();
@ -585,6 +583,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function handleInvalidAuthToken(event:InvalidAuthTokenEvent):void { private function handleInvalidAuthToken(event:InvalidAuthTokenEvent):void {
showlogoutWindow(ResourceUtil.getInstance().getString('bbb.mainshell.invalidAuthToken')); showlogoutWindow(ResourceUtil.getInstance().getString('bbb.mainshell.invalidAuthToken'));
globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.CANCEL_RECONNECTION_EVENT));
} }
private function handleRemoveToolbarComponent(event:ToolbarButtonEvent):void { private function handleRemoveToolbarComponent(event:ToolbarButtonEvent):void {

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:mate="http://mate.asfusion.com/"
verticalScrollPolicy="off"
horizontalScrollPolicy="off"
horizontalAlign="center"
width="250"
title="{ResourceUtil.getInstance().getString('bbb.connection.failure')}">
<mx:Script>
<![CDATA[
import org.bigbluebutton.util.i18n.ResourceUtil;
]]>
</mx:Script>
<mx:HBox width="100%" height="100%" verticalAlign="middle">
<mx:Box
paddingBottom="10"
paddingTop="10"
paddingLeft="10"
paddingRight="10"
>
<mx:Image id="typeImg" source="{typeImg.getStyle('refreshImage')}" width="34" height="34" styleName="statusImageStyle" />
</mx:Box>
<mx:Text
selectable="false"
text="{ResourceUtil.getInstance().getString('bbb.connection.reconnecting')}"
width="100%"/>
</mx:HBox>
</mx:Panel>

View File

@ -33,6 +33,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mx:Script> <mx:Script>
<![CDATA[ <![CDATA[
import flash.globalization.DateTimeFormatter;
import flash.globalization.DateTimeStyle;
import flash.globalization.LocaleID;
import mx.controls.ToolTip; import mx.controls.ToolTip;
import mx.core.FlexGlobals; import mx.core.FlexGlobals;
import mx.managers.PopUpManager; import mx.managers.PopUpManager;
@ -58,24 +62,38 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
private function handleSuccessMessageEvent(e:ClientStatusEvent):void { private function handleSuccessMessageEvent(e:ClientStatusEvent):void {
if(isUniqueMessage("success", e.title, e.message)) { handleMessageEvent("success", e);
messages.push({type:"success",title:e.title,message:e.message});
showNotification();
}
} }
private function handleWarningMessageEvent(e:ClientStatusEvent):void { private function handleWarningMessageEvent(e:ClientStatusEvent):void {
if(isUniqueMessage("warning", e.title, e.message)) { handleMessageEvent("warning", e);
messages.push({type:"warning",title:e.title,message:e.message});
showNotification();
}
} }
private function handleFailMessageEvent(e:ClientStatusEvent):void { private function handleFailMessageEvent(e:ClientStatusEvent):void {
if(isUniqueMessage("fail", e.title, e.message)) { handleMessageEvent("fail", e);
messages.push({type:"fail",title:e.title,message:e.message}); }
showNotification();
private function handleMessageEvent(type:String, e:ClientStatusEvent):void {
var index:Number = getMessageIndex(type, e.title, e.message);
var obj:Object;
if (index != -1) {
obj = messages.splice(index, 1)[0];
obj.occurrences++;
} else {
obj = {
type: type,
title: e.title,
message: e.message,
occurrences: 1
};
} }
var dtf:DateTimeFormatter = new DateTimeFormatter(LocaleID.DEFAULT, DateTimeStyle.NONE, DateTimeStyle.MEDIUM);
var time:String = dtf.format(new Date());
obj.time = time;
messages.push(obj);
showNotification();
} }
private function showNotification():void { private function showNotification():void {
@ -92,12 +110,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
} }
private function isUniqueMessage(type:String, title:String, message:String):Boolean { private function getMessageIndex(type:String, title:String, message:String):int {
for (var i:Number=0; i<messages.length; i++) { for (var i:Number=0; i<messages.length; i++) {
if (messages[i].type == type && messages[i].title == title && messages[i].message == message) if (messages[i].type == type && messages[i].title == title && messages[i].message == message)
return false; return i;
} }
return true; return -1;
} }
private function handleButtonClick():void { private function handleButtonClick():void {

View File

@ -116,12 +116,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function noButtonClicked():void { private function noButtonClicked():void {
userClosed = true; userClosed = true;
LOGGER.warn("Echo test failed."); LOGGER.warn("Echo test failed.");
var logData:Object = new Object();
logData.reason = "User requested.";
logData.user = UsersUtil.getUserData();
JSLog.info("WebRtc Echo test failed.", logData);
var dispatcher:Dispatcher = new Dispatcher(); var dispatcher:Dispatcher = new Dispatcher();
dispatcher.dispatchEvent(new WebRTCEchoTestEvent(WebRTCEchoTestEvent.WEBRTC_ECHO_TEST_NO_AUDIO)); dispatcher.dispatchEvent(new WebRTCEchoTestEvent(WebRTCEchoTestEvent.WEBRTC_ECHO_TEST_NO_AUDIO));
onCancelClicked(); onCancelClicked();

View File

@ -28,6 +28,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import mx.events.FlexEvent; import mx.events.FlexEvent;
import org.bigbluebutton.common.events.OpenWindowEvent; import org.bigbluebutton.common.events.OpenWindowEvent;
import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.EventConstants;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ModuleStartedEvent; import org.bigbluebutton.main.events.ModuleStartedEvent;
import org.bigbluebutton.modules.chat.events.ChatEvent; import org.bigbluebutton.modules.chat.events.ChatEvent;
import org.bigbluebutton.modules.chat.events.SendPrivateChatMessageEvent; import org.bigbluebutton.modules.chat.events.SendPrivateChatMessageEvent;
@ -79,6 +80,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<EventHandlers type="{TranscriptEvent.TRANSCRIPT_EVENT}" > <EventHandlers type="{TranscriptEvent.TRANSCRIPT_EVENT}" >
<MethodInvoker generator="{ChatMessageService}" method="sendWelcomeMessage"/> <MethodInvoker generator="{ChatMessageService}" method="sendWelcomeMessage"/>
</EventHandlers> </EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT}" >
<EventAnnouncer generator="{TranscriptEvent}" type="{TranscriptEvent.LOAD_TRANSCRIPT}"/>
</EventHandlers>
<Injectors target="{ChatMessageService}"> <Injectors target="{ChatMessageService}">
<PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/>

View File

@ -35,6 +35,8 @@ package org.bigbluebutton.modules.chat.services
{ {
private static const LOGGER:ILogger = getClassLogger(MessageReceiver); private static const LOGGER:ILogger = getClassLogger(MessageReceiver);
private var welcomed:Boolean = false;
public var dispatcher:IEventDispatcher; public var dispatcher:IEventDispatcher;
@ -67,9 +69,12 @@ package org.bigbluebutton.modules.chat.services
for (var i:int = 0; i < chats.length; i++) { for (var i:int = 0; i < chats.length; i++) {
handleChatReceivePublicMessageCommand(chats[i], true); handleChatReceivePublicMessageCommand(chats[i], true);
} }
var pcEvent:TranscriptEvent = new TranscriptEvent(TranscriptEvent.TRANSCRIPT_EVENT); if (!welcomed) {
dispatcher.dispatchEvent(pcEvent); var pcEvent:TranscriptEvent = new TranscriptEvent(TranscriptEvent.TRANSCRIPT_EVENT);
dispatcher.dispatchEvent(pcEvent);
welcomed = true;
}
} }
private function handleChatReceivePublicMessageCommand(message:Object, history:Boolean = false):void { private function handleChatReceivePublicMessageCommand(message:Object, history:Boolean = false):void {

View File

@ -62,6 +62,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mate:Listener type="{ShortcutEvent.CHANGE_FONT_COLOUR}" method="focusColourPicker" /> <mate:Listener type="{ShortcutEvent.CHANGE_FONT_COLOUR}" method="focusColourPicker" />
<mate:Listener type="{ShortcutEvent.SEND_MESSAGE}" method="remoteSendMessage" /> <mate:Listener type="{ShortcutEvent.SEND_MESSAGE}" method="remoteSendMessage" />
<mate:Listener type="{ShortcutEvent.CHAT_DEBUG}" method="chatDebugInfo" /> <mate:Listener type="{ShortcutEvent.CHAT_DEBUG}" method="chatDebugInfo" />
<mate:Listener type="{BBBEvent.RECONNECT_DISCONNECTED_EVENT}" receive="refreshChat(event)"/>
<mate:Listener type="{LockControlEvent.CHANGED_LOCK_SETTINGS}" method="lockSettingsChanged" /> <mate:Listener type="{LockControlEvent.CHANGED_LOCK_SETTINGS}" method="lockSettingsChanged" />
@ -83,6 +84,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.main.events.ShortcutEvent; import org.bigbluebutton.main.events.ShortcutEvent;
import org.bigbluebutton.main.events.UserJoinedEvent; import org.bigbluebutton.main.events.UserJoinedEvent;
import org.bigbluebutton.main.events.UserLeftEvent; import org.bigbluebutton.main.events.UserLeftEvent;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.model.users.BBBUser; import org.bigbluebutton.main.model.users.BBBUser;
import org.bigbluebutton.main.model.users.Conference; import org.bigbluebutton.main.model.users.Conference;
import org.bigbluebutton.modules.chat.ChatConstants; import org.bigbluebutton.modules.chat.ChatConstants;
@ -232,6 +234,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
scrollToEndOfMessage(); scrollToEndOfMessage();
} }
} }
private function refreshChat(e:BBBEvent):void {
if (e.payload.type == "BIGBLUEBUTTON_CONNECTION") {
if (publicChat) chatMessages = new ChatConversation();
}
}
private function handleUserJoinedEvent(event:UserJoinedEvent):void { private function handleUserJoinedEvent(event:UserJoinedEvent):void {
// Handle user joining so that the user can start to talk if the person rejoins // Handle user joining so that the user can start to talk if the person rejoins
if (!publicChat && event.userID == chatWithUserID) { if (!publicChat && event.userID == chatWithUserID) {

View File

@ -27,12 +27,16 @@ package org.bigbluebutton.modules.classyaudio.managers {
import flash.net.NetConnection; import flash.net.NetConnection;
import flash.net.ObjectEncoding; import flash.net.ObjectEncoding;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.modules.classyaudio.events.CallConnectedEvent; import org.bigbluebutton.modules.classyaudio.events.CallConnectedEvent;
import org.bigbluebutton.modules.classyaudio.events.CallDisconnectedEvent; import org.bigbluebutton.modules.classyaudio.events.CallDisconnectedEvent;
import org.bigbluebutton.modules.classyaudio.events.ConnectionStatusEvent; import org.bigbluebutton.modules.classyaudio.events.ConnectionStatusEvent;
public class ConnectionManager { public class ConnectionManager {
private static const LOGGER:ILogger = getClassLogger(ConnectionManager);
private var netConnection:NetConnection = null; private var netConnection:NetConnection = null;
private var username:String; private var username:String;
private var uri:String; private var uri:String;
@ -82,42 +86,42 @@ package org.bigbluebutton.modules.classyaudio.managers {
switch(evt.info.code) { switch(evt.info.code) {
case "NetConnection.Connect.Success": case "NetConnection.Connect.Success":
LogUtil.debug("Successfully connected to SIP application."); LOGGER.debug("Successfully connected to SIP application.");
event.status = ConnectionStatusEvent.SUCCESS; event.status = ConnectionStatusEvent.SUCCESS;
break; break;
case "NetConnection.Connect.Failed": case "NetConnection.Connect.Failed":
LogUtil.debug("Failed to connect to SIP application."); LOGGER.debug("Failed to connect to SIP application.");
event.status = ConnectionStatusEvent.FAILED; event.status = ConnectionStatusEvent.FAILED;
break; break;
case "NetConnection.Connect.Closed": case "NetConnection.Connect.Closed":
LogUtil.debug("Connection to SIP application has closed."); LOGGER.debug("Connection to SIP application has closed.");
event.status = ConnectionStatusEvent.CLOSED; event.status = ConnectionStatusEvent.CLOSED;
break; break;
case "NetConnection.Connect.Rejected": case "NetConnection.Connect.Rejected":
LogUtil.debug("Connection to SIP application was rejected."); LOGGER.debug("Connection to SIP application was rejected.");
event.status = ConnectionStatusEvent.REJECTED; event.status = ConnectionStatusEvent.REJECTED;
break; break;
default: default:
} }
LogUtil.debug("Phone Module Connection Status: " + event.status); LOGGER.debug("Phone Module Connection Status: {0}", [event.status]);
LogUtil.debug("Dispatching " + event.status); LOGGER.debug("Dispatching " + event.status);
dispatcher.dispatchEvent(event); dispatcher.dispatchEvent(event);
} }
private function asyncErrorHandler(event:AsyncErrorEvent):void { private function asyncErrorHandler(event:AsyncErrorEvent):void {
LogUtil.debug("AsyncErrorEvent: " + event); LOGGER.debug("AsyncErrorEvent: {0}", [event]);
} }
private function securityErrorHandler(event:SecurityErrorEvent):void { private function securityErrorHandler(event:SecurityErrorEvent):void {
LogUtil.debug("securityErrorHandler: " + event); LOGGER.debug("securityErrorHandler: {0}", [event]);
} }
public function call():void { public function call():void {
LogUtil.debug("Calling " + room); LOGGER.debug("Calling {0}", [room]);
doCall(room); doCall(room);
} }
@ -127,21 +131,21 @@ package org.bigbluebutton.modules.classyaudio.managers {
// //
//******************************************************************************************** //********************************************************************************************
public function failedToJoinVoiceConferenceCallback(msg:String):* { public function failedToJoinVoiceConferenceCallback(msg:String):* {
LogUtil.debug("failedToJoinVoiceConferenceCallback " + msg); LOGGER.debug("failedToJoinVoiceConferenceCallback {0}", [msg]);
var event:CallDisconnectedEvent = new CallDisconnectedEvent(); var event:CallDisconnectedEvent = new CallDisconnectedEvent();
dispatcher.dispatchEvent(event); dispatcher.dispatchEvent(event);
isConnected = false; isConnected = false;
} }
public function disconnectedFromJoinVoiceConferenceCallback(msg:String):* { public function disconnectedFromJoinVoiceConferenceCallback(msg:String):* {
LogUtil.debug("disconnectedFromJoinVoiceConferenceCallback " + msg); LOGGER.debug("disconnectedFromJoinVoiceConferenceCallback {0}", [msg]);
var event:CallDisconnectedEvent = new CallDisconnectedEvent(); var event:CallDisconnectedEvent = new CallDisconnectedEvent();
dispatcher.dispatchEvent(event); dispatcher.dispatchEvent(event);
isConnected = false; isConnected = false;
} }
public function successfullyJoinedVoiceConferenceCallback(publishName:String, playName:String, codec:String):* { public function successfullyJoinedVoiceConferenceCallback(publishName:String, playName:String, codec:String):* {
LogUtil.debug("successfullyJoinedVoiceConferenceCallback " + publishName + " : " + playName + " : " + codec); LOGGER.debug("successfullyJoinedVoiceConferenceCallback {0} : {1} : {2}", [publishName, playName, codec]);
isConnected = true; isConnected = true;
var event:CallConnectedEvent = new CallConnectedEvent(); var event:CallConnectedEvent = new CallConnectedEvent();
event.publishStreamName = publishName; event.publishStreamName = publishName;
@ -156,7 +160,7 @@ package org.bigbluebutton.modules.classyaudio.managers {
// //
//******************************************************************************************** //********************************************************************************************
public function doCall(dialStr:String):void { public function doCall(dialStr:String):void {
LogUtil.debug("Calling " + dialStr); LOGGER.debug("Calling ", [dialStr]);
netConnection.call("voiceconf.call", null, "default", username, dialStr); netConnection.call("voiceconf.call", null, "default", username, dialStr);
} }

View File

@ -58,6 +58,7 @@ package org.bigbluebutton.modules.deskshare.managers
public function startSharing(uri:String , useTLS:Boolean , room:String, autoStart:Boolean, autoFullScreen:Boolean):void { public function startSharing(uri:String , useTLS:Boolean , room:String, autoStart:Boolean, autoFullScreen:Boolean):void {
LOGGER.debug("DS:PublishWindowManager::opening desk share window, autostart={0} autoFullScreen={1}", [autoStart, autoFullScreen]); LOGGER.debug("DS:PublishWindowManager::opening desk share window, autostart={0} autoFullScreen={1}", [autoStart, autoFullScreen]);
shareWindow = new DesktopPublishWindow(); shareWindow = new DesktopPublishWindow();
shareWindow.initWindow(service.getConnection(), uri , useTLS , room, autoStart, autoFullScreen); shareWindow.initWindow(service.getConnection(), uri , useTLS , room, autoStart, autoFullScreen);
shareWindow.visible = true; shareWindow.visible = true;

View File

@ -83,7 +83,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</EventHandlers> </EventHandlers>
<EventHandlers type="{ViewWindowEvent.CLOSE}"> <EventHandlers type="{ViewWindowEvent.CLOSE}">
<MethodInvoker generator="{DeskshareManager}" method="handleViewWindowCloseEvent"/> <MethodInvoker generator="{DeskshareManager}" method="handleViewWindowCloseEvent"/>
</EventHandlers> </EventHandlers>
<EventHandlers type="{ModuleEvent.STOP}"> <EventHandlers type="{ModuleEvent.STOP}">

View File

@ -21,6 +21,7 @@ package org.bigbluebutton.modules.deskshare.services.red5
{ {
import com.asfusion.mate.events.Dispatcher; import com.asfusion.mate.events.Dispatcher;
import flash.events.AsyncErrorEvent;
import flash.events.NetStatusEvent; import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent; import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent; import flash.events.TimerEvent;
@ -30,20 +31,23 @@ package org.bigbluebutton.modules.deskshare.services.red5
import flash.net.SharedObject; import flash.net.SharedObject;
import flash.utils.Timer; import flash.utils.Timer;
import mx.utils.ObjectUtil;
import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.modules.deskshare.events.AppletStartedEvent; import org.bigbluebutton.modules.deskshare.events.AppletStartedEvent;
import org.bigbluebutton.modules.deskshare.events.CursorEvent; import org.bigbluebutton.modules.deskshare.events.CursorEvent;
import org.bigbluebutton.modules.deskshare.events.ViewStreamEvent; import org.bigbluebutton.modules.deskshare.events.ViewStreamEvent;
public class Connection { public class Connection {
private static const LOGGER:ILogger = getClassLogger(Connection); private static const LOGGER:ILogger = getClassLogger(Connection);
private var nc:NetConnection; private var nc:NetConnection;
private var uri:String; private var uri:String;
private const connectionTimeout:int = 5000;
private var retryTimer:Timer = null; private var retryTimer:Timer = null;
private var retryCount:int = 0; private var retryCount:int = 0;
private const MAX_RETRIES:int = 5; private const MAX_RETRIES:int = 5;
@ -52,7 +56,10 @@ package org.bigbluebutton.modules.deskshare.services.red5
private var width:Number; private var width:Number;
private var height:Number; private var height:Number;
private var room:String; private var room:String;
private var logoutOnUserCommand:Boolean = false;
private var reconnecting:Boolean = false;
private var wasPresenterBeforeDisconnect:Boolean = false;
private var dispatcher:Dispatcher = new Dispatcher(); private var dispatcher:Dispatcher = new Dispatcher();
public function Connection(room:String) { public function Connection(room:String) {
@ -90,6 +97,8 @@ package org.bigbluebutton.modules.deskshare.services.red5
nc.objectEncoding = ObjectEncoding.AMF0; nc.objectEncoding = ObjectEncoding.AMF0;
nc.client = this; nc.client = this;
nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, debugAsyncErrorHandler);
nc.addEventListener(NetStatusEvent.NET_STATUS, debugNetStatusHandler);
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
@ -110,12 +119,6 @@ package org.bigbluebutton.modules.deskshare.services.red5
} }
nc.connect(getURI(), UsersUtil.getInternalMeetingID()); nc.connect(getURI(), UsersUtil.getInternalMeetingID());
if (!retry) {
retryTimer = new Timer(connectionTimeout, 1);
retryTimer.addEventListener(TimerEvent.TIMER_COMPLETE, connectTimeoutHandler);
retryTimer.start();
}
} }
private function connectTimeoutHandler(e:TimerEvent):void { private function connectTimeoutHandler(e:TimerEvent):void {
@ -180,6 +183,11 @@ package org.bigbluebutton.modules.deskshare.services.red5
switch(event.info.code){ switch(event.info.code){
case "NetConnection.Connect.Failed": case "NetConnection.Connect.Failed":
if (reconnecting) {
var attemptFailedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT);
attemptFailedEvent.payload.type = ReconnectionManager.DESKSHARE_CONNECTION;
dispatcher.dispatchEvent(attemptFailedEvent);
}
ce.status = ConnectionEvent.FAILED; ce.status = ConnectionEvent.FAILED;
dispatcher.dispatchEvent(ce); dispatcher.dispatchEvent(ce);
@ -187,6 +195,17 @@ package org.bigbluebutton.modules.deskshare.services.red5
case "NetConnection.Connect.Success": case "NetConnection.Connect.Success":
ce.status = ConnectionEvent.SUCCESS; ce.status = ConnectionEvent.SUCCESS;
if (reconnecting) {
reconnecting = false;
if (wasPresenterBeforeDisconnect) {
wasPresenterBeforeDisconnect = false;
stopSharingDesktop(room, room)
}
var attemptSucceeded:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT);
attemptSucceeded.payload.type = ReconnectionManager.DESKSHARE_CONNECTION;
dispatcher.dispatchEvent(attemptSucceeded);
}
dispatcher.dispatchEvent(ce); dispatcher.dispatchEvent(ce);
connectionSuccessHandler(); connectionSuccessHandler();
break; break;
@ -199,7 +218,24 @@ package org.bigbluebutton.modules.deskshare.services.red5
case "NetConnection.Connect.Closed": case "NetConnection.Connect.Closed":
LOGGER.debug("Deskshare connection closed."); LOGGER.debug("Deskshare connection closed.");
ce.status = ConnectionEvent.CLOSED; ce.status = ConnectionEvent.CLOSED;
// dispatcher.dispatchEvent(ce); if (UsersUtil.amIPresenter()) {
// Let's keep our presenter status before disconnected. We can't
// tell the other user's to stop desktop sharing as our connection is broken. (ralam july 24, 2015)
wasPresenterBeforeDisconnect = true;
} else {
stopViewing();
}
if (!logoutOnUserCommand) {
reconnecting = true;
var disconnectedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_DISCONNECTED_EVENT);
disconnectedEvent.payload.type = ReconnectionManager.DESKSHARE_CONNECTION;
disconnectedEvent.payload.callback = connect;
disconnectedEvent.payload.callbackParameters = [];
dispatcher.dispatchEvent(disconnectedEvent);
}
break; break;
case "NetConnection.Connect.InvalidApp": case "NetConnection.Connect.InvalidApp":
@ -253,6 +289,7 @@ package org.bigbluebutton.modules.deskshare.services.red5
} }
public function disconnect():void{ public function disconnect():void{
logoutOnUserCommand = true;
if (nc != null) nc.close(); if (nc != null) nc.close();
} }
@ -261,10 +298,20 @@ package org.bigbluebutton.modules.deskshare.services.red5
var deskSOName:String = room + "-deskSO"; var deskSOName:String = room + "-deskSO";
deskSO = SharedObject.getRemote(deskSOName, uri, false); deskSO = SharedObject.getRemote(deskSOName, uri, false);
deskSO.client = this; deskSO.client = this;
deskSO.addEventListener(AsyncErrorEvent.ASYNC_ERROR, debugAsyncErrorHandler);
deskSO.addEventListener(NetStatusEvent.NET_STATUS, debugNetStatusHandler);
deskSO.connect(nc); deskSO.connect(nc);
checkIfStreamIsPublishing(room); checkIfStreamIsPublishing(room);
} }
private function debugNetStatusHandler(e:NetStatusEvent):void {
LOGGER.debug("netStatusHandler target={0} info={1}", [e.target, ObjectUtil.toString(e.info)]);
}
private function debugAsyncErrorHandler(e:AsyncErrorEvent):void {
LOGGER.debug("asyncErrorHandler target={0} info={1}", [e.target, e.text]);
}
public function getConnection():NetConnection{ public function getConnection():NetConnection{
return nc; return nc;
@ -285,10 +332,13 @@ package org.bigbluebutton.modules.deskshare.services.red5
*/ */
public function appletStarted(videoWidth:Number, videoHeight:Number):void{ public function appletStarted(videoWidth:Number, videoHeight:Number):void{
LOGGER.debug("Got applet started"); LOGGER.debug("Got applet started");
var event:AppletStartedEvent = new AppletStartedEvent(); if (nc != null && nc.connected) {
event.videoWidth = videoWidth; var event:AppletStartedEvent = new AppletStartedEvent();
event.videoHeight = videoHeight; event.videoWidth = videoWidth;
dispatcher.dispatchEvent(event); event.videoHeight = videoHeight;
dispatcher.dispatchEvent(event);
}
} }
/** /**

View File

@ -103,6 +103,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[Bindable] private var baseIndex:int; [Bindable] private var baseIndex:int;
[Bindable] private var dsOptions:DeskshareOptions; [Bindable] private var dsOptions:DeskshareOptions;
private var calledStopApplet:Boolean = false;
private function init():void { private function init():void {
dsOptions = new DeskshareOptions(); dsOptions = new DeskshareOptions();
baseIndex = dsOptions.baseTabIndex; baseIndex = dsOptions.baseTabIndex;
@ -246,6 +248,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
private function onAppletStart(event:AppletStartedEvent):void{ private function onAppletStart(event:AppletStartedEvent):void{
if (!connection.connected) return;
LOGGER.debug("DeskShareWindow::onAppletStart"); LOGGER.debug("DeskShareWindow::onAppletStart");
startPreviewStream(connection, room, event.videoWidth, event.videoHeight); startPreviewStream(connection, room, event.videoWidth, event.videoHeight);
var streamEvent:StreamEvent = new StreamEvent(StreamEvent.START); var streamEvent:StreamEvent = new StreamEvent(StreamEvent.START);
@ -343,15 +347,24 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
closeWindow(); closeWindow();
} }
private function closeWindow():void { private function callStopApplet():void {
ExternalInterface.call("stopApplet"); if (!calledStopApplet) {
dispatchEvent(new ShareWindowEvent(ShareWindowEvent.CLOSE)); calledStopApplet = true;
} LOGGER.debug("Calling stopApplet in callStopApplet()");
ExternalInterface.call("stopApplet");
private function restartJava():void { }
ExternalInterface.call("stopApplet"); }
shareScreen(sharingFullScreen); private function closeWindow():void {
} LOGGER.debug("Calling stopApplet in closeWindow()");
callStopApplet();
dispatchEvent(new ShareWindowEvent(ShareWindowEvent.CLOSE));
}
private function restartJava():void {
LOGGER.debug("Calling stopApplet in restartJava()");
callStopApplet();
shareScreen(sharingFullScreen);
}
private function handleDeskshareAppletLaunchedEvent(e:DeskshareAppletLaunchedEvent):void { private function handleDeskshareAppletLaunchedEvent(e:DeskshareAppletLaunchedEvent):void {
if (javaTimer && javaTimer.running) { if (javaTimer && javaTimer.running) {

View File

@ -187,6 +187,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
private function onNetStatus(e:NetStatusEvent):void{ private function onNetStatus(e:NetStatusEvent):void{
LOGGER.debug("onNetStatus info={0}", [e.info.text]);
switch(e.info.code){ switch(e.info.code){
case "NetStream.Play.Start": case "NetStream.Play.Start":
LOGGER.debug("NetStream.Publish.Start for broadcast stream {0}", [stream]); LOGGER.debug("NetStream.Publish.Start for broadcast stream {0}", [stream]);

View File

@ -12,11 +12,13 @@ package org.bigbluebutton.modules.phone.events
public static const NETWORK_CHANGE:String = "flash voice connection status network change event"; public static const NETWORK_CHANGE:String = "flash voice connection status network change event";
public var status:String; public var status:String;
public var reconnecting:Boolean;
public function FlashVoiceConnectionStatusEvent(connStatus:String) public function FlashVoiceConnectionStatusEvent(connStatus:String, isReconnecting:Boolean = false)
{ {
super(CONN_STATUS, true, false); super(CONN_STATUS, true, false);
status = connStatus; status = connStatus;
reconnecting = isReconnecting;
} }
} }
} }

View File

@ -30,7 +30,9 @@ package org.bigbluebutton.modules.phone.managers {
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.main.api.JSLog; import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent; import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent;
import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent; import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent;
import org.bigbluebutton.modules.phone.events.FlashVoiceConnectionStatusEvent; import org.bigbluebutton.modules.phone.events.FlashVoiceConnectionStatusEvent;
@ -49,6 +51,8 @@ package org.bigbluebutton.modules.phone.managers {
private var registered:Boolean = false; private var registered:Boolean = false;
private var closedByUser:Boolean = false; private var closedByUser:Boolean = false;
private var reconnecting:Boolean = false;
private var dispatcher:Dispatcher; private var dispatcher:Dispatcher;
@ -99,11 +103,39 @@ package org.bigbluebutton.modules.phone.managers {
} }
} }
private function handleConnectionSuccess():void {
if (reconnecting) {
var attemptSucceeded:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT);
attemptSucceeded.payload.type = ReconnectionManager.SIP_CONNECTION;
dispatcher.dispatchEvent(attemptSucceeded);
}
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.CONNECTED));
reconnecting = false;
}
private function handleConnectionFailed():void {
if (reconnecting) {
var attemptFailedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT);
attemptFailedEvent.payload.type = ReconnectionManager.SIP_CONNECTION;
dispatcher.dispatchEvent(attemptFailedEvent);
}
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.FAILED, reconnecting));
}
private function handleConnectionClosed():void { private function handleConnectionClosed():void {
if (!closedByUser) { if (!closedByUser) {
reconnecting = true;
var disconnectedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_DISCONNECTED_EVENT);
disconnectedEvent.payload.type = ReconnectionManager.SIP_CONNECTION;
disconnectedEvent.payload.callback = connect;
disconnectedEvent.payload.callbackParameters = [];
dispatcher.dispatchEvent(disconnectedEvent);
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.DISCONNECTED)); dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.DISCONNECTED));
} }
} }
private function netStatus (event:NetStatusEvent ):void { private function netStatus (event:NetStatusEvent ):void {
var info : Object = event.info; var info : Object = event.info;
var statusCode : String = info.code; var statusCode : String = info.code;
@ -115,12 +147,12 @@ package org.bigbluebutton.modules.phone.managers {
case "NetConnection.Connect.Success": case "NetConnection.Connect.Success":
LOGGER.debug("Connection success"); LOGGER.debug("Connection success");
JSLog.debug("Successfully connected to BBB Voice", logData); JSLog.debug("Successfully connected to BBB Voice", logData);
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.CONNECTED)); handleConnectionSuccess();
break; break;
case "NetConnection.Connect.Failed": case "NetConnection.Connect.Failed":
LOGGER.debug("Connection failed"); LOGGER.debug("Connection failed");
JSLog.error("Failed to connect to BBB Voice", logData); JSLog.error("Failed to connect to BBB Voice", logData);
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.FAILED)); handleConnectionFailed();
break; break;
case "NetConnection.Connect.NetworkChange": case "NetConnection.Connect.NetworkChange":
LOGGER.debug("Detected network change. User might be on a wireless and temporarily dropped connection. Doing nothing. Just making a note."); LOGGER.debug("Detected network change. User might be on a wireless and temporarily dropped connection. Doing nothing. Just making a note.");

View File

@ -10,6 +10,7 @@
import org.as3commons.logging.util.jsonXify; import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.main.api.JSLog; import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.ClientStatusEvent;
import org.bigbluebutton.modules.phone.PhoneOptions; import org.bigbluebutton.modules.phone.PhoneOptions;
import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent; import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent;
import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent; import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent;
@ -368,23 +369,49 @@
} }
} }
public function handleFlashVoiceConnected():void {
switch (state) {
case JOIN_VOICE_CONFERENCE:
callIntoVoiceConference();
break;
case DO_ECHO_TEST:
callIntoEchoTest();
break;
case CALL_TO_LISTEN_ONLY_STREAM:
callToListenOnlyStream();
break;
case ON_LISTEN_ONLY_STREAM:
callToListenOnlyStream();
break;
case IN_CONFERENCE:
LOGGER.debug("Reconnected while transmiting mic. Automatic retransmission not implemented.");
state = INITED;
break;
default:
LOGGER.debug("unhandled state: {0}", [state]);
break;
}
}
public function handleFlashVoiceConnectionStatusEvent(event:FlashVoiceConnectionStatusEvent):void { public function handleFlashVoiceConnectionStatusEvent(event:FlashVoiceConnectionStatusEvent):void {
LOGGER.debug("Connection status event. status=[{0}]", [event.status]); LOGGER.debug("Connection status event. status=[{0}]", [event.status]);
if (event.status == FlashVoiceConnectionStatusEvent.CONNECTED) { switch (event.status) {
switch (state) { case FlashVoiceConnectionStatusEvent.CONNECTED:
case JOIN_VOICE_CONFERENCE: handleFlashVoiceConnected();
callIntoVoiceConference(); break;
break;
case DO_ECHO_TEST: case FlashVoiceConnectionStatusEvent.FAILED:
callIntoEchoTest(); case FlashVoiceConnectionStatusEvent.DISCONNECTED:
break; // If reconnection is under way the state should de kept
case CALL_TO_LISTEN_ONLY_STREAM: if(!event.reconnecting) {
callToListenOnlyStream(); state = INITED;
break; }
default: dispatcher.dispatchEvent(new FlashLeftVoiceConferenceEvent());
LOGGER.debug("unhandled state: {0}", [state]); break;
break;
} default:
LOGGER.debug("unhandled state: {0}", [state]);
} }
} }
@ -392,5 +419,12 @@
usingFlash = true; usingFlash = true;
startCall(true); startCall(true);
} }
public function handleFlashLeftVoiceConference():void {
if (isConnected()) {
streamManager.stopStreams();
connectionManager.disconnect(true);
}
}
} }
} }

View File

@ -13,6 +13,7 @@ package org.bigbluebutton.modules.phone.managers
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.main.api.JSAPI; import org.bigbluebutton.main.api.JSAPI;
import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.ClientStatusEvent; import org.bigbluebutton.main.events.ClientStatusEvent;
import org.bigbluebutton.modules.phone.PhoneModel; import org.bigbluebutton.modules.phone.PhoneModel;
import org.bigbluebutton.modules.phone.PhoneOptions; import org.bigbluebutton.modules.phone.PhoneOptions;
@ -188,6 +189,11 @@ package org.bigbluebutton.modules.phone.managers
errorString = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError.unknown", [event.errorCode]); errorString = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError.unknown", [event.errorCode]);
} }
var logData:Object = new Object();
logData.reason = errorString;
logData.user = UsersUtil.getUserData();
JSLog.warn("WebRtc Echo test failed.", logData);
sendWebRTCAlert(ResourceUtil.getInstance().getString("bbb.webrtcWarning.title"), ResourceUtil.getInstance().getString("bbb.webrtcWarning.message", [errorString]), errorString); sendWebRTCAlert(ResourceUtil.getInstance().getString("bbb.webrtcWarning.title"), ResourceUtil.getInstance().getString("bbb.webrtcWarning.message", [errorString]), errorString);
} }

View File

@ -33,6 +33,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent; import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent;
import org.bigbluebutton.modules.phone.events.FlashEchoTestHasAudioEvent; import org.bigbluebutton.modules.phone.events.FlashEchoTestHasAudioEvent;
import org.bigbluebutton.modules.phone.events.FlashEchoTestNoAudioEvent; import org.bigbluebutton.modules.phone.events.FlashEchoTestNoAudioEvent;
import org.bigbluebutton.modules.phone.events.FlashLeftVoiceConferenceEvent;
import org.bigbluebutton.modules.phone.events.FlashStartEchoTestCommand; import org.bigbluebutton.modules.phone.events.FlashStartEchoTestCommand;
import org.bigbluebutton.modules.phone.events.FlashStopEchoTestCommand; import org.bigbluebutton.modules.phone.events.FlashStopEchoTestCommand;
import org.bigbluebutton.modules.phone.events.FlashVoiceConnectionStatusEvent; import org.bigbluebutton.modules.phone.events.FlashVoiceConnectionStatusEvent;
@ -106,7 +107,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<EventHandlers type="{LeaveVoiceConferenceCommand.LEAVE_VOICE_CONF}"> <EventHandlers type="{LeaveVoiceConferenceCommand.LEAVE_VOICE_CONF}">
<MethodInvoker generator="{FlashCallManager}" method="handleLeaveVoiceConferenceCommand" arguments="{event}"/> <MethodInvoker generator="{FlashCallManager}" method="handleLeaveVoiceConferenceCommand" arguments="{event}"/>
</EventHandlers> </EventHandlers>
<EventHandlers type="{FlashLeftVoiceConferenceEvent.LEFT_VOICE_CONFERENCE}">
<MethodInvoker generator="{FlashCallManager}" method="handleFlashLeftVoiceConference"/>
</EventHandlers>
<EventHandlers type="{UseFlashModeCommand.USE_FLASH_MODE}"> <EventHandlers type="{UseFlashModeCommand.USE_FLASH_MODE}">
<MethodInvoker generator="{FlashCallManager}" method="handleUseFlashModeCommand"/> <MethodInvoker generator="{FlashCallManager}" method="handleUseFlashModeCommand"/>
</EventHandlers> </EventHandlers>

View File

@ -1,16 +1,19 @@
package org.bigbluebutton.modules.polling.model package org.bigbluebutton.modules.polling.model
{ {
import mx.collections.ArrayCollection;
public class SimplePollResult public class SimplePollResult
{ {
private var _id:String; private var _id:String;
private var _answers: Array; private var _answers: Array;
private var _numRespondents: int;
public function SimplePollResult(id:String, answers:Array) private var _numResponders: int;
public function SimplePollResult(id:String, answers:Array, numRespondents: int, numResponders: int)
{ {
_id = id; _id = id;
_answers = answers; _answers = answers;
_numRespondents = numRespondents;
_numResponders = numResponders;
} }
public function get id():String { public function get id():String {
@ -22,5 +25,12 @@ package org.bigbluebutton.modules.polling.model
return _answers; return _answers;
} }
public function get numRespondents():int {
return _numRespondents;
}
public function get numResponders():int {
return _numResponders;
}
} }
} }

View File

@ -2,8 +2,13 @@ package org.bigbluebutton.modules.polling.service
{ {
import com.asfusion.mate.events.Dispatcher; import com.asfusion.mate.events.Dispatcher;
import flash.accessibility.Accessibility;
import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.modules.chat.ChatConstants;
import org.bigbluebutton.modules.chat.events.PublicChatMessageEvent;
import org.bigbluebutton.modules.chat.vo.ChatMessageVO;
import org.bigbluebutton.modules.polling.events.PollShowResultEvent; import org.bigbluebutton.modules.polling.events.PollShowResultEvent;
import org.bigbluebutton.modules.polling.events.PollStartedEvent; import org.bigbluebutton.modules.polling.events.PollStartedEvent;
import org.bigbluebutton.modules.polling.events.PollStoppedEvent; import org.bigbluebutton.modules.polling.events.PollStoppedEvent;
@ -13,6 +18,7 @@ package org.bigbluebutton.modules.polling.service
import org.bigbluebutton.modules.polling.model.SimpleAnswerResult; import org.bigbluebutton.modules.polling.model.SimpleAnswerResult;
import org.bigbluebutton.modules.polling.model.SimplePoll; import org.bigbluebutton.modules.polling.model.SimplePoll;
import org.bigbluebutton.modules.polling.model.SimplePollResult; import org.bigbluebutton.modules.polling.model.SimplePollResult;
import org.bigbluebutton.util.i18n.ResourceUtil;
public class PollDataProcessor public class PollDataProcessor
{ {
@ -61,19 +67,43 @@ package org.bigbluebutton.modules.polling.service
var map:Object = JSON.parse(msg.msg); var map:Object = JSON.parse(msg.msg);
if (map.hasOwnProperty("poll")) { if (map.hasOwnProperty("poll")) {
var poll:Object = map.poll; var poll:Object = map.poll;
if (poll.hasOwnProperty("id") && poll.hasOwnProperty("answers")) { if (poll.hasOwnProperty("id") && poll.hasOwnProperty("answers")
&& poll.hasOwnProperty("num_responders") && poll.hasOwnProperty("num_respondents")) {
var pollId:String = poll.id; var pollId:String = poll.id;
var answers:Array = poll.answers as Array; var answers:Array = poll.answers as Array;
var accessibleAnswers:String = ResourceUtil.getInstance().getString("bbb.polling.results.accessible.header") + "<br />";
var ans:Array = new Array(); var ans:Array = new Array();
for (var j:int = 0; j < answers.length; j++) { for (var j:int = 0; j < answers.length; j++) {
var a:Object = answers[j]; var a:Object = answers[j];
ans.push(new SimpleAnswerResult(a.id as Number, a.key, a.num_votes as Number)); ans.push(new SimpleAnswerResult(a.id as Number, a.key, a.num_votes as Number));
accessibleAnswers += ResourceUtil.getInstance().getString("bbb.polling.results.accessible.answer", [ResourceUtil.getInstance().getString("bbb.polling.answer."+a.key), a.num_votes]) + "<br />";
} }
dispatcher.dispatchEvent(new PollShowResultEvent(new SimplePollResult(pollId, ans))); var numRespondents:Number = poll.num_respondents;
var numResponders:Number = poll.num_responders;
dispatcher.dispatchEvent(new PollShowResultEvent(new SimplePollResult(pollId, ans, numRespondents, numResponders)));
if (Accessibility.active) {
var pollResultMessage:ChatMessageVO = new ChatMessageVO();
pollResultMessage.chatType = ChatConstants.PUBLIC_CHAT;
pollResultMessage.fromUserID = ResourceUtil.getInstance().getString("bbb.chat.chatMessage.systemMessage");
pollResultMessage.fromUsername = ResourceUtil.getInstance().getString("bbb.chat.chatMessage.systemMessage");
pollResultMessage.fromColor = "86187";
pollResultMessage.fromTime = new Date().getTime();
pollResultMessage.fromTimezoneOffset = new Date().getTimezoneOffset();
pollResultMessage.toUserID = ResourceUtil.getInstance().getString("bbb.chat.chatMessage.systemMessage");
pollResultMessage.toUsername = ResourceUtil.getInstance().getString("bbb.chat.chatMessage.systemMessage");
pollResultMessage.message = accessibleAnswers;
var pollResultMessageEvent:PublicChatMessageEvent = new PublicChatMessageEvent(PublicChatMessageEvent.PUBLIC_CHAT_MESSAGE_EVENT);
pollResultMessageEvent.message = pollResultMessage;
pollResultMessageEvent.history = false;
dispatcher.dispatchEvent(pollResultMessageEvent);
}
} }
} }
} }
@ -83,7 +113,8 @@ package org.bigbluebutton.modules.polling.service
var map:Object = JSON.parse(msg.msg); var map:Object = JSON.parse(msg.msg);
if (map.hasOwnProperty("poll")) { if (map.hasOwnProperty("poll")) {
var poll:Object = map.poll; var poll:Object = map.poll;
if (poll.hasOwnProperty("id") && poll.hasOwnProperty("answers")) { if (poll.hasOwnProperty("id") && poll.hasOwnProperty("answers")
&& poll.hasOwnProperty("num_responders") && poll.hasOwnProperty("num_respondents")) {
var pollId:String = poll.id; var pollId:String = poll.id;
var answers:Array = poll.answers as Array; var answers:Array = poll.answers as Array;
@ -95,7 +126,10 @@ package org.bigbluebutton.modules.polling.service
ans.push(new SimpleAnswerResult(a.id as Number, a.key, a.num_votes as Number)); ans.push(new SimpleAnswerResult(a.id as Number, a.key, a.num_votes as Number));
} }
dispatcher.dispatchEvent(new PollVotedEvent(new SimplePollResult(pollId, ans))); var numRespondents:Number = poll.num_respondents;
var numResponders:Number = poll.num_responders;
dispatcher.dispatchEvent(new PollVotedEvent(new SimplePollResult(pollId, ans, numRespondents, numResponders)));
} }
} }

View File

@ -233,6 +233,7 @@ package org.bigbluebutton.modules.polling.views
private function findFontSize(textField:TextField, defaultSize:Number):int { private function findFontSize(textField:TextField, defaultSize:Number):int {
var tFormat:TextFormat = new TextFormat(); var tFormat:TextFormat = new TextFormat();
tFormat.size = defaultSize; tFormat.size = defaultSize;
tFormat.font = "arial";
tFormat.align = TextFormatAlign.CENTER; tFormat.align = TextFormatAlign.CENTER;
textField.setTextFormat(tFormat); textField.setTextFormat(tFormat);
var size:Number = defaultSize; var size:Number = defaultSize;

View File

@ -8,6 +8,10 @@ package org.bigbluebutton.modules.polling.views
import mx.containers.HBox; import mx.containers.HBox;
import mx.containers.TitleWindow; import mx.containers.TitleWindow;
import mx.controls.Button; import mx.controls.Button;
import mx.controls.HRule;
import mx.controls.Label;
import mx.controls.TextArea;
import mx.core.ScrollPolicy;
import mx.managers.PopUpManager; import mx.managers.PopUpManager;
import org.bigbluebutton.modules.polling.events.PollVotedEvent; import org.bigbluebutton.modules.polling.events.PollVotedEvent;
@ -21,6 +25,7 @@ package org.bigbluebutton.modules.polling.views
public class PollResultsModal extends TitleWindow { public class PollResultsModal extends TitleWindow {
private var _voteListener:Listener; private var _voteListener:Listener;
private var _respondersLabel:Label;
private var _pollGraphic:PollGraphic; private var _pollGraphic:PollGraphic;
private var _publishBtn:Button; private var _publishBtn:Button;
private var _closeBtn:Button; private var _closeBtn:Button;
@ -28,23 +33,33 @@ package org.bigbluebutton.modules.polling.views
public function PollResultsModal() { public function PollResultsModal() {
super(); super();
styleName = "micSettingsWindowStyle";
width = 300; width = 300;
height = 300; height = 300;
setStyle("verticalGap", 15);
showCloseButton = false; showCloseButton = false;
layout = "vertical"; layout = "vertical";
setStyle("horizontalAlign", "center"); setStyle("horizontalAlign", "center");
setStyle("verticalAlign", "middle"); setStyle("verticalAlign", "middle");
var modalTitle:TextArea = new TextArea();
modalTitle.setStyle("borderSkin", null);
modalTitle.verticalScrollPolicy = ScrollPolicy.OFF;
modalTitle.editable = false;
modalTitle.text = ResourceUtil.getInstance().getString('bbb.polling.pollModal.title');
modalTitle.styleName = "micSettingsWindowTitleStyle";
modalTitle.percentWidth = 100;
modalTitle.height = 25;
addChild(modalTitle);
var topBox:HBox = new HBox(); var hrule:HRule = new HRule();
_publishBtn = new Button(); hrule.percentWidth = 100;
_publishBtn.label = ResourceUtil.getInstance().getString('bbb.polling.publishButton.label'); addChild(hrule);
_publishBtn.addEventListener(MouseEvent.CLICK, handlePublishClick);
topBox.addChild(_publishBtn); _respondersLabel = new Label();
_closeBtn = new Button(); _respondersLabel.styleName = "pollResondersLabelStyle";
_closeBtn.label = ResourceUtil.getInstance().getString('bbb.polling.closeButton.label'); _respondersLabel.text = " ";// ResourceUtil.getInstance().getString('bbb.polling.respondersLabel.novotes');
_closeBtn.addEventListener(MouseEvent.CLICK, handleCloseClick); addChild(_respondersLabel);
topBox.addChild(_closeBtn);
addChild(topBox);
_pollGraphic = new PollGraphic(); _pollGraphic = new PollGraphic();
_pollGraphic.data = null; _pollGraphic.data = null;
@ -52,6 +67,23 @@ package org.bigbluebutton.modules.polling.views
_pollGraphic.minWidth = 130; _pollGraphic.minWidth = 130;
addChild(_pollGraphic); addChild(_pollGraphic);
hrule = new HRule();
hrule.percentWidth = 100;
addChild(hrule);
var botBox:HBox = new HBox();
botBox.setStyle("gap", 10);
_publishBtn = new Button();
_publishBtn.label = ResourceUtil.getInstance().getString('bbb.polling.publishButton.label');
_publishBtn.addEventListener(MouseEvent.CLICK, handlePublishClick);
botBox.addChild(_publishBtn);
_closeBtn = new Button();
_closeBtn.label = ResourceUtil.getInstance().getString('bbb.polling.closeButton.label');
_closeBtn.addEventListener(MouseEvent.CLICK, handleCloseClick);
botBox.addChild(_closeBtn);
addChild(botBox);
_voteListener = new Listener(); _voteListener = new Listener();
_voteListener.type = PollVotedEvent.POLL_VOTED; _voteListener.type = PollVotedEvent.POLL_VOTED;
_voteListener.method = handlePollVotedEvent; _voteListener.method = handlePollVotedEvent;
@ -62,14 +94,14 @@ package org.bigbluebutton.modules.polling.views
var answers:Array = poll.answers; var answers:Array = poll.answers;
for (var j:int = 0; j < answers.length; j++) { for (var j:int = 0; j < answers.length; j++) {
var a:SimpleAnswer = answers[j] as SimpleAnswer; var a:SimpleAnswer = answers[j] as SimpleAnswer;
resultData.push({a:a.key, v:0}); resultData.push({a:ResourceUtil.getInstance().getString('bbb.polling.answer.' + a.key), v:0});
} }
_pollGraphic.data = resultData; _pollGraphic.data = resultData;
_pollGraphic.height = ((23+10)*_pollGraphic.data.length+10); _pollGraphic.height = ((23+10)*_pollGraphic.data.length+10);
_pollGraphic.minHeight = ((16+10)*_pollGraphic.data.length+10); _pollGraphic.minHeight = ((16+10)*_pollGraphic.data.length+10);
height = _pollGraphic.height + 140; height = _pollGraphic.height + 220;
} }
private function handlePollVotedEvent(e:PollVotedEvent):void { private function handlePollVotedEvent(e:PollVotedEvent):void {
@ -77,10 +109,11 @@ package org.bigbluebutton.modules.polling.views
var answers:Array = e.result.answers; var answers:Array = e.result.answers;
for (var j:int = 0; j < answers.length; j++) { for (var j:int = 0; j < answers.length; j++) {
var a:SimpleAnswerResult = answers[j] as SimpleAnswerResult; var a:SimpleAnswerResult = answers[j] as SimpleAnswerResult;
resultData.push({a:a.key, v:a.numVotes}); resultData.push({a:ResourceUtil.getInstance().getString('bbb.polling.answer.' + a.key), v:a.numVotes});
} }
_pollGraphic.data = resultData; _pollGraphic.data = resultData;
_respondersLabel.text = ResourceUtil.getInstance().getString('bbb.polling.respondersLabel.text', [e.result.numResponders + "/" + e.result.numRespondents]);
} }
private function handlePublishClick(e:MouseEvent):void { private function handlePublishClick(e:MouseEvent):void {

View File

@ -0,0 +1,43 @@
package org.bigbluebutton.modules.polling.views {
import com.asfusion.mate.events.Listener;
import mx.controls.Button;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.modules.present.events.PageLoadedEvent;
import org.bigbluebutton.modules.present.model.Page;
import org.bigbluebutton.modules.present.model.PresentationModel;
public class QuickPollButton extends Button {
private static const LOGGER:ILogger = getClassLogger(QuickPollButton);
public function QuickPollButton() {
super();
visible = false;
var listener:Listener = new Listener();
listener.type = PageLoadedEvent.PAGE_LOADED_EVENT;
listener.method = handlePageLoadedEvent;
}
private function handlePageLoadedEvent(e:PageLoadedEvent):void {
var page:Page = PresentationModel.getInstance().getPage(e.pageId);
if (page != null) {
parseSlideText(page.txtData);
}
}
private function parseSlideText(text:String):void {
var regEx:RegExp = new RegExp("\n[^\s]+[\.\)]", "g");
var matchedArray:Array = text.match(regEx);
LOGGER.debug("Parse Result: {0} {1}", [matchedArray.length, matchedArray.join(" ")]);
if (matchedArray.length > 2) {
label = "A-" + (matchedArray.length < 8 ? matchedArray.length : 7);
visible = true;
} else {
visible = false;
}
}
}
}

View File

@ -24,6 +24,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<![CDATA[ <![CDATA[
import mx.events.FlexEvent; import mx.events.FlexEvent;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.model.users.events.RoleChangeEvent; import org.bigbluebutton.main.model.users.events.RoleChangeEvent;
import org.bigbluebutton.modules.present.api.PresentationAPI; import org.bigbluebutton.modules.present.api.PresentationAPI;
import org.bigbluebutton.modules.present.business.PresentProxy; import org.bigbluebutton.modules.present.business.PresentProxy;
@ -71,6 +72,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<MethodInvoker generator="{PresentManager}" method="handleStopModuleEvent" /> <MethodInvoker generator="{PresentManager}" method="handleStopModuleEvent" />
</EventHandlers> </EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT}" >
<MethodInvoker generator="{PresentProxy}" method="getCurrentPresentationInfo" />
</EventHandlers>
<EventHandlers type="{UploadEvent.OPEN_UPLOAD_WINDOW}" > <EventHandlers type="{UploadEvent.OPEN_UPLOAD_WINDOW}" >
<MethodInvoker generator="{PresentManager}" method="handleOpenUploadWindow" arguments="{event}" /> <MethodInvoker generator="{PresentManager}" method="handleOpenUploadWindow" arguments="{event}" />
</EventHandlers> </EventHandlers>

View File

@ -11,7 +11,6 @@ package org.bigbluebutton.modules.present.model
private static var instance:PresentationModel = null; private static var instance:PresentationModel = null;
private var _pages:ArrayCollection = new ArrayCollection();
private var _presentations:ArrayCollection = new ArrayCollection(); private var _presentations:ArrayCollection = new ArrayCollection();
private var _presenter: Presenter; private var _presenter: Presenter;
@ -48,10 +47,6 @@ package org.bigbluebutton.modules.present.model
return _presenter; return _presenter;
} }
public function addPage(page: Page):void {
_pages.addItem(page);
}
public function addPresentation(p: Presentation):void { public function addPresentation(p: Presentation):void {
_presentations.addItem(p); _presentations.addItem(p);
} }
@ -68,6 +63,10 @@ package org.bigbluebutton.modules.present.model
return null; return null;
} }
public function removeAllPresentations():void {
_presentations.removeAll();
}
public function replacePresentation(p: Presentation):void { public function replacePresentation(p: Presentation):void {
var oldPres:Presentation = removePresentation(p.id); var oldPres:Presentation = removePresentation(p.id);
if (oldPres == null) { if (oldPres == null) {

View File

@ -150,6 +150,10 @@ package org.bigbluebutton.modules.present.services
LOGGER.debug("Switching presentation but presentation [{0}] is not current [{0}]", [presVO.id, presVO.isCurrent()]); LOGGER.debug("Switching presentation but presentation [{0}] is not current [{0}]", [presVO.id, presVO.isCurrent()]);
} }
} }
public function removeAllPresentations():void {
model.removeAllPresentations();
}
public function removePresentation(presentationID:String):void { public function removePresentation(presentationID:String):void {
var removedEvent:RemovePresentationEvent = new RemovePresentationEvent(RemovePresentationEvent.PRESENTATION_REMOVED_EVENT); var removedEvent:RemovePresentationEvent = new RemovePresentationEvent(RemovePresentationEvent.PRESENTATION_REMOVED_EVENT);

View File

@ -294,6 +294,7 @@ package org.bigbluebutton.modules.present.services.messaging
presos.addItem(presVO); presos.addItem(presVO);
} }
service.removeAllPresentations();
service.addPresentations(presos); service.addPresentations(presos);
} }

View File

@ -39,7 +39,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
width="{DEFAULT_WINDOW_WIDTH}" height="{DEFAULT_WINDOW_HEIGHT}" width="{DEFAULT_WINDOW_WIDTH}" height="{DEFAULT_WINDOW_HEIGHT}"
x="{DEFAULT_X_POSITION}" y="{DEFAULT_Y_POSITION}" x="{DEFAULT_X_POSITION}" y="{DEFAULT_Y_POSITION}"
title="{ResourceUtil.getInstance().getString('bbb.presentation.titleWithPres',[currentPresentation])}" title="{ResourceUtil.getInstance().getString('bbb.presentation.titleWithPres',[currentPresentation])}"
xmlns:views="org.bigbluebutton.modules.present.ui.views.*" xmlns:views="org.bigbluebutton.modules.present.ui.views.*"
xmlns:poll="org.bigbluebutton.modules.polling.views.*"
> >
<mate:Dispatcher id="globalDispatcher" /> <mate:Dispatcher id="globalDispatcher" />
@ -616,6 +617,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
} }
private function quickPollClicked(e:MouseEvent):void {
sendStartPollEvent(Button(e.target).label);
}
private function onPollStartButtonClicked():void { private function onPollStartButtonClicked():void {
openPollTypeMenu(); openPollTypeMenu();
} }
@ -640,39 +645,45 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
private function sendStartPollEvent(pollType:String):void {
// Let's reset the page to display full size so we can display the result
// on the bottom right-corner.
onResetZoom();
var dispatcher:Dispatcher = new Dispatcher();
dispatchEvent(new StartPollEvent(pollType));
}
private function menuClickHandler(e:MenuEvent):void { private function menuClickHandler(e:MenuEvent):void {
if(pollMenuData[e.index] != undefined) { if(pollMenuData[e.index] != undefined) {
// start the requested poll // start the requested poll
var pollEvent:StartPollEvent = null; var pollEvent:StartPollEvent = null;
switch (e.index) { switch (e.index) {
case 0: case 0:
pollEvent = new StartPollEvent("YN"); sendStartPollEvent("YN");
break; break;
case 1: case 1:
pollEvent = new StartPollEvent("TF"); sendStartPollEvent("TF");
break; break;
case 2: case 2:
pollEvent = new StartPollEvent("A-2"); sendStartPollEvent("A-2");
break; break;
case 3: case 3:
pollEvent = new StartPollEvent("A-3"); sendStartPollEvent("A-3");
break; break;
case 4: case 4:
pollEvent = new StartPollEvent("A-4"); sendStartPollEvent("A-4");
break; break;
case 5: case 5:
pollEvent = new StartPollEvent("A-5"); sendStartPollEvent("A-5");
break; break;
case 6: case 6:
pollEvent = new StartPollEvent("A-6"); sendStartPollEvent("A-6");
break; break;
case 7: case 7:
pollEvent = new StartPollEvent("A-7"); sendStartPollEvent("A-7");
break; break;
} }
var dispatcher:Dispatcher = new Dispatcher();
//dispatcher.
dispatchEvent(pollEvent);
} }
} }
@ -759,30 +770,32 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mx:Button id="pollStartBtn" visible="false" height="30" styleName="pollStartButtonStyle" <mx:Button id="pollStartBtn" visible="false" height="30" styleName="pollStartButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.polling.startButton.tooltip')}" toolTip="{ResourceUtil.getInstance().getString('bbb.polling.startButton.tooltip')}"
click="onPollStartButtonClicked()" tabIndex="{baseIndex+6}"/> click="onPollStartButtonClicked()" tabIndex="{baseIndex+6}"/>
<poll:QuickPollButton id="quickPollBtn" height="30" tabIndex="{baseIndex+7}"
click="quickPollClicked(event)" />
<mx:Spacer width="100%" id="spacer1"/> <mx:Spacer width="100%" id="spacer1"/>
<mx:Button id="backButton" visible="false" width="45" height="30" styleName="presentationBackButtonStyle" <mx:Button id="backButton" visible="false" width="45" height="30" styleName="presentationBackButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.backBtn.toolTip')}" click="gotoPreviousSlide()" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.backBtn.toolTip')}" click="gotoPreviousSlide()"
tabIndex="{baseIndex+7}"/> tabIndex="{baseIndex+8}"/>
<mx:Button id="btnSlideNum" visible="false" label="" click="showThumbnails()" <mx:Button id="btnSlideNum" visible="false" label="" click="showThumbnails()"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.btnSlideNum.toolTip')}" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.btnSlideNum.toolTip')}"
tabIndex="{baseIndex+8}"/> tabIndex="{baseIndex+9}"/>
<mx:Button id="forwardButton" visible="false" width="45" height="30" styleName="presentationForwardButtonStyle" <mx:Button id="forwardButton" visible="false" width="45" height="30" styleName="presentationForwardButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.forwardBtn.toolTip')}" click="gotoNextSlide()" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.forwardBtn.toolTip')}" click="gotoNextSlide()"
tabIndex="{baseIndex+9}"/> tabIndex="{baseIndex+10}"/>
<mx:Spacer width="50%" id="spacer3"/> <mx:Spacer width="50%" id="spacer3"/>
<mx:HSlider id="zoomSlider" visible="false" value="{slideView.zoomPercentage}" styleName="presentationZoomSliderStyle" <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" labels="['100%','400%']"
useHandCursor="true" snapInterval="5" allowTrackClick="true" liveDragging="true" useHandCursor="true" snapInterval="5" allowTrackClick="true" liveDragging="true"
dataTipFormatFunction="removeDecimalFromDataTip" change="onSliderZoom()" width="100" dataTipFormatFunction="removeDecimalFromDataTip" change="onSliderZoom()" width="100"
tabIndex="{baseIndex+10}" accessibilityName="{ResourceUtil.getInstance().getString('bbb.presentation.slider')}"/> tabIndex="{baseIndex+11}" accessibilityName="{ResourceUtil.getInstance().getString('bbb.presentation.slider')}"/>
<mx:Button id="btnFitToWidth" visible="false" width="30" height="30" styleName="presentationFitToWidthButtonStyle" <mx:Button id="btnFitToWidth" visible="false" width="30" height="30" styleName="presentationFitToWidthButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToWidth.toolTip')}" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToWidth.toolTip')}"
click="onFitToPage(false)" click="onFitToPage(false)"
tabIndex="{baseIndex+11}"/> tabIndex="{baseIndex+12}"/>
<mx:Button id="btnFitToPage" visible="false" width="30" height="30" styleName="presentationFitToPageButtonStyle" <mx:Button id="btnFitToPage" visible="false" width="30" height="30" styleName="presentationFitToPageButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToPage.toolTip')}" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToPage.toolTip')}"
click="onFitToPage(true)" click="onFitToPage(true)"
tabIndex="{baseIndex+12}"/> tabIndex="{baseIndex+13}"/>
<!-- This spacer is to prevent the whiteboard toolbar from overlapping the fit-ot-page button --> <!-- This spacer is to prevent the whiteboard toolbar from overlapping the fit-ot-page button -->
<mx:Spacer width="30" height="30" id="spacer4"/> <mx:Spacer width="30" height="30" id="spacer4"/>
</mx:HBox> </mx:HBox>

View File

@ -179,6 +179,14 @@ package org.bigbluebutton.modules.users.services
var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS); var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS);
e.userid = userid; e.userid = userid;
dispatcher.dispatchEvent(e); dispatcher.dispatchEvent(e);
// If the user was the presenter he's reconnecting and must become viewer
if (UserManager.getInstance().getConference().amIPresenter) {
sendSwitchedPresenterEvent(false, UsersUtil.getPresenterUserID());
UserManager.getInstance().getConference().amIPresenter = false;
var viewerEvent:MadePresenterEvent = new MadePresenterEvent(MadePresenterEvent.SWITCH_TO_VIEWER_MODE);
dispatcher.dispatchEvent(viewerEvent);
}
} }
private function handleMeetingMuted(msg:Object):void { private function handleMeetingMuted(msg:Object):void {
@ -346,6 +354,7 @@ package org.bigbluebutton.modules.users.services
if (UsersUtil.hasUser(internUserID)) { if (UsersUtil.hasUser(internUserID)) {
var bu:BBBUser = UsersUtil.getUser(internUserID); var bu:BBBUser = UsersUtil.getUser(internUserID);
bu.talking = voiceUser.talking;
bu.voiceMuted = voiceUser.muted; bu.voiceMuted = voiceUser.muted;
bu.voiceJoined = true; bu.voiceJoined = true;
@ -409,6 +418,9 @@ package org.bigbluebutton.modules.users.services
LOGGER.debug("*** handleGetUsersReply {0} **** \n", [msg.msg]); LOGGER.debug("*** handleGetUsersReply {0} **** \n", [msg.msg]);
var map:Object = JSON.parse(msg.msg); var map:Object = JSON.parse(msg.msg);
var users:Object = map.users as Array; var users:Object = map.users as Array;
// since might be a reconnection, clean up users list
UserManager.getInstance().getConference().removeAllParticipants();
if (map.count > 0) { if (map.count > 0) {
LOGGER.debug("number of users = [{0}]", [users.length]); LOGGER.debug("number of users = [{0}]", [users.length]);
@ -555,6 +567,10 @@ package org.bigbluebutton.modules.users.services
} }
} }
if (joinedUser.voiceUser.joined) {
userJoinedVoice(joinedUser);
}
UserManager.getInstance().getConference().presenterStatusChanged(user.userID, joinedUser.presenter); UserManager.getInstance().getConference().presenterStatusChanged(user.userID, joinedUser.presenter);
UserManager.getInstance().getConference().raiseHand(user.userID, joinedUser.raiseHand); UserManager.getInstance().getConference().raiseHand(user.userID, joinedUser.raiseHand);
@ -582,4 +598,4 @@ package org.bigbluebutton.modules.users.services
} }
} }
} }
} }

View File

@ -36,9 +36,13 @@ package org.bigbluebutton.modules.videoconf.business
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; import org.bigbluebutton.modules.videoconf.events.ConnectedEvent;
import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent;
import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent;
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
import org.bigbluebutton.main.api.JSLog;
public class VideoProxy public class VideoProxy
@ -48,15 +52,12 @@ package org.bigbluebutton.modules.videoconf.business
public var videoOptions:VideoConfOptions; public var videoOptions:VideoConfOptions;
private var nc:NetConnection; private var nc:NetConnection;
private var ns:NetStream;
private var _url:String; private var _url:String;
private var camerasPublishing:Object = new Object(); private var camerasPublishing:Object = new Object();
private var connected:Boolean = false; private var reconnect:Boolean = false;
private var reconnect:Boolean = false;
private var reconnecting:Boolean = false; private var reconnecting:Boolean = false;
private var dispatcher:Dispatcher = new Dispatcher();
private var autoReconnectTimer:Timer = new Timer(1000, 1);
private function parseOptions():void { private function parseOptions():void {
videoOptions = new VideoConfOptions(); videoOptions = new VideoConfOptions();
videoOptions.parseOptions(); videoOptions.parseOptions();
@ -91,32 +92,70 @@ package org.bigbluebutton.modules.videoconf.business
LOGGER.debug("VIDEO WEBCAM onIOError"); LOGGER.debug("VIDEO WEBCAM onIOError");
} }
private function onConnectedToVideoApp():void{ private function onConnectedToVideoApp():void{
var dispatcher:Dispatcher = new Dispatcher(); dispatcher.dispatchEvent(new ConnectedEvent(reconnecting));
dispatcher.dispatchEvent(new ConnectedEvent(reconnecting)); if (reconnecting) {
reconnecting = false; reconnecting = false;
}
var attemptSucceeded:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT);
attemptSucceeded.payload.type = ReconnectionManager.VIDEO_CONNECTION;
dispatcher.dispatchEvent(attemptSucceeded);
}
}
private function onNetStatus(event:NetStatusEvent):void{ private function onNetStatus(event:NetStatusEvent):void{
LOGGER.debug("[{0}] for [{1}]", [event.info.code, _url]); LOGGER.debug("[{0}] for [{1}]", [event.info.code, _url]);
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
switch(event.info.code){ switch(event.info.code){
case "NetConnection.Connect.Success": case "NetConnection.Connect.Success":
connected = true;
//ns = new NetStream(nc);
onConnectedToVideoApp(); onConnectedToVideoApp();
break; break;
case "NetStream.Play.Failed": case "NetStream.Play.Failed":
disconnect(); if (reconnect) {
JSLog.warn("NetStream.Play.Failed from bbb-video", logData);
}
break; break;
case "NetStream.Play.Stop": case "NetStream.Play.Stop":
disconnect(); if (reconnect) {
JSLog.warn("NetStream.Play.Stop from bbb-video", logData);
}
break; break;
case "NetConnection.Connect.Closed": case "NetConnection.Connect.Closed":
dispatcher.dispatchEvent(new StopBroadcastEvent());
if (reconnect) {
reconnecting = true;
var disconnectedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_DISCONNECTED_EVENT);
disconnectedEvent.payload.type = ReconnectionManager.VIDEO_CONNECTION;
disconnectedEvent.payload.callback = connect;
disconnectedEvent.payload.callbackParameters = [];
dispatcher.dispatchEvent(disconnectedEvent);
}
break;
case "NetConnection.Connect.Failed":
if (reconnecting) {
var attemptFailedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT);
attemptFailedEvent.payload.type = ReconnectionManager.VIDEO_CONNECTION;
dispatcher.dispatchEvent(attemptFailedEvent);
}
if (reconnect) {
JSLog.warn("Disconnected from bbb-video", logData);
}
disconnect(); disconnect();
break; break;
case "NetConnection.Connect.NetworkChange":
JSLog.warn("Detected network change on bbb-video", logData);
break;
default: default:
LOGGER.debug("[{0}] for [{1}]", [event.info.code, _url]); LOGGER.debug("[{0}] for [{1}]", [event.info.code, _url]);
connected = false;
break; break;
} }
} }
@ -129,17 +168,14 @@ package org.bigbluebutton.modules.videoconf.business
} }
public function startPublishing(e:StartBroadcastEvent):void{ public function startPublishing(e:StartBroadcastEvent):void{
ns = new NetStream(nc); var ns:NetStream = new NetStream(nc);
ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus ); ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus );
ns.addEventListener( IOErrorEvent.IO_ERROR, onIOError ); ns.addEventListener( IOErrorEvent.IO_ERROR, onIOError );
ns.addEventListener( AsyncErrorEvent.ASYNC_ERROR, onAsyncError ); ns.addEventListener( AsyncErrorEvent.ASYNC_ERROR, onAsyncError );
ns.client = this; ns.client = this;
ns.attachCamera(e.camera); ns.attachCamera(e.camera);
// Uncomment if you want to build support for H264. But you need at least FP 11. (ralam july 23, 2011)
// if (Capabilities.version.search("11,0") != -1) {
if ((BBB.getFlashPlayerVersion() >= 11) && e.videoProfile.enableH264) { if ((BBB.getFlashPlayerVersion() >= 11) && e.videoProfile.enableH264) {
// if (BBB.getFlashPlayerVersion() >= 11) {
LOGGER.info("Using H264 codec for video.");
var h264:H264VideoStreamSettings = new H264VideoStreamSettings(); var h264:H264VideoStreamSettings = new H264VideoStreamSettings();
var h264profile:String = H264Profile.MAIN; var h264profile:String = H264Profile.MAIN;
if (e.videoProfile.h264Profile != "main") { if (e.videoProfile.h264Profile != "main") {
@ -165,7 +201,6 @@ package org.bigbluebutton.modules.videoconf.business
case "5.1": h264Level = H264Level.LEVEL_5_1; break; case "5.1": h264Level = H264Level.LEVEL_5_1; break;
} }
LOGGER.info("Codec used: {0}", [h264Level]);
h264.setProfileLevel(h264profile, h264Level); h264.setProfileLevel(h264profile, h264Level);
ns.videoStreamSettings = h264; ns.videoStreamSettings = h264;
@ -200,18 +235,6 @@ package org.bigbluebutton.modules.videoconf.business
LOGGER.debug("VideoProxy:: disconnecting from Video application"); LOGGER.debug("VideoProxy:: disconnecting from Video application");
stopAllBroadcasting(); stopAllBroadcasting();
if (nc != null) nc.close(); if (nc != null) nc.close();
if (reconnect) {
var reconnectTimer:Timer = new Timer(1000, 1);
reconnectTimer.addEventListener("timer", reconnectTimerHandler);
reconnectTimer.start();
}
}
private function reconnectTimerHandler(event:TimerEvent):void {
LOGGER.debug("rtmptRetryTimerHandler: {0}", [event]);
reconnecting = true;
connect();
} }
public function onBWCheck(... rest):Number { public function onBWCheck(... rest):Number {

View File

@ -23,7 +23,7 @@ package org.bigbluebutton.modules.videoconf.events
public class ConnectedEvent extends Event public class ConnectedEvent extends Event
{ {
public static const VIDEO_CONNECTED:String = "connected to video app event"; public static const VIDEO_CONNECTED:String = "connected to video app event";
public var reconnection:Boolean = false; public var reconnection:Boolean = false;
public function ConnectedEvent(reconnection:Boolean) public function ConnectedEvent(reconnection:Boolean)

View File

@ -129,7 +129,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<EventHandlers type="{BBBEvent.CAM_SETTINGS_CLOSED}"> <EventHandlers type="{BBBEvent.CAM_SETTINGS_CLOSED}">
<MethodInvoker generator="{VideoEventMapDelegate}" method="handleCamSettingsClosedEvent" arguments="{event}"/> <MethodInvoker generator="{VideoEventMapDelegate}" method="handleCamSettingsClosedEvent" arguments="{event}"/>
</EventHandlers> </EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_DISCONNECTED_EVENT}">
<MethodInvoker generator="{VideoEventMapDelegate}" method="closeAllWindows"/>
</EventHandlers>
<!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
</EventMap> </EventMap>

View File

@ -110,7 +110,7 @@ package org.bigbluebutton.modules.videoconf.maps
public function handleStreamStoppedEvent(event:StreamStoppedEvent):void { public function handleStreamStoppedEvent(event:StreamStoppedEvent):void {
if (UserManager.getInstance().getConference().amIThisUser(event.userId)) { if (UserManager.getInstance().getConference().amIThisUser(event.userId)) {
closePublishWindowWithStream(event.userId, event.streamId); closePublishWindowByStream(event.streamId);
} else { } else {
closeViewWindowWithStream(event.userId, event.streamId); closeViewWindowWithStream(event.userId, event.streamId);
} }
@ -262,11 +262,19 @@ package org.bigbluebutton.modules.videoconf.maps
_graphics.removeGraphicsFor(userID); _graphics.removeGraphicsFor(userID);
} }
private function closePublishWindowWithStream(userID:String, stream:String):int { private function closePublishWindowByStream(stream:String):int {
return _graphics.removeVideoByStreamName(userID, stream); return _graphics.removeVideoByStreamName(UsersUtil.getMyUserID(), stream);
}
private function closePublishWindow():void {
closeWindow(UsersUtil.getMyUserID());
} }
private function openViewWindowFor(userID:String):void { private function openViewWindowFor(userID:String):void {
if (!proxy.connection.connected) {
return;
}
LOGGER.debug("VideoEventMapDelegate:: [{0}] openViewWindowFor:: Opening VIEW window for [{1}] [{2}]", [me, userID, UsersUtil.getUserName(userID)]); LOGGER.debug("VideoEventMapDelegate:: [{0}] openViewWindowFor:: Opening VIEW window for [{1}] [{2}]", [me, userID, UsersUtil.getUserName(userID)]);
var bbbUser:BBBUser = UsersUtil.getUser(userID); var bbbUser:BBBUser = UsersUtil.getUser(userID);
@ -305,49 +313,21 @@ package org.bigbluebutton.modules.videoconf.maps
public function stopPublishing(e:StopBroadcastEvent):void{ public function stopPublishing(e:StopBroadcastEvent):void{
LOGGER.debug("VideoEventMapDelegate:: [{0}] Stop publishing. ready = [{1}]", [me, _ready]); LOGGER.debug("VideoEventMapDelegate:: [{0}] Stop publishing. ready = [{1}]", [me, _ready]);
if(streamList.length <= 1) { checkLastBroadcasting();
setStopLastBroadcasting();
} else {
UsersUtil.setIAmPublishing(true);
}
streamList.removeItem(e.stream); streamList.removeItem(e.stream);
stopBroadcasting(e.stream); stopBroadcasting(e.stream);
button.setCamAsInactive(e.camId); button.setCamAsInactive(e.camId);
} }
private function stopAllBroadcasting():void { private function checkLastBroadcasting():void {
LOGGER.debug("[VideoEventMapDelegate:stopAllBroadcasting]"); LOGGER.debug("[VideoEventMapDelegate:checkLastBroadcasting]");
setStopLastBroadcasting(); _isPublishing = streamList.length > 0;
streamList = new ArrayList(); UsersUtil.setIAmPublishing(streamList.length > 0);
proxy.stopAllBroadcasting();
var userID:String = UsersUtil.getMyUserID();
_graphics.removeGraphicsFor(userID);
var broadcastEvent:BroadcastStoppedEvent = new BroadcastStoppedEvent();
broadcastEvent.stream = "";
broadcastEvent.userid = UsersUtil.getMyUserID();
broadcastEvent.avatarURL = UsersUtil.getAvatarURL();
_dispatcher.dispatchEvent(broadcastEvent);
if (proxy.videoOptions.showButton) {
//Make toolbar button enabled again
button.setAllCamAsInactive();
}
if (options.displayAvatar) {
LOGGER.debug("VideoEventMapDelegate:: [{0}] Opening avatar", [me]);
openAvatarWindowFor(UsersUtil.getMyUserID());
}
} }
private function setStopLastBroadcasting():void { private function stopBroadcasting(stream:String = ""):void {
LOGGER.debug("[VideoEventMapDelegate:setStopLastBroadcasting]"); if (stream == null) stream = "";
_isPublishing = false; LOGGER.debug("Stopping broadcast{0}", [(stream.length > 0? " of stream [" + stream + "]": "")]);
UsersUtil.setIAmPublishing(false);
}
private function stopBroadcasting(stream:String):void {
LOGGER.debug("Stopping broadcast of stream [{0}]", [stream]);
proxy.stopBroadcasting(stream); proxy.stopBroadcasting(stream);
@ -357,11 +337,20 @@ package org.bigbluebutton.modules.videoconf.maps
broadcastEvent.avatarURL = UsersUtil.getAvatarURL(); broadcastEvent.avatarURL = UsersUtil.getAvatarURL();
_dispatcher.dispatchEvent(broadcastEvent); _dispatcher.dispatchEvent(broadcastEvent);
var camId:int = closePublishWindowWithStream(UsersUtil.getMyUserID(), stream); if (stream.length > 0) {
var camId:int = closePublishWindowByStream(stream);
if (proxy.videoOptions.showButton) { if (proxy.videoOptions.showButton) {
//Make toolbar button enabled again //Make toolbar button enabled again
button.publishingStatus(button.STOP_PUBLISHING, camId); button.publishingStatus(button.STOP_PUBLISHING, camId);
}
} else {
closePublishWindow();
if (proxy.videoOptions.showButton) {
// make toolbar button enabled again
button.setAllCamAsInactive();
}
} }
if (streamList.length == 0 && options.displayAvatar) { if (streamList.length == 0 && options.displayAvatar) {
@ -373,7 +362,7 @@ package org.bigbluebutton.modules.videoconf.maps
public function handleClosePublishWindowEvent(event:ClosePublishWindowEvent):void { public function handleClosePublishWindowEvent(event:ClosePublishWindowEvent):void {
LOGGER.debug("Closing publish window"); LOGGER.debug("Closing publish window");
if (_isPublishing || _chromeWebcamPermissionDenied) { if (_isPublishing || _chromeWebcamPermissionDenied) {
stopAllBroadcasting(); stopBroadcasting();
} }
} }
@ -388,7 +377,7 @@ package org.bigbluebutton.modules.videoconf.maps
public function handleStopAllShareCameraRequestEvent(event:StopShareCameraRequestEvent):void { public function handleStopAllShareCameraRequestEvent(event:StopShareCameraRequestEvent):void {
LOGGER.debug("[VideoEventMapDelegate:handleStopAllShareCameraRequestEvent]"); LOGGER.debug("[VideoEventMapDelegate:handleStopAllShareCameraRequestEvent]");
stopAllBroadcasting(); stopBroadcasting();
} }
public function handleStopShareCameraRequestEvent(event:StopShareCameraRequestEvent):void { public function handleStopShareCameraRequestEvent(event:StopShareCameraRequestEvent):void {
@ -425,7 +414,7 @@ package org.bigbluebutton.modules.videoconf.maps
public function closeAllWindows():void{ public function closeAllWindows():void{
LOGGER.debug("VideoEventMapDelegate:: closing all windows"); LOGGER.debug("VideoEventMapDelegate:: closing all windows");
if (_isPublishing) { if (_isPublishing) {
stopAllBroadcasting(); stopBroadcasting();
} }
_graphics.shutdown(); _graphics.shutdown();
@ -446,7 +435,7 @@ package org.bigbluebutton.modules.videoconf.maps
LOGGER.debug("****************** Switching to viewer. Show video button?=[{0}]", [UsersUtil.amIPresenter()]); LOGGER.debug("****************** Switching to viewer. Show video button?=[{0}]", [UsersUtil.amIPresenter()]);
displayToolbarButton(); displayToolbarButton();
if (_isPublishing && options.presenterShareOnly) { if (_isPublishing && options.presenterShareOnly) {
stopAllBroadcasting(); stopBroadcasting();
} }
} }
} }
@ -454,17 +443,13 @@ package org.bigbluebutton.modules.videoconf.maps
public function connectedToVideoApp(event: ConnectedEvent):void{ public function connectedToVideoApp(event: ConnectedEvent):void{
LOGGER.debug("VideoEventMapDelegate:: [{0}] Connected to video application.", [me]); LOGGER.debug("VideoEventMapDelegate:: [{0}] Connected to video application.", [me]);
_ready = true; _ready = true;
if (event.reconnection) { if (event.reconnection) {
LOGGER.debug("VideoEventMapDelegate:: Got reconnected event."); closeAllWindows()
stopAllBroadcasting(); } else {
LOGGER.debug("VideoEventMapDelegate:: Closing all webcam windows."); addToolbarButton();
closeAllWindows() }
openWebcamWindows(); openWebcamWindows();
} else {
addToolbarButton();
openWebcamWindows();
}
} }
public function handleCameraSetting(event:BBBEvent):void { public function handleCameraSetting(event:BBBEvent):void {

View File

@ -233,17 +233,6 @@ package org.bigbluebutton.modules.videoconf.views
} }
} }
/*
override public function validateDisplayList():void {
super.validateDisplayList();
if (priorityMode) {
updateDisplayListHelperByPriority(this.width, this.height);
} else {
updateDisplayListHelper(this.width, this.height);
}
}
*/
public function addAvatarFor(userId:String):void { public function addAvatarFor(userId:String):void {
if (! UsersUtil.hasUser(userId)) return; if (! UsersUtil.hasUser(userId)) return;

View File

@ -217,7 +217,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
} }
} }
private function updateTalkingStatus() : void{ private function updateTalkingStatus():void {
if (user.talking) { if (user.talking) {
titleBox.setStyle("styleName", "videoToolbarBackgroundTalkingStyle"); titleBox.setStyle("styleName", "videoToolbarBackgroundTalkingStyle");
} else { } else {

View File

@ -1,287 +1,289 @@
/** /**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* *
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
* *
* This program is free software; you can redistribute it and/or modify it under the * This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software * terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later * Foundation; either version 3.0 of the License, or (at your option) any later
* version. * version.
* *
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License along * You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package org.bigbluebutton.modules.whiteboard.business.shapes package org.bigbluebutton.modules.whiteboard.business.shapes
{ {
import flash.text.TextField; import flash.text.TextField;
import flash.text.TextFormat; import flash.text.TextFormat;
import flash.text.TextFormatAlign; import flash.text.TextFormatAlign;
import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger; import org.as3commons.logging.api.getClassLogger;
import org.as3commons.logging.util.jsonXify; import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.modules.whiteboard.models.Annotation; import org.bigbluebutton.modules.whiteboard.models.Annotation;
import org.bigbluebutton.util.i18n.ResourceUtil;
public class PollResultObject extends DrawObject {
private static const LOGGER:ILogger = getClassLogger(PollResultObject); public class PollResultObject extends DrawObject {
private static const LOGGER:ILogger = getClassLogger(PollResultObject);
//private const h:uint = 100;
//private const w:uint = 280; //private const h:uint = 100;
private const bgFill:uint = 0XCECECE; //0xFFFFFF; //private const w:uint = 280;
private const colFill:uint = 0x000000; private const bgFill:uint = 0xFFFFFF;
private const vpadding:Number = 10; private const colFill:uint = 0x000000;
private const hpadding:Number = 5; private const vpadding:Number = 10;
private const labelStartWidth:int = 40; private const hpadding:Number = 5;
private const percentStartWidth:int = 40; private const labelStartWidth:int = 40;
private const percentStartWidth:int = 40;
private var sampledata:Array = [{a:"A", v:3}, {a:"B", v:1}, {a:"C", v:5}, {a:"D", v:8}];
private var _data:Array; private var sampledata:Array = [{a:"A", v:3}, {a:"B", v:1}, {a:"C", v:5}, {a:"D", v:8}];
private var _textFields:Array; private var _data:Array;
private var _textFields:Array;
public function PollResultObject(id:String, type:String, status:String) {
super(id, type, status) public function PollResultObject(id:String, type:String, status:String) {
super(id, type, status)
_textFields = new Array();
data = null; _textFields = new Array();
// temp setter for testing purposes data = null;
//data = sampledata; // temp setter for testing purposes
//data = sampledata;
}
}
public function set data(d:Array):void {
_data = d; public function set data(d:Array):void {
} _data = d;
}
public function get data():Array {
return _data; public function get data():Array {
} return _data;
}
private function makeTextFields(num:int):void {
if (num > _textFields.length) { private function makeTextFields(num:int):void {
var textField:TextField; if (num > _textFields.length) {
for (var i:int=_textFields.length; i < num; i++) { var textField:TextField;
textField = new TextField(); for (var i:int=_textFields.length; i < num; i++) {
addChild(textField); textField = new TextField();
_textFields.push(textField); addChild(textField);
} _textFields.push(textField);
} else if (num < _textFields.length) { }
for (var j:int=_textFields.length; i > num; i--) { } else if (num < _textFields.length) {
removeChild(_textFields.pop()); for (var j:int=_textFields.length; i > num; i--) {
} removeChild(_textFields.pop());
} }
} }
}
private function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
// graphics.clear(); private function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
// graphics.clear();
if (_data != null && _data.length > 0) {
graphics.lineStyle(2); if (_data != null && _data.length > 0) {
graphics.beginFill(bgFill, 1.0); graphics.lineStyle(2);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight); graphics.beginFill(bgFill, 1.0);
graphics.endFill(); graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
var actualRH:Number = (unscaledHeight-vpadding*(_data.length+1)) / _data.length;
LOGGER.debug("PollGraphic - as raw {0} int {1}", [actualRH, int(actualRH)]); var actualRH:Number = (unscaledHeight-vpadding*(_data.length+1)) / _data.length;
// Current problem is that the rowHeight is truncated. It would be nice if the extra pixels LOGGER.debug("PollGraphic - as raw {0} int {1}", [actualRH, int(actualRH)]);
// could be distributed for a more even look. // Current problem is that the rowHeight is truncated. It would be nice if the extra pixels
var avgRowHeight:int = (unscaledHeight-vpadding*(_data.length+1)) / _data.length; // could be distributed for a more even look.
var extraVPixels:int = unscaledHeight - (_data.length * (avgRowHeight+vpadding) + vpadding); var avgRowHeight:int = (unscaledHeight-vpadding*(_data.length+1)) / _data.length;
LOGGER.debug("PollGraphic - extraVPixels {0}", [extraVPixels]); var extraVPixels:int = unscaledHeight - (_data.length * (avgRowHeight+vpadding) + vpadding);
var largestVal:int = -1; LOGGER.debug("PollGraphic - extraVPixels {0}", [extraVPixels]);
var totalCount:Number = 0; var largestVal:int = -1;
//find largest value var totalCount:Number = 0;
for (var i:int=0; i<_data.length; i++) { //find largest value
if (_data[i].v > largestVal) largestVal = _data[i].v; for (var i:int=0; i<_data.length; i++) {
totalCount += _data[i].v; if (_data[i].v > largestVal) largestVal = _data[i].v;
} totalCount += _data[i].v;
}
var currTFIdx:int = 0;
var answerText:TextField; var currTFIdx:int = 0;
var percentText:TextField; var answerText:TextField;
var answerArray:Array = new Array(); var percentText:TextField;
var percentArray:Array = new Array(); var answerArray:Array = new Array();
var minFontSize:int = 20; var percentArray:Array = new Array();
var currFontSize:int; var minFontSize:int = 20;
var currFontSize:int;
graphics.lineStyle(2);
graphics.beginFill(colFill, 1.0); graphics.lineStyle(2);
for (var j:int=0, vp:int=extraVPixels, ry:int=0, curRowHeight:int=0; j<_data.length; j++) { graphics.beginFill(colFill, 1.0);
ry += Math.round(curRowHeight/2)+vpadding; // add the last row's height plus padding for (var j:int=0, vp:int=extraVPixels, ry:int=0, curRowHeight:int=0; j<_data.length; j++) {
ry += Math.round(curRowHeight/2)+vpadding; // add the last row's height plus padding
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) { curRowHeight = avgRowHeight;
curRowHeight += 1; if (j%2==0 && vp > 0) {
vp--; curRowHeight += 1;
} vp--;
ry += curRowHeight/2; }
ry += curRowHeight/2;
//ry += curRowHeight * (j+0.5) + vpadding*(j+1);
// add row label //ry += curRowHeight * (j+0.5) + vpadding*(j+1);
answerText = _textFields[currTFIdx++]; // add row label
answerText.text = _data[j].a; answerText = _textFields[currTFIdx++];
answerText.width = labelStartWidth; answerText.text = _data[j].a;
answerText.height = curRowHeight; answerText.width = labelStartWidth;
answerText.selectable = false; answerText.height = curRowHeight;
//addChild(answerText); answerText.selectable = false;
answerArray.push(answerText); //addChild(answerText);
currFontSize = findFontSize(answerText, 20); answerArray.push(answerText);
if (currFontSize < minFontSize) minFontSize = currFontSize; currFontSize = findFontSize(answerText, 20);
//rowText.height = rowText.textHeight; if (currFontSize < minFontSize) minFontSize = currFontSize;
answerText.x = hpadding; //rowText.height = rowText.textHeight;
//rowText.y = ry-rowText.height/2; answerText.x = hpadding;
//rowText.y = ry-rowText.height/2;
// add percentage
percentText = _textFields[currTFIdx++];;// new TextField(); // add percentage
var percentNum:Number = (totalCount == 0 ? 0 : ((_data[j].v/totalCount)*100)); percentText = _textFields[currTFIdx++];;// new TextField();
percentText.text = Math.round(percentNum).toString() + "%"; var percentNum:Number = (totalCount == 0 ? 0 : ((_data[j].v/totalCount)*100));
percentText.width = percentStartWidth; percentText.text = Math.round(percentNum).toString() + "%";
percentText.height = curRowHeight; percentText.width = percentStartWidth;
percentText.selectable = false; percentText.height = curRowHeight;
//addChild(percentText); percentText.selectable = false;
percentArray.push(percentText); //addChild(percentText);
currFontSize = findFontSize(percentText, 20); percentArray.push(percentText);
if (currFontSize < minFontSize) minFontSize = currFontSize; currFontSize = findFontSize(percentText, 20);
//percentText.height = percentText.textHeight; if (currFontSize < minFontSize) minFontSize = currFontSize;
//percentText.x = unscaledWidth-percentStartWidth/2-percentText.width/2; //percentText.height = percentText.textHeight;
//percentText.y = ry-percentText.height/2; //percentText.x = unscaledWidth-percentStartWidth/2-percentText.width/2;
} //percentText.y = ry-percentText.height/2;
}
var maxAnswerWidth:int = 0;
var maxPercentWidth:int = 0; var maxAnswerWidth:int = 0;
var maxPercentWidth:int = 0;
for (j=0, vp=extraVPixels, ry=0, curRowHeight=0; j<_data.length; j++) {
ry += Math.round(curRowHeight/2)+vpadding; // add the last row's height plus padding for (j=0, vp=extraVPixels, ry=0, curRowHeight=0; j<_data.length; j++) {
ry += Math.round(curRowHeight/2)+vpadding; // add the last row's height plus padding
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) { curRowHeight = avgRowHeight;
curRowHeight += 1; if (j%2==0 && vp > 0) {
vp--; curRowHeight += 1;
} vp--;
ry += curRowHeight/2; }
ry += curRowHeight/2;
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
answerText = TextField(answerArray[j]);
findFontSize(answerText, minFontSize); answerText = TextField(answerArray[j]);
answerText.width = answerText.textWidth+4; findFontSize(answerText, minFontSize);
answerText.height = answerText.textHeight+4; answerText.width = answerText.textWidth+4;
answerText.y = ry-answerText.height/2; answerText.height = answerText.textHeight+4;
if (answerText.width > maxAnswerWidth) maxAnswerWidth = answerText.width; answerText.y = ry-answerText.height/2;
if (answerText.width > maxAnswerWidth) maxAnswerWidth = answerText.width;
percentText = TextField(percentArray[j]);
findFontSize(percentText, minFontSize); percentText = TextField(percentArray[j]);
percentText.width = percentText.textWidth+4; findFontSize(percentText, minFontSize);
percentText.height = percentText.textHeight+4; percentText.width = percentText.textWidth+4;
percentText.x = unscaledWidth - hpadding - percentText.width; percentText.height = percentText.textHeight+4;
percentText.y = ry-percentText.height/2; percentText.x = unscaledWidth - hpadding - percentText.width;
if (percentText.width > maxPercentWidth) maxPercentWidth = percentText.width; percentText.y = ry-percentText.height/2;
if (percentText.width > maxPercentWidth) maxPercentWidth = percentText.width;
}
}
var countText:TextField;
var maxBarWidth:int = unscaledWidth - (hpadding*4) - maxAnswerWidth - maxPercentWidth; var countText:TextField;
var barStartX:int = maxAnswerWidth + (hpadding*2); var maxBarWidth:int = unscaledWidth - (hpadding*4) - maxAnswerWidth - maxPercentWidth;
var barStartX:int = maxAnswerWidth + (hpadding*2);
for (j=0, vp=extraVPixels, ry=0, curRowHeight=0; j<_data.length; j++) {
ry += Math.round(curRowHeight/2)+vpadding; // add the last row's height plus padding for (j=0, vp=extraVPixels, ry=0, curRowHeight=0; j<_data.length; j++) {
ry += Math.round(curRowHeight/2)+vpadding; // add the last row's height plus padding
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) { curRowHeight = avgRowHeight;
curRowHeight += 1; if (j%2==0 && vp > 0) {
vp--; curRowHeight += 1;
} vp--;
ry += curRowHeight/2; }
ry += curRowHeight/2;
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
// draw rect
var rectWidth:int = maxBarWidth*(_data[j].v/largestVal); // draw rect
graphics.drawRect(barStartX, ry-curRowHeight/2, rectWidth, curRowHeight); var rectWidth:int = maxBarWidth*(_data[j].v/largestVal);
// add vote count in middle of rect graphics.drawRect(barStartX, ry-curRowHeight/2, rectWidth, curRowHeight);
countText = _textFields[currTFIdx++]; // new TextField(); // add vote count in middle of rect
countText.text = _data[j].v; countText = _textFields[currTFIdx++]; // new TextField();
countText.width = rectWidth; countText.text = _data[j].v;
countText.height = curRowHeight; countText.width = rectWidth;
countText.textColor = 0xFFFFFF; countText.height = curRowHeight;
countText.selectable = false; countText.textColor = 0xFFFFFF;
//addChild(countText); countText.selectable = false;
findFontSize(countText, minFontSize); //addChild(countText);
countText.width = countText.textWidth+4; findFontSize(countText, minFontSize);
countText.height = countText.textHeight+4; countText.width = countText.textWidth+4;
countText.x = barStartX+rectWidth/2-countText.width/2; countText.height = countText.textHeight+4;
countText.y = ry-countText.height/2; countText.x = barStartX+rectWidth/2-countText.width/2;
} countText.y = ry-countText.height/2;
}
graphics.endFill();
} graphics.endFill();
} }
}
private function findFontSize(textField:TextField, defaultSize:Number):int {
var tFormat:TextFormat = new TextFormat(); private function findFontSize(textField:TextField, defaultSize:Number):int {
tFormat.size = defaultSize; var tFormat:TextFormat = new TextFormat();
tFormat.align = TextFormatAlign.CENTER; tFormat.size = defaultSize;
textField.setTextFormat(tFormat); tFormat.font = "arial";
var size:Number = defaultSize; tFormat.align = TextFormatAlign.CENTER;
while((textField.textWidth+4 > textField.width || textField.textHeight+4 > textField.height) && size > 0) { textField.setTextFormat(tFormat);
size = size - 1; var size:Number = defaultSize;
tFormat.size = size; while((textField.textWidth+4 > textField.width || textField.textHeight+4 > textField.height) && size > 0) {
textField.setTextFormat(tFormat); size = size - 1;
} tFormat.size = size;
textField.setTextFormat(tFormat);
return size; }
}
return size;
private function drawRect(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void { }
var ao:Object = a.annotation;
this.graphics.lineStyle(1 * zoom, 0); private function drawRect(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void {
var ao:Object = a.annotation;
var arrayEnd:Number = (ao.points as Array).length; this.graphics.lineStyle(1 * zoom, 0);
var startX:Number = denormalize(21.845575, parentWidth);
var startY:Number = denormalize(23.145401, parentHeight); var arrayEnd:Number = (ao.points as Array).length;
var width:Number = denormalize(46.516006, parentWidth) - startX; var startX:Number = denormalize(21.845575, parentWidth);
var height:Number = denormalize(61.42433, parentHeight) - startY; var startY:Number = denormalize(23.145401, parentHeight);
var width:Number = denormalize(46.516006, parentWidth) - startX;
this.graphics.drawRect(startX, startY, width, height); var height:Number = denormalize(61.42433, parentHeight) - startY;
} this.graphics.drawRect(startX, startY, width, height);
override public function draw(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void { }
var ao:Object = a.annotation;
LOGGER.debug("RESULT = {0}", [jsonXify(a)]); override public function draw(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void {
var ao:Object = a.annotation;
var arrayEnd:Number = (ao.points as Array).length; LOGGER.debug("RESULT = {0}", [jsonXify(a)]);
var startX:Number = denormalize((ao.points as Array)[0], parentWidth);
var startY:Number = denormalize((ao.points as Array)[1], parentHeight); var arrayEnd:Number = (ao.points as Array).length;
var pwidth:Number = denormalize((ao.points as Array)[2], parentWidth) - startX; var startX:Number = denormalize((ao.points as Array)[0], parentWidth);
var pheight:Number = denormalize((ao.points as Array)[3], parentHeight) - startY; var startY:Number = denormalize((ao.points as Array)[1], parentHeight);
var pwidth:Number = denormalize((ao.points as Array)[2], parentWidth);
var answers:Array = ao.result as Array; var pheight:Number = denormalize((ao.points as Array)[3], parentHeight);
var ans:Array = new Array();
for (var j:int = 0; j < answers.length; j++) { var answers:Array = ao.result as Array;
var ar:Object = answers[j]; var ans:Array = new Array();
var rs:Object = {a: ar.key, v: ar.num_votes as Number}; for (var j:int = 0; j < answers.length; j++) {
LOGGER.debug("poll result a=[{0}] v=[{1}]", [ar.key, ar.num_votes]); var ar:Object = answers[j];
ans.push(rs); var rs:Object = {a: ResourceUtil.getInstance().getString('bbb.polling.answer.' + ar.key), v: ar.num_votes as Number};
} LOGGER.debug("poll result a=[{0}] v=[{1}]", [ar.key, ar.num_votes]);
ans.push(rs);
data = ans; }
makeTextFields((answers != null ? answers.length*3 : 0));
data = ans;
this.x = startX; makeTextFields((answers != null ? answers.length*3 : 0));
this.y = startY;
this.x = startX;
updateDisplayList(pwidth, pheight); this.y = startY;
} updateDisplayList(pwidth, pheight);
override public function redraw(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void { }
draw(a, parentWidth, parentHeight, zoom);
} override public function redraw(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void {
} draw(a, parentWidth, parentHeight, zoom);
}
}
} }

View File

@ -138,5 +138,10 @@ package org.bigbluebutton.modules.whiteboard.managers
public function handlePageChangedEvent(e:PageLoadedEvent):void { public function handlePageChangedEvent(e:PageLoadedEvent):void {
displayModel.changePage(e.pageId); displayModel.changePage(e.pageId);
} }
public function removeAnnotationsHistory():void {
// it will dispatch the cleanAnnotations in the displayModel later
whiteboardModel.clear();
}
} }
} }

View File

@ -23,6 +23,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/" xmlns:mate="org.bigbluebutton.common.mate.*"> <EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/" xmlns:mate="org.bigbluebutton.common.mate.*">
<mx:Script> <mx:Script>
<![CDATA[ <![CDATA[
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ModuleStartedEvent; import org.bigbluebutton.main.events.ModuleStartedEvent;
import org.bigbluebutton.modules.present.events.AddOverlayCanvasEvent; import org.bigbluebutton.modules.present.events.AddOverlayCanvasEvent;
import org.bigbluebutton.modules.present.events.NavigationEvent; import org.bigbluebutton.modules.present.events.NavigationEvent;
@ -122,6 +123,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<MethodInvoker generator="{WhiteboardManager}" method="handlePageChangedEvent" arguments="{event}" /> <MethodInvoker generator="{WhiteboardManager}" method="handlePageChangedEvent" arguments="{event}" />
</EventHandlers> </EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT}" >
<MethodInvoker generator="{WhiteboardManager}" method="removeAnnotationsHistory" />
</EventHandlers>
<Injectors target="{WhiteboardManager}"> <Injectors target="{WhiteboardManager}">
<ObjectBuilder generator="{WhiteboardModel}" cache="global" constructorArguments="{scope.dispatcher}"/> <ObjectBuilder generator="{WhiteboardModel}" cache="global" constructorArguments="{scope.dispatcher}"/>
<PropertyInjector targetKey="whiteboardModel" source="{WhiteboardModel}"/> <PropertyInjector targetKey="whiteboardModel" source="{WhiteboardModel}"/>

View File

@ -135,11 +135,16 @@ package org.bigbluebutton.modules.whiteboard.models
} }
public function clear(wbId:String):void { public function clear(wbId:String = null):void {
LOGGER.debug("Clearing whiteboard"); LOGGER.debug("Clearing whiteboard");
var wb:Whiteboard = getWhiteboard(wbId); if (wbId != null) {
if (wb != null) { var wb:Whiteboard = getWhiteboard(wbId);
wb.clear(); if (wb != null) {
wb.clear();
_dispatcher.dispatchEvent(new WhiteboardUpdate(WhiteboardUpdate.CLEAR_ANNOTATIONS));
}
} else {
_whiteboards.removeAll();
_dispatcher.dispatchEvent(new WhiteboardUpdate(WhiteboardUpdate.CLEAR_ANNOTATIONS)); _dispatcher.dispatchEvent(new WhiteboardUpdate(WhiteboardUpdate.CLEAR_ANNOTATIONS));
} }

View File

@ -37,7 +37,7 @@ package org.bigbluebutton.modules.whiteboard.services
} }
public function onMessage(messageName:String, message:Object):void { public function onMessage(messageName:String, message:Object):void {
// LogUtil.debug("WB: received message " + messageName); // trace("WB: received message " + messageName);
switch (messageName) { switch (messageName) {
case "WhiteboardRequestAnnotationHistoryReply": case "WhiteboardRequestAnnotationHistoryReply":

View File

@ -26,3 +26,5 @@ pagebakers:ionicons
ewall:foundation ewall:foundation
maibaum:foundation-icons maibaum:foundation-icons
gthacoder:sled gthacoder:sled
chriswessels:hammer@3.1.1
fastclick

View File

@ -1,5 +1,6 @@
agnito:raphael@0.1.0 agnito:raphael@0.1.0
aldeed:simple-schema@1.3.2 aldeed:simple-schema@1.3.2
aldeed:template-extension@3.4.3
amplify@1.0.0 amplify@1.0.0
arunoda:npm@0.2.6 arunoda:npm@0.2.6
autoupdate@1.2.1 autoupdate@1.2.1
@ -12,6 +13,7 @@ brentjanderson:winston-client@0.2.1
callback-hook@1.0.3 callback-hook@1.0.3
cfs:http-methods@0.0.27 cfs:http-methods@0.0.27
check@1.0.5 check@1.0.5
chriswessels:hammer@3.1.1
clinical:nightwatch@2.0.1 clinical:nightwatch@2.0.1
coffeescript@1.0.6 coffeescript@1.0.6
ddp@1.1.0 ddp@1.1.0

View File

@ -15,9 +15,9 @@
# Convert a color `value` as integer to a hex color (e.g. 255 to #0000ff) # Convert a color `value` as integer to a hex color (e.g. 255 to #0000ff)
@colourToHex = (value) -> @colourToHex = (value) ->
hex = parseInt(value).toString(16) hex = parseInt(value).toString(16)
hex = "0" + hex while hex.length < 6 hex = "0" + hex while hex.length < 6
"##{hex}" "##{hex}"
# color can be a number (a hex converted to int) or a string (e.g. "#ffff00") # color can be a number (a hex converted to int) or a string (e.g. "#ffff00")
@formatColor = (color) -> @formatColor = (color) ->
@ -34,6 +34,10 @@
@getTime = -> # returns epoch in ms @getTime = -> # returns epoch in ms
(new Date).valueOf() (new Date).valueOf()
# checks if the pan gesture is mostly horizontal
@isPanHorizontal = (event) ->
Math.abs(event.deltaX) > Math.abs(event.deltaY)
# helper to determine whether user has joined any type of audio # helper to determine whether user has joined any type of audio
Handlebars.registerHelper "amIInAudio", -> Handlebars.registerHelper "amIInAudio", ->
BBB.amIInAudio() BBB.amIInAudio()
@ -212,11 +216,55 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
$('.sl-left-drawer').addClass('hiddenInLandscape') $('.sl-left-drawer').addClass('hiddenInLandscape')
setTimeout(redrawWhiteboard, 0) setTimeout(redrawWhiteboard, 0)
@populateNotifications = (msg) ->
myUserId = getInSession "userId"
users = Meteor.Users.find().fetch()
# assuming that I only have access only to private messages where I am the sender or the recipient
myPrivateChats = Meteor.Chat.find({'message.chat_type': 'PRIVATE_CHAT'}).fetch()
uniqueArray = []
for chat in myPrivateChats
if chat.message.to_userid is myUserId
uniqueArray.push({userId: chat.message.from_userid, username: chat.message.from_username})
if chat.message.from_userid is myUserId
uniqueArray.push({userId: chat.message.to_userid, username: chat.message.to_username})
#keep unique entries only
uniqueArray = uniqueArray.filter((itm, i, a) ->
i is a.indexOf(itm)
)
if msg.message.to_userid is myUserId
new_msg_userid = msg.message.from_userid
if msg.message.from_userid is myUserId
new_msg_userid = msg.message.to_userid
chats = getInSession('chats')
if chats is undefined
initChats = [
userId: "PUBLIC_CHAT"
gotMail: false
number: 0;
]
setInSession 'chats', initChats
#insert the unique entries in the collection
for u in uniqueArray
chats = getInSession('chats')
if chats.filter((chat) -> chat.userId == u.userId).length is 0 and u.userId is new_msg_userid
chats.push {userId: u.userId, gotMail: false, number: 0}
setInSession 'chats', chats
@toggleShield = -> @toggleShield = ->
if $('.shield').hasClass('darken') if parseFloat($('.shield').css('opacity')) is 0.5 # triggered during a pan gesture
$('.shield').removeClass('darken') $('.shield').css('opacity', '')
else
if !$('.shield').hasClass('darken') and !$('.shield').hasClass('animatedShield')
$('.shield').addClass('darken') $('.shield').addClass('darken')
else
$('.shield').removeClass('darken')
$('.shield').removeClass('animatedShield')
@removeFullscreenStyles = -> @removeFullscreenStyles = ->
$('#whiteboard-paper').removeClass('verticallyCentered') $('#whiteboard-paper').removeClass('verticallyCentered')
@ -327,7 +375,7 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
@clearSessionVar = (callback) -> @clearSessionVar = (callback) ->
amplify.store('authToken', null) amplify.store('authToken', null)
amplify.store('bbbServerVersion', null) amplify.store('bbbServerVersion', null)
amplify.store('chatTabs', null) amplify.store('chats', null)
amplify.store('dateOfBuild', null) amplify.store('dateOfBuild', null)
amplify.store('display_chatPane', null) amplify.store('display_chatPane', null)
amplify.store('display_chatbar', null) amplify.store('display_chatbar', null)
@ -351,9 +399,11 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
setInSession "display_chatbar", true setInSession "display_chatbar", true
setInSession "display_whiteboard", true setInSession "display_whiteboard", true
setInSession "display_chatPane", true setInSession "display_chatPane", true
if not getInSession "inChatWith" then setInSession "inChatWith", 'PUBLIC_CHAT'
#if it is a desktop version of the client
if isPortraitMobile() or isLandscapeMobile() if isPortraitMobile() or isLandscapeMobile()
setInSession "messageFontSize", Meteor.config.app.mobileFont setInSession "messageFontSize", Meteor.config.app.mobileFont
#if this is a mobile version of the client
else else
setInSession "messageFontSize", Meteor.config.app.desktopFont setInSession "messageFontSize", Meteor.config.app.desktopFont
setInSession 'display_slidingMenu', false setInSession 'display_slidingMenu', false
@ -363,8 +413,34 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
else else
setInSession 'display_usersList', false setInSession 'display_usersList', false
setInSession 'display_menu', false setInSession 'display_menu', false
setInSession 'chatInputMinHeight', 0
#keep notifications and an opened private chat tab if page was refreshed
#reset to default if that's a new user
if loginOrRefresh()
initChats = [
userId: "PUBLIC_CHAT"
gotMail: false
number: 0
]
setInSession 'chats', initChats
setInSession "inChatWith", 'PUBLIC_CHAT'
TimeSync.loggingEnabled = false # suppresses the log messages from timesync TimeSync.loggingEnabled = false # suppresses the log messages from timesync
#true if it is a new user, false if the client was just refreshed
@loginOrRefresh = ->
userId = getInSession 'userId'
checkId = getInSession 'checkId'
if checkId is undefined
setInSession 'checkId', userId
return true
else if userId isnt checkId
setInSession 'checkId', userId
return true
else
return false
@onLoadComplete = -> @onLoadComplete = ->
document.title = "BigBlueButton #{BBB.getMeetingName() ? 'HTML5'}" document.title = "BigBlueButton #{BBB.getMeetingName() ? 'HTML5'}"
setDefaultSettings() setDefaultSettings()
@ -386,7 +462,14 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
navigator.userAgent.match(/webOS/i) navigator.userAgent.match(/webOS/i)
@isLandscape = -> @isLandscape = ->
window.matchMedia('(orientation: landscape)').matches not isMobile() and
window.matchMedia('(orientation: landscape)').matches and # browser is landscape
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches # device is landscape
@isPortrait = ->
not isMobile() and
window.matchMedia('(orientation: portrait)').matches and # browser is portrait
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches # device is landscape
# Checks if the view is portrait and a mobile device is being used # Checks if the view is portrait and a mobile device is being used
@isPortraitMobile = () -> @isPortraitMobile = () ->
@ -397,8 +480,8 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
# Checks if the view is landscape and mobile device is being used # Checks if the view is landscape and mobile device is being used
@isLandscapeMobile = () -> @isLandscapeMobile = () ->
isMobile() and isMobile() and
window.matchMedia('(orientation: landscape)').matches and # browser is landscape window.matchMedia('(orientation: landscape)').matches and # browser is landscape
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches # device is landscape window.matchMedia('(min-device-aspect-ratio: 1/1)').matches # device is landscape
# Checks if only one panel (userlist/whiteboard/chatbar) is currently open # Checks if only one panel (userlist/whiteboard/chatbar) is currently open
@isOnlyOnePanelOpen = () -> @isOnlyOnePanelOpen = () ->
@ -417,3 +500,22 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
return 'IE' return 'IE'
else else
return null return null
# changes the height of the chat input area if needed (based on the textarea content)
@adjustChatInputHeight = () ->
$('#newMessageInput').css('height', 'auto')
projectedHeight = $('#newMessageInput')[0].scrollHeight + 23
if projectedHeight isnt $('.panel-footer').height() and
projectedHeight >= getInSession('chatInputMinHeight')
$('#newMessageInput').css('overflow', 'hidden') # prevents a scroll bar
# resizes the chat input area
$('.panel-footer').css('top', - (projectedHeight - 70) + 'px')
$('.panel-footer').css('height', projectedHeight + 'px')
$('#newMessageInput').height($('#newMessageInput')[0].scrollHeight)
# resizes the chat messages container
$('#chatbody').height($('#chat').height() - projectedHeight - 45)
$('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight)
$('#newMessageInput').css('height', '')

View File

@ -141,5 +141,177 @@ Template.main.events
$('.signOutIcon').blur() $('.signOutIcon').blur()
$("#logoutModal").foundation('reveal', 'open'); $("#logoutModal").foundation('reveal', 'open');
Template.main.gestures
'panstart #container': (event, template) ->
if isPortraitMobile() and isPanHorizontal(event)
panIsValid = getInSession('panIsValid')
initTransformValue = getInSession('initTransform')
menuPanned = getInSession('menuPanned')
screenWidth = $('#container').width()
setInSession 'panStarted', true
if panIsValid and
menuPanned is 'left' and
initTransformValue + event.deltaX >= 0 and
initTransformValue + event.deltaX <= $('.left-drawer').width()
$('.left-drawer').css('transform', 'translateX(' + (initTransformValue + event.deltaX) + 'px)')
else if panIsValid and
menuPanned is 'right' and
initTransformValue + event.deltaX >= screenWidth - $('.right-drawer').width() and
initTransformValue + event.deltaX <= screenWidth
$('.right-drawer').css('transform', 'translateX(' + (initTransformValue + event.deltaX) + 'px)')
'panend #container': (event, template) ->
if isPortraitMobile()
panIsValid = getInSession('panIsValid')
menuPanned = getInSession('menuPanned')
leftDrawerWidth = $('.left-drawer').width()
screenWidth = $('#container').width()
setInSession 'panStarted', false
if panIsValid and
menuPanned is 'left' and
$('.left-drawer').css('transform') isnt 'none'
if parseInt($('.left-drawer').css('transform').split(',')[4]) < leftDrawerWidth / 2
$('.shield').removeClass('animatedShield')
$('.shield').css('opacity', '')
$('.left-drawer').removeClass('sl-left-drawer-out')
$('.left-drawer').css('transform', '')
$('.toggleUserlistButton').removeClass('sl-toggled-on')
$('.shield').removeClass('darken') # in case it was opened by clicking a button
else
$('.left-drawer').css('transform', 'translateX(' + leftDrawerWidth + 'px)')
$('.shield').css('opacity', 0.5)
$('.left-drawer').addClass('sl-left-drawer-out')
$('.left-drawer').css('transform', '')
$('.toggleUserlistButton').addClass('sl-toggled-on')
if panIsValid and
menuPanned is 'right' and
parseInt($('.right-drawer').css('transform').split(',')[4]) isnt leftDrawerWidth
if parseInt($('.right-drawer').css('transform').split(',')[4]) > screenWidth - $('.right-drawer').width() / 2
$('.shield').removeClass('animatedShield')
$('.shield').css('opacity', '')
$('.right-drawer').css('transform', 'translateX(' + screenWidth + 'px)')
$('.right-drawer').removeClass('sl-right-drawer-out')
$('.right-drawer').css('transform', '')
$('.toggleMenuButton').removeClass('sl-toggled-on')
$('.shield').removeClass('darken') # in case it was opened by clicking a button
else
$('.shield').css('opacity', 0.5)
$('.right-drawer').css('transform', 'translateX(' + (screenWidth - $('.right-drawer').width()) + 'px)')
$('.right-drawer').addClass('sl-right-drawer-out')
$('.right-drawer').css('transform', '')
$('.toggleMenuButton').addClass('sl-toggled-on')
$('.left-drawer').addClass('sl-left-drawer')
$('.sl-left-drawer').removeClass('left-drawer')
$('.right-drawer').addClass('sl-right-drawer')
$('.sl-right-drawer').removeClass('right-drawer')
'panright #container, panleft #container': (event, template) ->
if isPortraitMobile() and isPanHorizontal(event)
# panright/panleft is always triggered once right before panstart
if !getInSession('panStarted')
# opening the left-hand menu
if event.type is 'panright' and
event.center.x <= $('#container').width() * 0.1
setInSession 'panIsValid', true
setInSession 'menuPanned', 'left'
# closing the left-hand menu
else if event.type is 'panleft' and
event.center.x < $('#container').width() * 0.9
setInSession 'panIsValid', true
setInSession 'menuPanned', 'left'
# opening the right-hand menu
else if event.type is 'panleft' and
event.center.x >= $('#container').width() * 0.9
setInSession 'panIsValid', true
setInSession 'menuPanned', 'right'
# closing the right-hand menu
else if event.type is 'panright' and
event.center.x > $('#container').width() * 0.1
setInSession 'panIsValid', true
setInSession 'menuPanned', 'right'
else
setInSession 'panIsValid', false
setInSession 'eventType', event.type
if getInSession('menuPanned') is 'left'
if $('.sl-left-drawer').css('transform') isnt 'none' # menu is already transformed
setInSession 'initTransform', parseInt($('.sl-left-drawer').css('transform').split(',')[4]) # translateX value
else if $('.sl-left-drawer').hasClass('sl-left-drawer-out')
setInSession 'initTransform', $('.sl-left-drawer').width()
else
setInSession 'initTransform', 0
$('.sl-left-drawer').addClass('left-drawer')
$('.left-drawer').removeClass('sl-left-drawer') # to prevent animations from Sled library
$('.left-drawer').removeClass('sl-left-drawer-content-delay') # makes the menu content movable too
else if getInSession('menuPanned') is 'right'
if $('.sl-right-drawer').css('transform') isnt 'none' # menu is already transformed
setInSession 'initTransform', parseInt($('.sl-right-drawer').css('transform').split(',')[4]) # translateX value
else if $('.sl-right-drawer').hasClass('sl-right-drawer-out')
setInSession 'initTransform', $('.sl-right-drawer').width()
else
setInSession 'initTransform', 0
$('.sl-right-drawer').addClass('right-drawer')
$('.right-drawer').removeClass('sl-right-drawer') # to prevent animations from Sled library
$('.right-drawer').removeClass('sl-right-drawer-content-delay') # makes the menu content movable too
initTransformValue = getInSession('initTransform')
panIsValid = getInSession('panIsValid')
menuPanned = getInSession('menuPanned')
leftDrawerWidth = $('.left-drawer').width()
rightDrawerWidth = $('.right-drawer').width()
screenWidth = $('#container').width()
# moving the left-hand menu
if panIsValid and
menuPanned is 'left' and
initTransformValue + event.deltaX >= 0 and
initTransformValue + event.deltaX <= leftDrawerWidth
if $('.sl-right-drawer').hasClass('sl-right-drawer-out')
toggleRightDrawer()
toggleRightArrowClockwise()
$('.left-drawer').css('transform', 'translateX(' + (initTransformValue + event.deltaX) + 'px)')
if !getInSession('panStarted')
$('.shield').addClass('animatedShield')
$('.shield').css('opacity',
0.5 * (initTransformValue + event.deltaX) / leftDrawerWidth)
# moving the right-hand menu
else if panIsValid and
menuPanned is 'right' and
initTransformValue + event.deltaX >= screenWidth - rightDrawerWidth and
initTransformValue + event.deltaX <= screenWidth
if $('.sl-left-drawer').hasClass('sl-left-drawer-out')
toggleLeftDrawer()
toggleLeftArrowClockwise()
$('.right-drawer').css('transform', 'translateX(' + (initTransformValue + event.deltaX) + 'px)')
if !getInSession('panStarted')
$('.shield').addClass('animatedShield')
$('.shield').css('opacity',
0.5 * (screenWidth - initTransformValue - event.deltaX) / rightDrawerWidth)
Template.makeButton.rendered = -> Template.makeButton.rendered = ->
$('button[rel=tooltip]').tooltip() $('button[rel=tooltip]').tooltip()

View File

@ -1,9 +1,7 @@
<template name="header"> <template name="header">
<nav id="navbar" class="myNavbar gradientBar top-bar" role="navigation"> <nav id="navbar" class="myNavbar top-bar" role="navigation">
<button class="btn toggleUserlistButton navbarButton sl-hamburger sl-ham-la-cw sl-portrait-mobile sl-portrait-keyboard"> {{> makeButton btn_class="btn toggleUserlistButton navbarButton sl-hamburger sl-ham-la-cw sl-portrait-mobile sl-portrait-keyboard" rel="tooltip" title="Toggle Userlist" span=true notification="all_chats"}}
<span></span>
</button>
{{#if amIInAudio}} {{#if amIInAudio}}
{{#if amIListenOnlyAudio}} {{#if amIListenOnlyAudio}}
{{> makeButton btn_class="navbarButton leaveAudioButton" i_class="icon fi-volume-none" rel="tooltip" title="Exit Audio"}} {{> makeButton btn_class="navbarButton leaveAudioButton" i_class="icon fi-volume-none" rel="tooltip" title="Exit Audio"}}

View File

@ -11,9 +11,21 @@
height: 45px; height: 45px;
min-height: 45px; min-height: 45px;
.btn { .btn {
&:hover {
color: #3896D3;
background-color:white;
border-bottom: 1px solid #E5E5E5;
}
&:focus {
background-color:white;
border-bottom: 1px solid #E5E5E5;
}
padding: 0.625rem 0.5rem 0.6875rem 0.5rem;
background-color: inherit; background-color: inherit;
margin: 0; margin: 0;
height: 45px; height: 45px;
outline: none;
min-width: 50px;
} }
span { span {
position: relative; position: relative;
@ -29,23 +41,40 @@
} }
} }
.privateChatName { .privateChatName {
width: calc(~'100% - 105px');
min-width: 101px;
text-align: right;
float: right; float: right;
padding: 0.65rem 1.25rem 0.6rem 0.3rem; padding: 0.65rem 1.25rem 0.6rem 0.3rem;
font-family: "Helvetica Neue",Helvetica,Roboto,Arial,sans-serif; font-family: "Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;
font-size: 18px; font-size: 18px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.toPublic .unreadChatNumber {
@media @desktop-portrait, @landscape {
padding: 2px;
position: absolute;
top: 31%;
left: 100%;
margin: 0;
} }
} }
#chat { #chat {
@media @landscape { @media @landscape {
-webkit-flex: 3 3 30%; -webkit-flex: 1 1;
-moz-flex: 3 3 30%; -moz-flex: 1 1;
-ms-flex: 3 3 30%; -ms-flex: 1 1;
flex: 3 3 30%; flex: 1 1;
height: 100%; height: 100%;
border-top: 0px; border-top: 0px;
border-right: 0px; border-right: 0px;
border-top-left-radius: 0px; border-top-left-radius: 0px;
overflow: hidden;
} }
order: 3; order: 3;
} }
@ -136,23 +165,19 @@
} }
} }
.gotUnreadMail {
background: extract(@yellow, 2) !important;
}
#newMessageInput { #newMessageInput {
display: block; display: block;
float: left; float: left;
width: 75%; width: 75%;
resize: none; resize: none;
padding-top: 5px; padding-top: 0px;
padding-bottom: 0px; padding-bottom: 0px;
border:1px solid extract(@lightGrey, 3); border:1px solid extract(@lightGrey, 3);
margin: 0px; margin: 0px;
@media @landscape { @media @landscape {
height: 40px; /* same height as send button */ height: 100%; /* same height as send button */
} }
@media @mobile-portrait { @media @mobile-portrait {
font-size: 4vw; font-size: 4vw;
@ -173,22 +198,24 @@
position: relative; position: relative;
background: extract(@white, 1); background: extract(@white, 1);
padding: 0; padding: 0;
border-top: 1px solid #E5E5E5; border-top: 1px solid extract(@lightGrey, 3);
} }
#sendMessageButton { #sendMessageButton {
width: 20%; /* 75% for the message input, 5% margin between the two */ width: 20%; /* 75% for the message input, 5% margin between the two */
color: extract(@black, 1); color: extract(@black, 1);
background-color: extract(@white, 1); background-color: #3896D3;
font-weight: bold; font-weight: bold;
height: 50px; height: 50px;
margin: 0px; margin: 0px;
border: 1px solid extract(@lightGrey, 3); border: 1px solid extract(@lightGrey, 3);
@media @desktop-portrait { @media @desktop-portrait {
width: 20%; /* 75% for the message input, 5% margin between the two */ width: 20%; /* 75% for the message input, 5% margin between the two */
font-size: 30px; font-size: 30px;
height: 60px; height: 60px;
&:hover {
background: #3A82D4;
}
} }
@media @mobile-portrait { @media @mobile-portrait {
width: 15vw; width: 15vw;
@ -203,6 +230,11 @@
@media @landscape { @media @landscape {
height: 50px; height: 50px;
padding: 0px; padding: 0px;
position: absolute;
bottom: 10px;
&:hover {
background: #3A82D4;
}
} }
} }
@ -215,3 +247,16 @@
min-width: 140px; min-width: 140px;
} }
} }
.button-group {
@media @landscape {
height: 100%;
}
}
#chatInput {
@media @landscape {
height: 100%;
padding-bottom: 10px;
}
}

View File

@ -33,9 +33,42 @@ body {
margin-left:5px margin-left:5px
} }
@media @mobile-portrait {
.sl-toggled-on .unreadChat {
display: none;
}
}
.navbarButton {
color: #469DCF;
.unreadChat {
position: absolute;
z-index: 1;
background:red;
border-radius:80%;
@media @desktop-portrait, @landscape {
top: 20%;
left: 65%;
width:19%;
height:20%;
}
@media @mobile-portrait {
top: 14%;
left: 70%;
width:25%;
height:25%;
}
}
}
.myNavbar { .myNavbar {
border-bottom: 0px; border-bottom: 1px;
&.gradientBar { @media @desktop-portrait, @landscape {
background-color: white;
border-bottom: 1px solid extract(@lightGrey, 1);
}
@media @mobile-portrait-with-keyboard, @mobile-portrait {
.linear-gradient(rgb(72,76,85), rgb(65,68,77)); .linear-gradient(rgb(72,76,85), rgb(65,68,77));
} }
.btn { .btn {
@ -45,13 +78,15 @@ body {
top: 0 !important; top: 0 !important;
padding-left: 1% !important; padding-left: 1% !important;
padding-right: 1% !important; padding-right: 1% !important;
background-color: white;
border-bottom: 1px solid extract(@lightGrey, 1);
} }
@media @mobile-portrait-with-keyboard, @mobile-portrait { @media @mobile-portrait-with-keyboard, @mobile-portrait {
height: 100px !important; height: 100px !important;
width: 10%; width: 10%;
min-width: 60px; min-width: 60px;
.linear-gradient(rgb(72,76,85), rgb(65,68,77));
} }
.linear-gradient(rgb(72,76,85), rgb(65,68,77));
.push-menu-icon { .push-menu-icon {
.icon-bar { .icon-bar {
margin-left: auto; margin-left: auto;
@ -71,10 +106,9 @@ body {
&.toggleUserlistButton, &.toggleMenuButton { &.toggleUserlistButton, &.toggleMenuButton {
background: transparent; background: transparent;
} }
span { background-color: white; } span { background-color: #469DCF; }
} }
@media @landscape { @media @landscape {
min-width: 768px;
height: 50px; height: 50px;
} }
@media @mobile-portrait-with-keyboard, @mobile-portrait { @media @mobile-portrait-with-keyboard, @mobile-portrait {
@ -87,28 +121,35 @@ body {
} }
.navbarTitle { .navbarTitle {
color: extract(@white, 1);
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@media @landscape { position: absolute;
font-size: 16px;
width: 30% !important;
}
@media @mobile-portrait-with-keyboard, @mobile-portrait { @media @mobile-portrait-with-keyboard, @mobile-portrait {
font-size: 30px; color: extract(@white, 1);
padding-top: 30px; font-size: 35px;
padding-left: 5px;
height: 72px; height: 72px;
width: 70%; max-width: 70%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
height: 110px;
padding-top: 55px; // half the height
padding-left: 30px;
} }
@media @desktop-portrait { @media @desktop-portrait {
max-width: calc(~'100% - 155px'); // navbar width minus the space for 3 buttons
}
@media @landscape {
max-width: calc(~'100% - 125px'); // navbar width minus the space for 4 buttons
}
@media @landscape, @desktop-portrait {
color: extract(@darkGrey, 1);
font-size: 16px; font-size: 16px;
width: calc(~'100% - 102.4px'); height: 50px;
padding-top: 25px; // half the height
padding-left: 15px;
} }
} }
} }
@ -141,30 +182,32 @@ body {
} }
.meetingTitle { .meetingTitle {
text-align: center;
font-weight: bold; font-weight: bold;
line-height: 2em; line-height: 2em;
margin: 0; margin: 0;
@media @mobile-portrait, @mobile-portrait-with-keyboard, @desktop-portrait { @media @mobile-portrait, @mobile-portrait-with-keyboard {
.linear-gradient(rgb(72,76,85), rgb(65,68,77)); .linear-gradient(rgb(72,76,85), rgb(65,68,77));
padding-left: 140px;
padding-top: 20px; padding-top: 20px;
color: white; color: white;
font-size: 4vw;
height: 110px;
}
@media @desktop-portrait {
.linear-gradient(rgb(72,76,85), rgb(65,68,77));
padding-top: 10px;
color: white;
} }
@media @landscape { @media @landscape {
border-bottom: 1px solid extract(@lightGrey, 1); border-bottom: 1px solid extract(@lightGrey, 1);
color: extract(@darkGrey, 1); color: extract(@darkGrey, 1);
padding-bottom: 5px; padding-bottom: 5px;
padding-left: 10px;
padding-top: 5px; padding-top: 5px;
} }
@media @desktop-portrait, @landscape { @media @desktop-portrait, @landscape {
font-size: 18px; font-size: 18px;
height: 50px; height: 50px;
} }
@media @mobile-portrait, @mobile-portrait-with-keyboard {
font-size: 4vw;
height: 110px;
}
} }
#container { #container {
@ -272,10 +315,10 @@ body {
#main { #main {
@media @landscape { @media @landscape {
-webkit-flex: 4 4 80%; -webkit-flex: 1 1;
-moz-flex: 4 4 80%; -moz-flex: 1 1;
-ms-flex: 4 4 80%; -ms-flex: 1 1;
flex: 4 4 80%; flex: 1 1;
height: 100%; height: 100%;
} }
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait { @media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
@ -303,6 +346,12 @@ body {
.signOutIcon { .signOutIcon {
@media @landscape { @media @landscape {
&:hover {
color: #2A5E7C;
}
&:focus {
color: #2A5E7C;
}
height: 28px; height: 28px;
width: 75px; width: 75px;
margin-left: 10px; margin-left: 10px;
@ -333,8 +382,18 @@ body {
z-index: 1001; z-index: 1001;
} }
.toggleUserlistButton {
outline: none;
}
.settingsIcon { .settingsIcon {
@media @landscape { @media @landscape {
&:hover {
color: #2A5E7C;
}
&:focus {
color: #2A5E7C;
}
width: 57px; width: 57px;
margin-right: 5px; margin-right: 5px;
} }
@ -459,7 +518,7 @@ body {
z-index: 1000; z-index: 1000;
position: fixed; position: fixed;
left: -400px; left: -400px;
width: 400px; width: 400px !important; // overrides any width value set manually in landscape
&.sl-left-drawer-out { &.sl-left-drawer-out {
left: 0px; left: 0px;
} }
@ -478,10 +537,7 @@ body {
.sl-left-drawer { .sl-left-drawer {
height: 100%; height: 100%;
@media @landscape { @media @landscape {
-webkit-flex: 1 1 20%; width: 300px;
-moz-flex: 1 1 20%;
-ms-flex: 1 1 20%;
flex: 1 1 20%;
} }
} }
@ -514,7 +570,7 @@ body {
@media @landscape { @media @landscape {
display: none; display: none;
} }
&:not(.darken) { &:not(.darken):not(.animatedShield) {
display: none; display: none;
} }
} }
@ -532,3 +588,28 @@ body {
opacity: 0.5; opacity: 0.5;
} }
} }
.ui-resizable-handle {
@media @mobile-portrait, @desktop-portrait, @mobile-portrait-with-keyboard {
display: none; // hides the sizing handle everywhere except the landscape mode
}
}
// same as Sled's left drawer except for animations
.left-drawer {
z-index: 1000;
position: fixed;
width: 60vw;
left: -60vw;
height: 100%;
}
// same as Sled's right drawer except for animations
.right-drawer {
z-index: 1000;
position: fixed;
transform: translateX(100vw);
height: 100%;
.linear-gradient(rgb(65,68,77), rgb(58,60,69));
width: 60vw;
}

View File

@ -1,26 +1,48 @@
@import "variables"; @import "variables";
@import "mixins"; @import "mixins";
.usericon { .statusIcon {
font-size: 16px; font-size: 18px;
} }
#usericons { #usericons {
text-align: right; text-align: right;
color: white;
span i {
@media @landscape, @desktop-portrait {
margin-right: 10px;
font-size: 20px;
}
}
}
.unreadChatNumber {
@media @landscape, @desktop-portrait{
float: left;
margin-top: 5px;
width: 28px;
height: 18px;
border-radius: 41%;
font-size: 12px;
color: #fff;
text-align: center;
background: #3896D3;
}
} }
.usernameEntry { .usernameEntry {
cursor: pointer;
line-height: 1.1;
float:left; float:left;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
width: 60%; width: calc(~'100% - 130px');
@media @landscape { @media @landscape {
height: 20px; height: 27px;
font-size: 4.5mm; font-size: 4.5mm;
} }
@media @desktop-portrait { @media @desktop-portrait {
height: 25px; height: 27px;
font-size: 4.5mm; font-size: 4.5mm;
} }
@media @mobile-portrait, @mobile-portrait-with-keyboard { @media @mobile-portrait, @mobile-portrait-with-keyboard {
@ -34,7 +56,6 @@
border-left: 0px; border-left: 0px;
border-top: 0px; border-top: 0px;
border-top-right-radius: 0px; border-top-right-radius: 0px;
background-color: #FAFAFA;
} }
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait { @media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
width: 100%; width: 100%;
@ -62,15 +83,24 @@
} }
#user-contents { #user-contents {
height: 83vh; /* for the inside scrolling list to utilize as much room as possible, this surrounding window must also take up as much room as possible */ height: calc(~'100% - 50px'); /* user-contents = user-contens - meetingTitle */
@media @landscape, @desktop-portrait{
background-color: #34495E;
}
.userlist { .userlist {
height: calc(~'100% - 29px'); /* height of user contents - user list */ max-height: 100% !important;
height: 100%;
#content:hover {
@media @landscape, @desktop-portrait {
background-color: #2C4155;
}
}
#content { #content {
span:hover { span:hover {
/*background-color: #EEEEEE;*/ @media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
background: transparent; background: transparent;
}
} }
margin-top: 10px; margin-top: 10px;
&:first-child { margin-top: 0px; } &:first-child { margin-top: 0px; }
@ -103,17 +133,13 @@
font-weight: bold; font-weight: bold;
} }
.closeUserlistIcon { .toggleUserlistButton, .toggleMenuButton {
float: right; span {
margin-right: 5px; @media @landscape, @desktop-portrait {
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait { width: 26px;
display: none; margin-left: auto;
} margin-right: auto;
.closeSettings { height: 8%;
@media @landscape {
color: #aaa;
font-style: normal;
font-size: 30px;
} }
} }
} }
@ -128,3 +154,18 @@
.muteIcon .ion-ios-mic-off:hover:before { .muteIcon .ion-ios-mic-off:hover:before {
content: "\f461"; content: "\f461";
} }
.userName {
color: #E6E6E6;
line-height: 1.5;
}
.status {
float: left;
width: 15px;
min-width: 15px;
height: 27px;
margin-right: 10px;
font-size: 18px;
color: #FFFFFF;
}

View File

@ -7,19 +7,15 @@
border-top: 0px; border-top: 0px;
border-left: 0px; border-left: 0px;
border-right: 0px; border-right: 0px;
-webkit-order: 2; border-top-left-radius: 0;
order: 2; border-top-right-radius: 0;
-webkit-flex: 7 7 70%; width: 70%;
-moz-flex: 7 7 70%;
-ms-flex: 7 7 70%;
flex: 7 7 70%;
min-width: 50%;
height: 100%; height: 100%;
} }
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait { @media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
-webkit-order: 1; -webkit-order: 1;
order: 1; order: 1;
width: 100%; width: 100% !important; // overrides any width value set manually in landscape
} }
&:-webkit-full-screen { &:-webkit-full-screen {
width: 100%; width: 100%;
@ -126,6 +122,7 @@
} }
.switchSlideButton { .switchSlideButton {
outline: none;
width: 50px; width: 50px;
height: 100%; height: 100%;
margin-bottom: 0; margin-bottom: 0;
@ -200,11 +197,12 @@
margin-bottom: 0; margin-bottom: 0;
padding: 0; padding: 0;
border-radius: 50%; border-radius: 50%;
background: rgba(0, 0, 0, 0.2); background: #3896D3;
&:hover { &:hover {
background: rgba(0, 0, 0, 0.4); background: #3A82D4;
} }
&:focus { &:focus {
background: #3896D3;
outline:0; outline:0;
} }
i { i {

View File

@ -22,13 +22,15 @@
else else
chatMessage.message?.from_userid chatMessage.message?.from_userid
Tracker.autorun (comp) -> Tracker.autorun (comp) ->
tabsTime = getInSession('tabsRenderedTime') tabsTime = getInSession('userListRenderedTime')
if tabsTime? and chatMessage.message.from_userid isnt "SYSTEM_MESSAGE" and chatMessage.message.from_time - tabsTime > 0 if tabsTime? and chatMessage.message.from_userid isnt "SYSTEM_MESSAGE" and chatMessage.message.from_time - tabsTime > 0
populateChatTabs(chatMessage) # check if we need to open a new tab populateNotifications(chatMessage) # check if we need to show a new notification
destinationTab = findDestinationTab() destinationTab = findDestinationTab()
if destinationTab isnt getInSession "inChatWith" if destinationTab isnt getInSession "inChatWith"
setInSession 'chatTabs', getInSession('chatTabs').map((tab) -> setInSession 'chats', getInSession('chats').map((tab) ->
tab.gotMail = true if tab.userId is destinationTab if tab.userId is destinationTab
tab.gotMail = true
tab.number++
tab tab
) )
comp.stop() comp.stop()
@ -120,22 +122,51 @@ Template.chatbar.helpers
return Meteor.Users.findOne({userId: getInSession('inChatWith')})? return Meteor.Users.findOne({userId: getInSession('inChatWith')})?
# When chatbar gets rendered, launch the auto-check for unread chat # When chatbar gets rendered, launch the auto-check for unread chat
Template.chatbar.rendered = -> detectUnreadChat() Template.chatbar.rendered = ->
detectUnreadChat()
# When "< Public" is clicked, go to public chat # When "< Public" is clicked, go to public chat
Template.chatbar.events Template.chatbar.events
'click .toPublic': (event) -> 'click .toPublic': (event) ->
setInSession 'inChatWith', 'PUBLIC_CHAT' setInSession 'inChatWith', 'PUBLIC_CHAT'
setInSession 'chats', getInSession('chats').map((chat) ->
if chat.userId is "PUBLIC_CHAT"
chat.gotMail = false
chat.number = 0
chat
)
Template.privateChatTab.rendered = ->
if isLandscape() or isPortrait()
$("#newMessageInput").focus()
# When message gets rendered, scroll to the bottom # When message gets rendered, scroll to the bottom
Template.message.rendered = -> Template.message.rendered = ->
$('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight) $('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight)
false false
Template.chatInput.rendered = ->
$('.panel-footer').resizable
handles: 'n'
minHeight: 70
resize: (event, ui) ->
if $('.panel-footer').css('top') is '0px'
$('.panel-footer').height(70) # prevents the element from shrinking vertically for 1-2 px
else
$('.panel-footer').css('top', parseInt($('.panel-footer').css('top')) + 1 + 'px')
$('#chatbody').height($('#chat').height() - $('.panel-footer').height() - 45)
$('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight)
start: (event, ui) ->
$('#newMessageInput').css('overflow', '')
$('.panel-footer').resizable('option', 'maxHeight', Math.max($('.panel-footer').height(), $('#chat').height() / 2))
stop: (event, ui) ->
setInSession 'chatInputMinHeight', $('.panel-footer').height() + 1
Template.chatInput.events Template.chatInput.events
'click #sendMessageButton': (event) -> 'click #sendMessageButton': (event) ->
$('#sendMessageButton').blur() $('#sendMessageButton').blur()
sendMessage() sendMessage()
adjustChatInputHeight()
'keypress #newMessageInput': (event) -> # user pressed a button inside the chatbox 'keypress #newMessageInput': (event) -> # user pressed a button inside the chatbox
key = (if event.charCode then event.charCode else (if event.keyCode then event.keyCode else 0)) key = (if event.charCode then event.charCode else (if event.keyCode then event.keyCode else 0))
@ -152,6 +183,11 @@ Template.chatInput.events
$('#newMessageInput').val("") $('#newMessageInput').val("")
return false return false
Template.chatInputControls.rendered = ->
$('#newMessageInput').on('keydown paste cut', () -> setTimeout(() ->
adjustChatInputHeight()
, 0))
Template.message.helpers Template.message.helpers
sanitizeAndFormat: (str) -> sanitizeAndFormat: (str) ->
if typeof str is 'string' if typeof str is 'string'

View File

@ -2,13 +2,7 @@
<div id="{{id}}" {{visibility name}} class="component"> <div id="{{id}}" {{visibility name}} class="component">
<div class="chatBodyContainer"> <div class="chatBodyContainer">
{{#if inPrivateChat}} {{#if inPrivateChat}}
<div class="privateChatTab"> {{> privateChatTab}}
{{> makeButton id="close" btn_class="secondary tiny round toPublic " i_class="ion-ios-arrow-left" rel="tooltip"
data_placement="bottom" title="Back to public" label="Public"}}
<div class="privateChatName">
{{privateChatName}}
</div>
</div>
{{/if}} {{/if}}
<div id="chatbody"> <div id="chatbody">
<ul class="chat" {{messageFontSize}}> <ul class="chat" {{messageFontSize}}>
@ -27,6 +21,16 @@
</div> </div>
</template> </template>
<template name="privateChatTab">
<div class="privateChatTab">
{{> makeButton id="close" btn_class="secondary tiny toPublic " i_class="ion-ios-arrow-left" rel="tooltip"
data_placement="bottom" title="Back to public" label="Public" notification="PUBLIC_CHAT"}}
<div class="privateChatName">
{{privateChatName}}
</div>
</div>
</template>
<template name="chatInput"> <template name="chatInput">
<div id="chatInput" class="chat-input-wrapper"> <div id="chatInput" class="chat-input-wrapper">
{{#if inPrivateChat}} {{#if inPrivateChat}}

View File

@ -46,12 +46,12 @@
<div class="bar bottomBar"> <div class="bar bottomBar">
<a href="#" class="closeSettings close-reveal-modal"><u>Cancel</u></a> <a href="#" class="closeSettings close-reveal-modal"><u>Cancel</u></a>
{{> makeButton id="saveSettings" btn_class="settingsButton" rel="tooltip" title="Save Changes" text="Save"}} {{> makeButton id="saveSettings" btn_class="settingsButton" rel="tooltip" title="Save Changes" label="Save"}}
</div> </div>
</template> </template>
<template name="logoutModal"> <template name="logoutModal">
<p>Are you sure you want to logout?</p> <p>Are you sure you want to logout?</p>
{{> makeButton id="yes" btn_class="logoutButton" rel="tooltip" title="Logout" text="Yes"}} {{> makeButton id="yes" btn_class="logoutButton" rel="tooltip" title="Logout" label="Yes"}}
{{> makeButton id="no" btn_class="logoutButton" rel="tooltip" title="Logout" text="No"}} {{> makeButton id="no" btn_class="logoutButton" rel="tooltip" title="Logout" label="No"}}
</template> </template>

View File

@ -0,0 +1,33 @@
Template.makeButton.helpers
hasGotUnreadMail: (userId) ->
chats = getInSession('chats')
if chats isnt undefined
if userId is "all_chats"
for tabs in chats
if tabs.gotMail is true
return true
else if userId is "PUBLIC_CHAT"
for tabs in chats
if tabs.userId is userId and tabs.gotMail is true
return true
return false
getNumberOfUnreadMessages: (userId) ->
if userId is "all_chats"
return
else
chats = getInSession('chats')
if chats isnt undefined
for chat in chats
if chat.userId is userId and chat.gotMail
if chat.number > 9
return "9+"
else
return chat.number
return
getNotificationClass: (userId) ->
if userId is "all_chats"
return "unreadChat"
if userId is "PUBLIC_CHAT"
return "unreadChatNumber"

View File

@ -1,11 +1,18 @@
<template name="makeButton"> <template name="makeButton">
<button type="submit" id="{{id}}" class="btn {{btn_class}}" {{isDisabled}} rel="{{rel}}" data-placement="{{data_placement}}" title="{{title}}" style="{{style}}"> <button type="submit" id="{{id}}" class="btn {{btn_class}}" {{isDisabled}} rel="{{rel}}" data-placement="{{data_placement}}" title="{{title}}" style="{{style}}">
{{#if text}} {{#if notification}}
<span>{{text}}</span> {{#if hasGotUnreadMail notification }}
{{else}} <div class="{{getNotificationClass notification}}">{{getNumberOfUnreadMessages notification}}</div>
{{#if i_class}}
<i class="{{i_class}}"></i><span>{{label}}</span>
{{/if}}
{{/if}} {{/if}}
</button> {{/if}}
{{#if i_class}}
<i class="{{i_class}}"></i>
{{/if}}
{{#if label}}
<span>{{label}}</span>
{{/if}}
{{#if span}}
<span></span>
{{/if}}
</button>
</template> </template>

View File

@ -26,5 +26,49 @@ Template.displayUserIcons.helpers
Template.usernameEntry.events Template.usernameEntry.events
'click .usernameEntry': (event) -> 'click .usernameEntry': (event) ->
userIdSelected = @.userId userIdSelected = @.userId
unless userIdSelected is null or userIdSelected is BBB.getCurrentUser()?.userId unless userIdSelected is null
setInSession "inChatWith", userIdSelected if userIdSelected is BBB.getCurrentUser()?.userId
setInSession "inChatWith", "PUBLIC_CHAT"
else
setInSession "inChatWith", userIdSelected
if isLandscape()
$("#newMessageInput").focus()
if isPortrait() or isPortraitMobile()
toggleUsersList()
$("#newMessageInput").focus()
'click .gotUnreadMail': (event) ->
_this = @
currentId = getInSession('userId')
if currentId isnt undefined and currentId is _this.userId
_id = "PUBLIC_CHAT"
else
_id = _this.userId
chats = getInSession('chats')
if chats isnt undefined
for chat in chats
if chat.userId is _id
chat.gotMail = false
chat.number = 0
break
setInSession 'chats', chats
Template.usernameEntry.helpers
hasGotUnreadMailClass: (userId) ->
chats = getInSession('chats')
if chats isnt undefined
for chat in chats
if chat.userId is userId and chat.gotMail
return true
return false
getNumberOfUnreadMessages: (userId) ->
chats = getInSession('chats')
if chats isnt undefined
for chat in chats
if chat.userId is userId and chat.gotMail
if chat.number > 9
return "9+"
else
return chat.number
return

View File

@ -54,38 +54,50 @@
<i class="icon fi-lock usericon"></i> <i class="icon fi-lock usericon"></i>
</span> </span>
{{/if}} {{/if}}
{{#if user.presenter}}
<span rel="tooltip" data-placement="bottom" title="{{user.name}} is the presenter">
<i class="icon fi-projection-screen usericon"></i>
</span>
{{else}}
{{#if equals user.role "MODERATOR"}}
<span rel="tooltip" data-placement="bottom" title="{{user.name}} is a moderator">
<i class="icon fi-torso usericon"></i>
</span>
{{/if}}
{{/if}}
{{#if user.raise_hand}}
{{#if isCurrentUser userId}}
<span class="ion-android-hand usericon" rel="tooltip" data-placement="bottom" title="Lower your hand"></span>
{{else}}
<span class="ion-android-hand usericon" rel="tooltip" data-placement="bottom" title="{{user.name}} has raised their hand"></span>
{{/if}}
{{/if}}
</template> </template>
<template name="usernameEntry"> <template name="usernameEntry">
{{#if isCurrentUser userId}} <div class="status">
<span class="userCurrent usernameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}} (you)"> {{#if user.raise_hand}}
{{user.name}} {{#if user.presenter}} (presenter) {{/if}} (you) {{#if isCurrentUser userId}}
</span> <span rel="tooltip" data-placement="bottom" title="Lower your hand">
{{else}} <i class="icon ion-android-hand statusIcon"></i>
<span class="usernameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}}"> </span>
{{user.name}} {{#if user.presenter}} (presenter) {{/if}} {{else}}
</span> <span rel="tooltip" data-placement="bottom" title="{{user.name}} has raised their hand">
{{/if}} <i class="icon ion-android-hand statusIcon"></i>
</span>
{{/if}}
{{else}}
{{#if user.presenter}}
<span rel="tooltip" data-placement="bottom" title="{{user.name}} is the presenter">
<i class="icon fi-projection-screen statusIcon"></i>
</span>
{{else}}
{{#if equals user.role "MODERATOR"}}
<span rel="tooltip" data-placement="bottom" title="{{user.name}} is a moderator">
<i class="icon fi-torso statusIcon"></i>
</span>
{{/if}}
{{/if}}
{{/if}}
</div>
{{#if isCurrentUser userId}}
<span class="userCurrent usernameEntry {{#if hasGotUnreadMailClass 'PUBLIC_CHAT'}}gotUnreadMail{{/if}}" rel="tooltip" data-placement="bottom" title="{{user.name}} (you)">
<span class="userName">{{user.name}} {{#if user.presenter}} (presenter) {{/if}} (you)</span>
</span>
{{#if hasGotUnreadMailClass 'PUBLIC_CHAT' }}
<div class="unreadChatNumber">{{getNumberOfUnreadMessages 'PUBLIC_CHAT'}}</div>
{{/if}}
{{else}}
<span class="usernameEntry {{#if hasGotUnreadMailClass user.userid}}gotUnreadMail{{/if}}" rel="tooltip" data-placement="bottom" title="{{user.name}}">
<span class="userName"> {{user.name}} {{#if user.presenter}} (presenter) {{/if}}</span>
</span>
{{#if hasGotUnreadMailClass user.userid }}
<div class="unreadChatNumber">{{getNumberOfUnreadMessages user.userid}}</div>
{{/if}}
{{/if}}
</template> </template>
<template name="userItem"> <template name="userItem">

View File

@ -5,6 +5,14 @@ Template.usersList.helpers
return "Users: #{numberUsers}" return "Users: #{numberUsers}"
# do not display the label if there are just a few users # do not display the label if there are just a few users
Template.usersList.events Template.usersList.rendered = ->
"click .closeUserlistIcon": (event, template) -> $('.sl-left-drawer').resizable
toggleUsersList() handles: 'e'
maxWidth: 600
minWidth: 200
resize: () ->
adjustChatInputHeight()
Tracker.autorun (comp) ->
setInSession 'userListRenderedTime', TimeSync.serverTime()
if getInSession('userListRenderedTime') isnt undefined
comp.stop()

View File

@ -2,11 +2,6 @@
<div id="{{id}}" {{visibility name}} class="component {{class}}"> <div id="{{id}}" {{visibility name}} class="component {{class}}">
<h3 class="meetingTitle"> <h3 class="meetingTitle">
{{getMeetingName}} {{getMeetingName}}
<span class="closeUserlistIcon">
<a href="#">
<i class="closeSettings close-reveal-modal">&#215;</i>
</a>
</span>
</h3> </h3>
<div id="user-contents"> <div id="user-contents">
<div class="userlist ScrollableWindowY"> <div class="userlist ScrollableWindowY">

View File

@ -6,7 +6,8 @@ Template.slide.rendered = ->
setInSession 'slideOriginalHeight', this.height setInSession 'slideOriginalHeight', this.height
$(window).resize( -> $(window).resize( ->
# redraw the whiteboard to adapt to the resized window # redraw the whiteboard to adapt to the resized window
redrawWhiteboard() if !$('.panel-footer').hasClass('ui-resizable-resizing') # not in the middle of resizing the message input
redrawWhiteboard()
) )
if currentSlide?.slide?.png_uri? if currentSlide?.slide?.png_uri?
createWhiteboardPaper (wpm) -> createWhiteboardPaper (wpm) ->

View File

@ -46,3 +46,18 @@ Template.whiteboard.events
'click .lowerHand': (event) -> 'click .lowerHand': (event) ->
BBB.lowerHand(BBB.getMeetingId(), getInSession('userId'), getInSession('userId'), getInSession('authToken')) BBB.lowerHand(BBB.getMeetingId(), getInSession('userId'), getInSession('userId'), getInSession('authToken'))
Template.whiteboard.rendered = ->
$('#whiteboard').resizable
handles: 'e'
minWidth: 150
resize: () ->
adjustChatInputHeight()
start: () ->
if $('#chat').width() / $('#panels').width() > 0.2 # chat shrinking can't make it smaller than one fifth of the whiteboard-chat area
$('#whiteboard').resizable('option', 'maxWidth', $('#panels').width() - 200) # gives the chat enough space (200px)
else
$('#whiteboard').resizable('option', 'maxWidth', $('#whiteboard').width())
stop: () ->
$('#whiteboard').css('width', 100 * $('#whiteboard').width() / $('#panels').width() + '%') # transforms width to %
$('#whiteboard').resizable('option', 'maxWidth', null)

View File

@ -1,5 +1,5 @@
<template name="whiteboard"> <template name="whiteboard">
<div id="{{id}}" {{visibility name}} class="component gradientBar"> <div id="{{id}}" {{visibility name}} class="component">
{{#each getCurrentSlide}} {{#each getCurrentSlide}}
{{> slide}} {{> slide}}
{{/each}} {{/each}}

View File

@ -103,8 +103,12 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
r.setPlaybackFormat(rec.playback.format.text()); r.setPlaybackFormat(rec.playback.format.text());
r.setPlaybackLink(rec.playback.link.text()); r.setPlaybackLink(rec.playback.link.text());
r.setPlaybackDuration(rec.playback.duration.text()); r.setPlaybackDuration(rec.playback.duration.text());
r.setPlaybackExtensions(rec.playback.extension.children());
/*
Commenting this out to see if this is causing memory to hang around resulting in
OOM in tomcat7 (ralam july 23, 2015)
r.setPlaybackExtensions(rec.playback.extension.children());
*/
Map<String, String> meta = new HashMap<String, String>(); Map<String, String> meta = new HashMap<String, String>();
rec.meta.children().each { anode -> rec.meta.children().each { anode ->
log.debug("metadata: "+anode.name()+" "+anode.text()) log.debug("metadata: "+anode.name()+" "+anode.text())

View File

@ -201,7 +201,9 @@ public class DeskShareApplet extends JApplet implements ClientListener {
} }
public void onClientStop(ExitCode reason) { public void onClientStop(ExitCode reason) {
// determine if client is disconnected _PTS_272_ client.stop();
/*
if ( ExitCode.CONNECTION_TO_DESKSHARE_SERVER_DROPPED == reason ){ if ( ExitCode.CONNECTION_TO_DESKSHARE_SERVER_DROPPED == reason ){
JFrame pframe = new JFrame("Desktop Sharing Disconneted"); JFrame pframe = new JFrame("Desktop Sharing Disconneted");
if ( null != pframe ){ if ( null != pframe ){
@ -215,6 +217,7 @@ public class DeskShareApplet extends JApplet implements ClientListener {
}else{ }else{
client.stop(); client.stop();
} }
*/
} }
} }