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 = {
val answers = new Array[Answer](2)
answers(0) = new Answer(0, "N", Some("No"))
answers(1) = new Answer(1, "Y", Some("Yes"))
answers(0) = new Answer(0, "No", Some("No"))
answers(1) = new Answer(1, "Yes", Some("Yes"))
new Question(0, PollType.YesNoPollType, false, None, answers)
}
@ -27,8 +27,8 @@ object PollFactory {
private def processTrueFalsePollType(qType: String): Question = {
val answers = new Array[Answer](2)
answers(0) = new Answer(0, "F", Some("False"))
answers(1) = new Answer(1, "T", Some("True"))
answers(0) = new Answer(0, "False", Some("False"))
answers(1) = new Answer(1, "True", Some("True"))
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]()
shape += "num_respondents" -> new Integer(result.numRespondents)
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]];
result.answers.foreach(ans => {
@ -81,12 +84,13 @@ trait PollApp {
val gson = new Gson()
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]()
display.add(21.845575)
display.add(23.145401)
display.add(46.516006)
display.add(61.42433)
display.add(66.0)
display.add(60.0)
display.add(34.0)
display.add(40.0)
shape += "points" -> display
shape.toMap

View File

@ -228,6 +228,7 @@ trait UsersApp {
}
usersModel.removeUser(msg.userId)
usersModel.removeRegUser(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))
@ -249,11 +250,14 @@ trait UsersApp {
def handleUserunshareWebcam(msg: UserUnshareWebcam) {
usersModel.getUser(msg.userId) foreach { user =>
val streams = user.webcamStreams - msg.stream
val uvo = user.copy(hasStream = (!streams.isEmpty), webcamStreams = streams)
usersModel.addUser(uvo)
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))
val streamName = user.webcamStreams find (w => w == msg.stream) foreach { streamName =>
val streams = user.webcamStreams - streamName
val uvo = user.copy(hasStream = (!streams.isEmpty), webcamStreams = streams)
usersModel.addUser(uvo)
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 = {
val regUser = usersModel.getRegisteredUserWithToken(msg.authToken)
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,
ru.role, raiseHand = false, presenter = false,
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()))
// Become presenter if the only moderator
if (usersModel.numModerators == 1) {
if ((usersModel.numModerators == 1) || (usersModel.noPresenter())) {
if (ru.role == Role.MODERATOR) {
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
* 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()
moderator.foreach { mod =>
log.info("Presenter left meeting: mid=[" + mProps.meetingID + "] uid=[" + u.userID + "]. Making user=[" + mod.userID + "] presenter.")
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.")
}
case None => {
// 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.
val webUserId = usersModel.generateWebUserId
val webUserId = if (msg.userId != msg.callerIdName) {
msg.userId
} else {
// 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,
true, false, false, false)
true, false, msg.muted, msg.talking)
val sessionId = "PHONE-" + webUserId;

View File

@ -96,6 +96,10 @@ class UsersModel {
uservos.values find (u => u.role == MODERATOR)
}
def noPresenter(): Boolean = {
!getCurrentPresenter().isDefined
}
def getCurrentPresenter(): Option[UserVO] = {
uservos.values find (u => u.presenter == true)
}
@ -127,4 +131,17 @@ class UsersModel {
def getViewers(): Array[UserVO] = {
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 MessagePublisher red5InGW;
private final UserConnectionMapper userConnections = new UserConnectionMapper();
private final String APP = "BBB";
private final String CONN = "RED5-";
@ -166,6 +168,8 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
log.info("User joining bbb-apps: data={}", logStr);
userConnections.addUserConnection(userId, connId);
return super.roomConnect(connection, params);
}
@ -214,10 +218,15 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("User leaving bbb-apps: data={}", logStr);
red5InGW.userLeft(bbbSession.getRoom(), getBbbSession().getInternalUserID(), sessionId);
boolean removeUser = userConnections.userDisconnected(userId, connId);
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);
}

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;
}
.pollResondersLabelStyle {
color: #444444;
fontFamily: Arial;
fontSize: 12;
}
Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .cameraDisplaySettingsWindowProfileComboStyle, .cameraDisplaySettingsWindowCameraSelector, .languageSelectorStyle, .testJavaLinkButtonStyle, .recordButtonStyleNormal, .recordButtonStyleStart, .recordButtonStyleStop, .micSettingsWindowHelpButtonStyle {
textIndent: 0;
paddingLeft: 10;
@ -408,7 +414,7 @@ DataGrid {
icon: Embed('assets/images/webcam-private-chat.png');
}
.presentationFileUploadWindowStyle {
TitleWindow {
borderColor: #b9babc;
borderAlpha: 1;
borderThicknessLeft: 10;
@ -419,11 +425,8 @@ DataGrid {
cornerRadius: 5;
headerHeight: 20;
backgroundAlpha: 1;
headerColors: #b9babc, #b9babc;
footerColors: #b9babc, #b9babc;
backgroundColor: #EFEFEF;
dropShadowEnabled: true;
titleStyleName: "presentationFileUploadWindowTitleStyle";
}
.presentationFileUploadWindowTitleStyle, .presentationUploadTitleStyle {
@ -511,42 +514,6 @@ DataGrid {
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 {
fontFamily: Arial;
fontSize: 20;
@ -866,24 +833,6 @@ Alert {
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 {
fontFamily: Arial;
fontSize: 20;
@ -906,6 +855,7 @@ Alert {
successImage: Embed(source='assets/images/status_success.png');
warningImage: Embed(source='assets/images/status_warning.png');
failImage: Embed(source='assets/images/status_fail.png');
refreshImage: Embed(source='assets/images/status_refresh.png');
}
.warningButtonStyle {
@ -922,3 +872,8 @@ Alert {
fontSize: 12;
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
suggestions are always welcome!
<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.closeBtn.accessibilityName = Close the Chat Window
bbb.chat.chatTabs.accessibleNotice = New messages in this tab.
bbb.chat.chatMessage.systemMessage = System
bbb.publishVideo.changeCameraBtn.labelText = Change Webcam
bbb.publishVideo.changeCameraBtn.toolTip = Open the change webcam dialog box
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.yes = Yes
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.cmpColorPicker.toolTip = Text Color
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.publishButton.label = Publish
bbb.polling.closeButton.label = Close
bbb.polling.answer.Y = Yes
bbb.polling.answer.N = No
bbb.polling.answer.T = True
bbb.polling.answer.F = False
bbb.polling.pollModal.title = Poll
bbb.polling.respondersLabel.novotes = No Users Responded
bbb.polling.respondersLabel.text = {0} Users Responded
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.B = B
bbb.polling.answer.C = C
@ -504,6 +515,8 @@ bbb.polling.answer.D = D
bbb.polling.answer.E = E
bbb.polling.answer.F = F
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.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)
{
var deskshareElement = document.getElementById("deskshare");
if (deskshareElement == null) {
console.log("Starting deskshare applet.");
var div = document.createElement("div");
div.id = "deskshare";
@ -21,15 +24,40 @@ function startApplet(IP, useTLS , roomNumber, fullScreen, useSVC2)
"<param name=\"permissions\" value=\"all-permissions\"/>" +
"</applet>";
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 () {
var div = document.getElementById("deskshare");
if (div.parentNode) {
// Need to set the innerHTML otherwise the applet will restart in IE.
// see https://code.google.com/p/bigbluebutton/issues/detail?id=1776
div.innerHTML = "";
div.parentNode.removeChild(div);
// Do NOT remove the applet tag as it makes Firefox (38.0.5) prompt for
// 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.getClassLogger;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.EventBroadcaster;
import org.bigbluebutton.main.api.ExternalApiCallbacks;
@ -71,13 +72,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
protected function init():void {
setupTooltips();
setupAPI();
EventBroadcaster.getInstance().addEventListener("configLoadedEvent", configLoadedEventHandler);
BBB.initConfigManager();
EventBroadcaster.getInstance().addEventListener("configLoadedEvent", configLoadedEventHandler);
BBB.initConfigManager();
BBB.initVideoProfileManager();
globalModifier = ExternalInterface.call("determineGlobalModifier");
}
private function configLoadedEventHandler(e:Event):void {
LogUtil.initLogging();
LOGGER.debug("***** Config Loaded ****");
mainShell.initOptions(null);
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
}
} 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 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 payload:Object = new Object();
@ -51,4 +62,4 @@ package org.bigbluebutton.main.events {
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="{ConfigManager}" cache="global" />
<ObjectBuilder generator="{ReconnectionManager}" cache="global" />
<!--
Disabling temporarily the stream monitor
-->
@ -69,6 +70,22 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<MethodInvoker generator="{ModulesProxy}" method="startAllModules" />
</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>
@ -76,7 +93,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import mx.events.FlexEvent;
import org.bigbluebutton.core.managers.ConfigManager;
import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.core.services.SkinningService;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ConfigEvent;
import org.bigbluebutton.main.events.LogoutEvent;
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/
*
* 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
* 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 com.asfusion.mate.events.Dispatcher;
import flash.events.AsyncErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
import flash.net.NetConnection;
import flash.net.Responder;
import flash.utils.Timer;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.services.BandwidthMonitor;
import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.InvalidAuthTokenEvent;
import org.bigbluebutton.main.model.ConferenceParameters;
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* 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
* 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 com.asfusion.mate.events.Dispatcher;
import flash.events.AsyncErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
import flash.net.NetConnection;
import flash.net.Responder;
import flash.utils.Timer;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.managers.ReconnectionManager;
import org.bigbluebutton.core.services.BandwidthMonitor;
import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.BBBEvent;
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;
public class NetConnectionDelegate
{
private static const LOGGER:ILogger = getClassLogger(NetConnectionDelegate);
private var _netConnection:NetConnection;
private var connectionId:Number;
private var connected:Boolean = false;
private var _userid:Number = -1;
private var _role:String = "unknown";
private var _applicationURI:String;
private var _conferenceParameters:ConferenceParameters;
// These two are just placeholders. We'll get this from the server later and
// then pass to other modules.
private var _authToken:String = "AUTHORIZED";
private var _room:String;
private var tried_tunneling:Boolean = false;
private var logoutOnUserCommand:Boolean = false;
private var backoff:Number = 2000;
private var dispatcher:Dispatcher;
private var _messageListeners:Array = new Array();
private var authenticated: Boolean = false;
public function NetConnectionDelegate():void
{
dispatcher = new Dispatcher();
_netConnection = new NetConnection();
_netConnection.proxyType = "best";
_netConnection.client = this;
_netConnection.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
_netConnection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, netASyncError );
_netConnection.addEventListener( SecurityErrorEvent.SECURITY_ERROR, netSecurityError );
_netConnection.addEventListener( IOErrorEvent.IO_ERROR, netIOError );
}
public function setUri(uri:String):void {
_applicationURI = uri;
}
public function get connection():NetConnection {
return _netConnection;
}
public function addMessageListener(listener:IMessageListener):void {
_messageListeners.push(listener);
}
public function removeMessageListener(listener:IMessageListener):void {
for (var ob:int=0; ob<_messageListeners.length; ob++) {
if (_messageListeners[ob] == listener) {
_messageListeners.splice (ob,1);
break;
}
}
}
private function notifyListeners(messageName:String, message:Object):void {
if (messageName != null && messageName != "") {
for (var notify:String in _messageListeners) {
_messageListeners[notify].onMessage(messageName, message);
}
} 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()]);
if (!authenticated && (messageName == "validateAuthTokenReply")) {
handleValidateAuthTokenReply(msg)
} else if (messageName == "validateAuthTokenTimedOut") {
handleValidateAuthTokenTimedOut(msg)
} else if (authenticated) {
notifyListeners(messageName, msg);
} else {
LOGGER.debug("Ignoring message=[{0}] as our token hasn't been validated yet.", [messageName]);
}
}
private function validateToken():void {
var message:Object = new Object();
message["userId"] = _conferenceParameters.internalUserID;
message["authToken"] = _conferenceParameters.authToken;
sendMessage(
"validateToken",// Remote function name
// result - On successful result
function(result:Object):void {
LOGGER.debug("validating token for [{0}]", [_conferenceParameters.internalUserID]);
},
// status - On error occurred
function(status:Object):void {
LOGGER.error("Error occurred:");
for (var x:Object in status) {
LOGGER.error(x + " : " + status[x]);
}
},
message
); //_netConnection.call
}
private function handleValidateAuthTokenTimedOut(msg: Object):void {
LOGGER.debug("*** handleValidateAuthTokenTimedOut {0} **** \n", [msg.msg]);
var map:Object = JSON.parse(msg.msg);
var tokenValid: Boolean = map.valid as Boolean;
var userId: String = map.userId as String;
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
JSLog.critical("Validate auth token timed out.", logData);
if (tokenValid) {
authenticated = true;
LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]);
} else {
LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]);
dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
}
}
private function handleValidateAuthTokenReply(msg: Object):void {
LOGGER.debug("*** handleValidateAuthTokenReply {0} **** \n", [msg.msg]);
var map:Object = JSON.parse(msg.msg);
var tokenValid: Boolean = map.valid as Boolean;
var userId: String = map.userId as String;
if (tokenValid) {
authenticated = true;
LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
} else {
LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
}
}
private function sendConnectionSuccessEvent(userid:String):void{
var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS);
e.userid = userid;
dispatcher.dispatchEvent(e);
}
public function sendMessage(service:String, onSuccess:Function, onFailure:Function, message:Object=null):void {
LOGGER.debug("SENDING [{0}]", [service]);
var responder:Responder = new Responder(
function(result:Object):void { // On successful result
onSuccess("Successfully sent [" + service + "].");
},
function(status:Object):void { // status - On error occurred
var errorReason:String = "Failed to send [" + service + "]:\n";
for (var x:Object in status) {
errorReason += "\t" + x + " : " + status[x];
}
}
);
if (message == null) {
_netConnection.call(service, responder);
} else {
_netConnection.call(service, responder, message);
}
}
/**
* Connect to the server.
* uri: The uri to the conference application.
* 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 connect(params:ConferenceParameters, tunnel:Boolean = false):void {
_conferenceParameters = params;
tried_tunneling = tunnel;
try {
var uri:String = _applicationURI + "/" + _conferenceParameters.room;
LOGGER.debug("::Connecting to {0} [{1}]", [uri, jsonXify(_conferenceParameters)]);
_netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role,
_conferenceParameters.room, _conferenceParameters.voicebridge,
_conferenceParameters.record, _conferenceParameters.externUserID,
_conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings);
} catch(e:ArgumentError) {
// Invalid parameters.
switch (e.errorID) {
case 2004 :
LOGGER.debug("Error! Invalid server location: {0}", [uri]);
break;
default :
LOGGER.debug("UNKNOWN Error! Invalid server location: {0}", [uri]);
break;
}
}
}
public function disconnect(logoutOnUserCommand:Boolean):void {
this.logoutOnUserCommand = logoutOnUserCommand;
_netConnection.close();
}
public function forceClose():void {
_netConnection.close();
}
protected function netStatus(event:NetStatusEvent):void {
handleResult( event );
}
private var _bwMon:BandwidthMonitor = new BandwidthMonitor();
private function startMonitoringBandwidth():void {
LOGGER.info("Start monitoring bandwidth.");
var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/;
var result:Array = pattern.exec(_applicationURI);
_bwMon.serverURL = result.server;
_bwMon.serverApplication = "video";
_bwMon.start();
}
private var autoReconnectTimer:Timer = new Timer(1000, 1);
public function handleResult(event:Object):void {
var info : Object = event.info;
var statusCode : String = info.code;
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
switch (statusCode) {
case "NetConnection.Connect.Success":
LOGGER.debug("Connection to viewers application succeeded.");
JSLog.debug("Successfully connected to BBB App.", logData);
validateToken();
break;
case "NetConnection.Connect.Failed":
if (tried_tunneling) {
LOGGER.error(":Connection to viewers application failed...even when tunneling");
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED);
} else {
disconnect(false);
LOGGER.error(":Connection to viewers application failed...try tunneling");
var rtmptRetryTimer:Timer = new Timer(1000, 1);
rtmptRetryTimer.addEventListener("timer", rtmptRetryTimerHandler);
rtmptRetryTimer.start();
}
break;
case "NetConnection.Connect.Closed":
LOGGER.debug("Connection to viewers application closed");
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED);
break;
case "NetConnection.Connect.InvalidApp":
LOGGER.debug(":viewers application not found on server");
sendConnectionFailedEvent(ConnectionFailedEvent.INVALID_APP);
break;
case "NetConnection.Connect.AppShutDown":
LOGGER.debug(":viewers application has been shutdown");
sendConnectionFailedEvent(ConnectionFailedEvent.APP_SHUTDOWN);
break;
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]);
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED);
break;
case "NetConnection.Connect.NetworkChange":
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;
default :
LOGGER.debug(":Default status to the viewers application" );
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
break;
}
}
private function autoReconnectTimerHandler(event:TimerEvent):void {
LOGGER.debug("autoReconnectTimerHandler: {0}", [event]);
connect(_conferenceParameters, tried_tunneling);
}
private function rtmptRetryTimerHandler(event:TimerEvent):void {
LOGGER.debug("rtmptRetryTimerHandler: {0}", [event]);
connect(_conferenceParameters, true);
}
protected function netSecurityError(event: SecurityErrorEvent):void {
LOGGER.error("Security error - {0}", [event.text]);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
}
protected function netIOError(event: IOErrorEvent):void {
LOGGER.error("Input/output error - {0}", [event.text]);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
}
protected function netASyncError(event: AsyncErrorEvent):void {
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]);
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
}
private function sendConnectionFailedEvent(reason:String):void{
var logData:Object = new Object();
if (this.logoutOnUserCommand) {
logData.reason = "User requested.";
logData.user = UsersUtil.getUserData();
JSLog.debug("User logged out from BBB App.", logData);
sendUserLoggedOutEvent();
} else {
logData.reason = reason;
logData.user = UsersUtil.getUserData();
JSLog.warn("User disconnected from BBB App.", logData);
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);
}
private function attemptReconnect(backoff:Number):void{
var retryTimer:Timer = new Timer(backoff, 1);
retryTimer.addEventListener(TimerEvent.TIMER, function():void{
connect(_conferenceParameters, tried_tunneling);
});
retryTimer.start();
if (this.backoff < 16000) this.backoff = backoff *2;
}
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]);
}
}
}
public class NetConnectionDelegate
{
private static const LOGGER:ILogger = getClassLogger(NetConnectionDelegate);
private var _netConnection:NetConnection;
private var connectionId:Number;
private var connected:Boolean = false;
private var _userid:Number = -1;
private var _role:String = "unknown";
private var _applicationURI:String;
private var _conferenceParameters:ConferenceParameters;
// These two are just placeholders. We'll get this from the server later and
// then pass to other modules.
private var _authToken:String = "AUTHORIZED";
private var _room:String;
private var tried_tunneling:Boolean = false;
private var logoutOnUserCommand:Boolean = false;
private var backoff:Number = 2000;
private var dispatcher:Dispatcher;
private var _messageListeners:Array = new Array();
private var authenticated: Boolean = false;
private var reconnecting:Boolean = false;
public function NetConnectionDelegate():void
{
dispatcher = new Dispatcher();
_netConnection = new NetConnection();
_netConnection.proxyType = "best";
_netConnection.client = this;
_netConnection.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
_netConnection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, netASyncError );
_netConnection.addEventListener( SecurityErrorEvent.SECURITY_ERROR, netSecurityError );
_netConnection.addEventListener( IOErrorEvent.IO_ERROR, netIOError );
}
public function setUri(uri:String):void {
_applicationURI = uri;
}
public function get connection():NetConnection {
return _netConnection;
}
public function addMessageListener(listener:IMessageListener):void {
_messageListeners.push(listener);
}
public function removeMessageListener(listener:IMessageListener):void {
for (var ob:int=0; ob<_messageListeners.length; ob++) {
if (_messageListeners[ob] == listener) {
_messageListeners.splice (ob,1);
break;
}
}
}
private function notifyListeners(messageName:String, message:Object):void {
if (messageName != null && messageName != "") {
for (var notify:String in _messageListeners) {
_messageListeners[notify].onMessage(messageName, message);
}
} 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()]);
if (!authenticated && (messageName == "validateAuthTokenReply")) {
handleValidateAuthTokenReply(msg)
} else if (messageName == "validateAuthTokenTimedOut") {
handleValidateAuthTokenTimedOut(msg)
} else if (authenticated) {
notifyListeners(messageName, msg);
} else {
LOGGER.debug("Ignoring message=[{0}] as our token hasn't been validated yet.", [messageName]);
}
}
private function validateToken():void {
var message:Object = new Object();
message["userId"] = _conferenceParameters.internalUserID;
message["authToken"] = _conferenceParameters.authToken;
sendMessage(
"validateToken",// Remote function name
// result - On successful result
function(result:Object):void {
LOGGER.debug("validating token for [{0}]", [_conferenceParameters.internalUserID]);
},
// status - On error occurred
function(status:Object):void {
LOGGER.error("Error occurred:");
for (var x:Object in status) {
LOGGER.error(x + " : " + status[x]);
}
},
message
); //_netConnection.call
}
private function handleValidateAuthTokenTimedOut(msg: Object):void {
LOGGER.debug("*** handleValidateAuthTokenTimedOut {0} **** \n", [msg.msg]);
var map:Object = JSON.parse(msg.msg);
var tokenValid: Boolean = map.valid as Boolean;
var userId: String = map.userId as String;
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
JSLog.critical("Validate auth token timed out.", logData);
if (tokenValid) {
authenticated = true;
LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]);
} else {
LOGGER.debug("*** handleValidateAuthTokenTimedOut. valid=[{0}] **** \n", [tokenValid]);
dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
}
if (reconnecting) {
onReconnect();
reconnecting = false;
}
}
private function handleValidateAuthTokenReply(msg: Object):void {
LOGGER.debug("*** handleValidateAuthTokenReply {0} **** \n", [msg.msg]);
var map:Object = JSON.parse(msg.msg);
var tokenValid: Boolean = map.valid as Boolean;
var userId: String = map.userId as String;
if (tokenValid) {
authenticated = true;
LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
} else {
LOGGER.debug("*** handleValidateAuthTokenReply. valid=[{0}] **** \n", [tokenValid]);
dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
}
if (reconnecting) {
onReconnect();
reconnecting = false;
}
}
private function onReconnect():void {
if (authenticated) {
onReconnectSuccess();
} else {
onReconnectFailed();
}
}
private function onReconnectSuccess():void {
var attemptSucceeded:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_SUCCEEDED_EVENT);
attemptSucceeded.payload.type = ReconnectionManager.BIGBLUEBUTTON_CONNECTION;
dispatcher.dispatchEvent(attemptSucceeded);
}
private function onReconnectFailed():void {
sendUserLoggedOutEvent();
}
private function sendConnectionSuccessEvent(userid:String):void{
var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS);
e.userid = userid;
dispatcher.dispatchEvent(e);
}
public function sendMessage(service:String, onSuccess:Function, onFailure:Function, message:Object=null):void {
LOGGER.debug("SENDING [{0}]", [service]);
var responder:Responder = new Responder(
function(result:Object):void { // On successful result
onSuccess("Successfully sent [" + service + "].");
},
function(status:Object):void { // status - On error occurred
var errorReason:String = "Failed to send [" + service + "]:\n";
for (var x:Object in status) {
errorReason += "\t" + x + " : " + status[x];
}
}
);
if (message == null) {
_netConnection.call(service, responder);
} else {
_netConnection.call(service, responder, message);
}
}
/**
* Connect to the server.
* uri: The uri to the conference application.
* 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 connect(params:ConferenceParameters, tunnel:Boolean = false):void {
_conferenceParameters = params;
tried_tunneling = tunnel;
try {
var uri:String = _applicationURI + "/" + _conferenceParameters.room;
LOGGER.debug("::Connecting to {0} [{1}]", [uri, jsonXify(_conferenceParameters)]);
_netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role,
_conferenceParameters.room, _conferenceParameters.voicebridge,
_conferenceParameters.record, _conferenceParameters.externUserID,
_conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings);
} catch(e:ArgumentError) {
// Invalid parameters.
switch (e.errorID) {
case 2004 :
LOGGER.debug("Error! Invalid server location: {0}", [uri]);
break;
default :
LOGGER.debug("UNKNOWN Error! Invalid server location: {0}", [uri]);
break;
}
}
}
public function disconnect(logoutOnUserCommand:Boolean):void {
this.logoutOnUserCommand = logoutOnUserCommand;
_netConnection.close();
}
public function forceClose():void {
_netConnection.close();
}
protected function netStatus(event:NetStatusEvent):void {
handleResult( event );
}
private var _bwMon:BandwidthMonitor = new BandwidthMonitor();
private function startMonitoringBandwidth():void {
LOGGER.info("Start monitoring bandwidth.");
var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/;
var result:Array = pattern.exec(_applicationURI);
_bwMon.serverURL = result.server;
_bwMon.serverApplication = "video";
_bwMon.start();
}
public function handleResult(event:Object):void {
var info : Object = event.info;
var statusCode : String = info.code;
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
switch (statusCode) {
case "NetConnection.Connect.Success":
LOGGER.debug("Connection to viewers application succeeded.");
JSLog.debug("Successfully connected to BBB App.", logData);
validateToken();
break;
case "NetConnection.Connect.Failed":
if (tried_tunneling) {
LOGGER.error(":Connection to viewers application failed...even when tunneling");
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED);
} else {
disconnect(false);
LOGGER.error(":Connection to viewers application failed...try tunneling");
var rtmptRetryTimer:Timer = new Timer(1000, 1);
rtmptRetryTimer.addEventListener("timer", rtmptRetryTimerHandler);
rtmptRetryTimer.start();
}
break;
case "NetConnection.Connect.Closed":
LOGGER.debug("Connection to viewers application closed");
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED);
break;
case "NetConnection.Connect.InvalidApp":
LOGGER.debug(":viewers application not found on server");
sendConnectionFailedEvent(ConnectionFailedEvent.INVALID_APP);
break;
case "NetConnection.Connect.AppShutDown":
LOGGER.debug(":viewers application has been shutdown");
sendConnectionFailedEvent(ConnectionFailedEvent.APP_SHUTDOWN);
break;
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]);
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED);
break;
case "NetConnection.Connect.NetworkChange":
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;
default :
LOGGER.debug(":Default status to the viewers application" );
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
break;
}
}
private function rtmptRetryTimerHandler(event:TimerEvent):void {
LOGGER.debug("rtmptRetryTimerHandler: {0}", [event]);
connect(_conferenceParameters, true);
}
protected function netSecurityError(event: SecurityErrorEvent):void {
LOGGER.error("Security error - {0}", [event.text]);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
}
protected function netIOError(event: IOErrorEvent):void {
LOGGER.error("Input/output error - {0}", [event.text]);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
}
protected function netASyncError(event: AsyncErrorEvent):void {
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]);
LOGGER.debug("Asynchronous code error - {0}", [event.toString()]);
sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON);
}
private function sendConnectionFailedEvent(reason:String):void{
var logData:Object = new Object();
if (this.logoutOnUserCommand) {
logData.reason = "User requested.";
logData.user = UsersUtil.getUserData();
JSLog.debug("User logged out from BBB App.", logData);
sendUserLoggedOutEvent();
} else if (reason == ConnectionFailedEvent.CONNECTION_CLOSED) {
// do not try to reconnect if the connection failed is different than CONNECTION_CLOSED
logData.reason = reason;
logData.user = UsersUtil.getUserData();
JSLog.warn("User disconnected from BBB App.", logData);
if (reconnecting) {
var attemptFailedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_CONNECTION_ATTEMPT_FAILED_EVENT);
attemptFailedEvent.payload.type = ReconnectionManager.BIGBLUEBUTTON_CONNECTION;
dispatcher.dispatchEvent(attemptFailedEvent);
} else {
reconnecting = true;
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
}
titleLbl.text = value.title;
if (value.occurrences > 1) {
titleLbl.text = "(" + value.occurrences + ") " + titleLbl.text;
}
messageTxt.htmlText = value.message;
timeLbl.text = value.time;
validateNow();
}
@ -31,4 +35,7 @@
<mx:Label id="titleLbl" width="100%" styleName="statusTitleStyle"/>
<mx:Text id="messageTxt" width="100%" styleName="statusMessageStyle"/>
</mx:VBox>
<mx:VBox height="100%" verticalAlign="bottom">
<mx:Label id="timeLbl" width="100%" styleName="statusTimeStyle"/>
</mx:VBox>
</mx:HBox>

View File

@ -72,11 +72,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
//LogUtil.debug("Progress: " + totalProgress);
this.setProgress(totalProgress/numModules, 100);
}
private function allModulesLoaded(e:ModuleLoadEvent):void{
parent.removeChild(this);
if (parent != null) parent.removeChild(this);
}
private function testRTMP(e:PortTestEvent):void{
//- Cannot get locale string this early in loading process
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"
title="{ResourceUtil.getInstance().getString('bbb.logout.title')}" showCloseButton="false" creationComplete="init()"
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>
<![CDATA[
import mx.core.FlexGlobals;
@ -52,6 +52,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
request.method = URLRequestMethod.GET;
urlLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, handleComplete);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, handleRedirectError);
urlLoader.load(request);
}
@ -77,6 +78,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
PopUpManager.removePopUp(this);
}
private function handleRedirectError(e:IOErrorEvent):void {
PopUpManager.removePopUp(this);
}
private function onUserLoggedOutWindowClose(e:Event):void {
PopUpManager.removePopUp(this);
}
@ -110,17 +115,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
break;
}
}
private function reconnect():void {
ExternalInterface.call("document.location.reload", true);
}
]]>
</mx:Script>
<mx:VBox width="100%" height="100%" horizontalAlign="center">
<mx:Text text="{message}"/>
<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: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.bigbluebutton.common.IBbbModuleWindow;
import org.bigbluebutton.common.Images;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.common.events.AddUIComponentToMainCanvas;
import org.bigbluebutton.common.events.CloseWindowEvent;
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 {
LogUtil.initLogging();
UserManager.getInstance().getConference().configLockSettings();
layoutOptions = new LayoutOptions();
layoutOptions.parseOptions();
@ -585,6 +583,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function handleInvalidAuthToken(event:InvalidAuthTokenEvent):void {
showlogoutWindow(ResourceUtil.getInstance().getString('bbb.mainshell.invalidAuthToken'));
globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.CANCEL_RECONNECTION_EVENT));
}
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>
<![CDATA[
import flash.globalization.DateTimeFormatter;
import flash.globalization.DateTimeStyle;
import flash.globalization.LocaleID;
import mx.controls.ToolTip;
import mx.core.FlexGlobals;
import mx.managers.PopUpManager;
@ -58,24 +62,38 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function handleSuccessMessageEvent(e:ClientStatusEvent):void {
if(isUniqueMessage("success", e.title, e.message)) {
messages.push({type:"success",title:e.title,message:e.message});
showNotification();
}
handleMessageEvent("success", e);
}
private function handleWarningMessageEvent(e:ClientStatusEvent):void {
if(isUniqueMessage("warning", e.title, e.message)) {
messages.push({type:"warning",title:e.title,message:e.message});
showNotification();
}
handleMessageEvent("warning", e);
}
private function handleFailMessageEvent(e:ClientStatusEvent):void {
if(isUniqueMessage("fail", e.title, e.message)) {
messages.push({type:"fail",title:e.title,message:e.message});
showNotification();
handleMessageEvent("fail", e);
}
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 {
@ -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++) {
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 {

View File

@ -116,12 +116,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function noButtonClicked():void {
userClosed = true;
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();
dispatcher.dispatchEvent(new WebRTCEchoTestEvent(WebRTCEchoTestEvent.WEBRTC_ECHO_TEST_NO_AUDIO));
onCancelClicked();

View File

@ -28,6 +28,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import mx.events.FlexEvent;
import org.bigbluebutton.common.events.OpenWindowEvent;
import org.bigbluebutton.core.EventConstants;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ModuleStartedEvent;
import org.bigbluebutton.modules.chat.events.ChatEvent;
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}" >
<MethodInvoker generator="{ChatMessageService}" method="sendWelcomeMessage"/>
</EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT}" >
<EventAnnouncer generator="{TranscriptEvent}" type="{TranscriptEvent.LOAD_TRANSCRIPT}"/>
</EventHandlers>
<Injectors target="{ChatMessageService}">
<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 var welcomed:Boolean = false;
public var dispatcher:IEventDispatcher;
@ -67,9 +69,12 @@ package org.bigbluebutton.modules.chat.services
for (var i:int = 0; i < chats.length; i++) {
handleChatReceivePublicMessageCommand(chats[i], true);
}
var pcEvent:TranscriptEvent = new TranscriptEvent(TranscriptEvent.TRANSCRIPT_EVENT);
dispatcher.dispatchEvent(pcEvent);
if (!welcomed) {
var pcEvent:TranscriptEvent = new TranscriptEvent(TranscriptEvent.TRANSCRIPT_EVENT);
dispatcher.dispatchEvent(pcEvent);
welcomed = true;
}
}
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.SEND_MESSAGE}" method="remoteSendMessage" />
<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" />
@ -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.UserJoinedEvent;
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.Conference;
import org.bigbluebutton.modules.chat.ChatConstants;
@ -232,6 +234,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
scrollToEndOfMessage();
}
}
private function refreshChat(e:BBBEvent):void {
if (e.payload.type == "BIGBLUEBUTTON_CONNECTION") {
if (publicChat) chatMessages = new ChatConversation();
}
}
private function handleUserJoinedEvent(event:UserJoinedEvent):void {
// Handle user joining so that the user can start to talk if the person rejoins
if (!publicChat && event.userID == chatWithUserID) {

View File

@ -27,12 +27,16 @@ package org.bigbluebutton.modules.classyaudio.managers {
import flash.net.NetConnection;
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.CallDisconnectedEvent;
import org.bigbluebutton.modules.classyaudio.events.ConnectionStatusEvent;
import org.bigbluebutton.modules.classyaudio.events.ConnectionStatusEvent;
public class ConnectionManager {
private static const LOGGER:ILogger = getClassLogger(ConnectionManager);
private var netConnection:NetConnection = null;
private var username:String;
private var uri:String;
@ -82,42 +86,42 @@ package org.bigbluebutton.modules.classyaudio.managers {
switch(evt.info.code) {
case "NetConnection.Connect.Success":
LogUtil.debug("Successfully connected to SIP application.");
LOGGER.debug("Successfully connected to SIP application.");
event.status = ConnectionStatusEvent.SUCCESS;
break;
case "NetConnection.Connect.Failed":
LogUtil.debug("Failed to connect to SIP application.");
LOGGER.debug("Failed to connect to SIP application.");
event.status = ConnectionStatusEvent.FAILED;
break;
case "NetConnection.Connect.Closed":
LogUtil.debug("Connection to SIP application has closed.");
LOGGER.debug("Connection to SIP application has closed.");
event.status = ConnectionStatusEvent.CLOSED;
break;
case "NetConnection.Connect.Rejected":
LogUtil.debug("Connection to SIP application was rejected.");
LOGGER.debug("Connection to SIP application was rejected.");
event.status = ConnectionStatusEvent.REJECTED;
break;
default:
}
LogUtil.debug("Phone Module Connection Status: " + event.status);
LogUtil.debug("Dispatching " + event.status);
LOGGER.debug("Phone Module Connection Status: {0}", [event.status]);
LOGGER.debug("Dispatching " + event.status);
dispatcher.dispatchEvent(event);
}
private function asyncErrorHandler(event:AsyncErrorEvent):void {
LogUtil.debug("AsyncErrorEvent: " + event);
LOGGER.debug("AsyncErrorEvent: {0}", [event]);
}
private function securityErrorHandler(event:SecurityErrorEvent):void {
LogUtil.debug("securityErrorHandler: " + event);
LOGGER.debug("securityErrorHandler: {0}", [event]);
}
public function call():void {
LogUtil.debug("Calling " + room);
LOGGER.debug("Calling {0}", [room]);
doCall(room);
}
@ -127,21 +131,21 @@ package org.bigbluebutton.modules.classyaudio.managers {
//
//********************************************************************************************
public function failedToJoinVoiceConferenceCallback(msg:String):* {
LogUtil.debug("failedToJoinVoiceConferenceCallback " + msg);
LOGGER.debug("failedToJoinVoiceConferenceCallback {0}", [msg]);
var event:CallDisconnectedEvent = new CallDisconnectedEvent();
dispatcher.dispatchEvent(event);
isConnected = false;
}
public function disconnectedFromJoinVoiceConferenceCallback(msg:String):* {
LogUtil.debug("disconnectedFromJoinVoiceConferenceCallback " + msg);
LOGGER.debug("disconnectedFromJoinVoiceConferenceCallback {0}", [msg]);
var event:CallDisconnectedEvent = new CallDisconnectedEvent();
dispatcher.dispatchEvent(event);
isConnected = false;
}
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;
var event:CallConnectedEvent = new CallConnectedEvent();
event.publishStreamName = publishName;
@ -156,7 +160,7 @@ package org.bigbluebutton.modules.classyaudio.managers {
//
//********************************************************************************************
public function doCall(dialStr:String):void {
LogUtil.debug("Calling " + dialStr);
LOGGER.debug("Calling ", [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 {
LOGGER.debug("DS:PublishWindowManager::opening desk share window, autostart={0} autoFullScreen={1}", [autoStart, autoFullScreen]);
shareWindow = new DesktopPublishWindow();
shareWindow.initWindow(service.getConnection(), uri , useTLS , room, autoStart, autoFullScreen);
shareWindow.visible = true;

View File

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

View File

@ -21,6 +21,7 @@ package org.bigbluebutton.modules.deskshare.services.red5
{
import com.asfusion.mate.events.Dispatcher;
import flash.events.AsyncErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
@ -30,20 +31,23 @@ package org.bigbluebutton.modules.deskshare.services.red5
import flash.net.SharedObject;
import flash.utils.Timer;
import mx.utils.ObjectUtil;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
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.CursorEvent;
import org.bigbluebutton.modules.deskshare.events.ViewStreamEvent;
public class Connection {
private static const LOGGER:ILogger = getClassLogger(Connection);
private static const LOGGER:ILogger = getClassLogger(Connection);
private var nc:NetConnection;
private var uri:String;
private const connectionTimeout:int = 5000;
private var nc:NetConnection;
private var uri:String;
private var retryTimer:Timer = null;
private var retryCount:int = 0;
private const MAX_RETRIES:int = 5;
@ -52,7 +56,10 @@ package org.bigbluebutton.modules.deskshare.services.red5
private var width:Number;
private var height:Number;
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();
public function Connection(room:String) {
@ -90,6 +97,8 @@ package org.bigbluebutton.modules.deskshare.services.red5
nc.objectEncoding = ObjectEncoding.AMF0;
nc.client = this;
nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, debugAsyncErrorHandler);
nc.addEventListener(NetStatusEvent.NET_STATUS, debugNetStatusHandler);
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
@ -110,12 +119,6 @@ package org.bigbluebutton.modules.deskshare.services.red5
}
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 {
@ -180,6 +183,11 @@ package org.bigbluebutton.modules.deskshare.services.red5
switch(event.info.code){
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;
dispatcher.dispatchEvent(ce);
@ -187,6 +195,17 @@ package org.bigbluebutton.modules.deskshare.services.red5
case "NetConnection.Connect.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);
connectionSuccessHandler();
break;
@ -199,7 +218,24 @@ package org.bigbluebutton.modules.deskshare.services.red5
case "NetConnection.Connect.Closed":
LOGGER.debug("Deskshare connection 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;
case "NetConnection.Connect.InvalidApp":
@ -253,6 +289,7 @@ package org.bigbluebutton.modules.deskshare.services.red5
}
public function disconnect():void{
logoutOnUserCommand = true;
if (nc != null) nc.close();
}
@ -261,10 +298,20 @@ package org.bigbluebutton.modules.deskshare.services.red5
var deskSOName:String = room + "-deskSO";
deskSO = SharedObject.getRemote(deskSOName, uri, false);
deskSO.client = this;
deskSO.addEventListener(AsyncErrorEvent.ASYNC_ERROR, debugAsyncErrorHandler);
deskSO.addEventListener(NetStatusEvent.NET_STATUS, debugNetStatusHandler);
deskSO.connect(nc);
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{
return nc;
@ -285,10 +332,13 @@ package org.bigbluebutton.modules.deskshare.services.red5
*/
public function appletStarted(videoWidth:Number, videoHeight:Number):void{
LOGGER.debug("Got applet started");
var event:AppletStartedEvent = new AppletStartedEvent();
event.videoWidth = videoWidth;
event.videoHeight = videoHeight;
dispatcher.dispatchEvent(event);
if (nc != null && nc.connected) {
var event:AppletStartedEvent = new AppletStartedEvent();
event.videoWidth = videoWidth;
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 dsOptions:DeskshareOptions;
private var calledStopApplet:Boolean = false;
private function init():void {
dsOptions = new DeskshareOptions();
baseIndex = dsOptions.baseTabIndex;
@ -246,6 +248,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function onAppletStart(event:AppletStartedEvent):void{
if (!connection.connected) return;
LOGGER.debug("DeskShareWindow::onAppletStart");
startPreviewStream(connection, room, event.videoWidth, event.videoHeight);
var streamEvent:StreamEvent = new StreamEvent(StreamEvent.START);
@ -343,15 +347,24 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
closeWindow();
}
private function closeWindow():void {
ExternalInterface.call("stopApplet");
dispatchEvent(new ShareWindowEvent(ShareWindowEvent.CLOSE));
}
private function restartJava():void {
ExternalInterface.call("stopApplet");
shareScreen(sharingFullScreen);
}
private function callStopApplet():void {
if (!calledStopApplet) {
calledStopApplet = true;
LOGGER.debug("Calling stopApplet in callStopApplet()");
ExternalInterface.call("stopApplet");
}
}
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 {
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{
LOGGER.debug("onNetStatus info={0}", [e.info.text]);
switch(e.info.code){
case "NetStream.Play.Start":
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 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);
status = connStatus;
reconnecting = isReconnecting;
}
}
}

View File

@ -30,7 +30,9 @@ package org.bigbluebutton.modules.phone.managers {
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.managers.ReconnectionManager;
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.FlashCallDisconnectedEvent;
import org.bigbluebutton.modules.phone.events.FlashVoiceConnectionStatusEvent;
@ -49,6 +51,8 @@ package org.bigbluebutton.modules.phone.managers {
private var registered:Boolean = false;
private var closedByUser:Boolean = false;
private var reconnecting:Boolean = false;
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 {
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));
}
}
private function netStatus (event:NetStatusEvent ):void {
var info : Object = event.info;
var statusCode : String = info.code;
@ -115,12 +147,12 @@ package org.bigbluebutton.modules.phone.managers {
case "NetConnection.Connect.Success":
LOGGER.debug("Connection success");
JSLog.debug("Successfully connected to BBB Voice", logData);
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.CONNECTED));
handleConnectionSuccess();
break;
case "NetConnection.Connect.Failed":
LOGGER.debug("Connection failed");
JSLog.error("Failed to connect to BBB Voice", logData);
dispatcher.dispatchEvent(new FlashVoiceConnectionStatusEvent(FlashVoiceConnectionStatusEvent.FAILED));
handleConnectionFailed();
break;
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.");

View File

@ -10,6 +10,7 @@
import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.ClientStatusEvent;
import org.bigbluebutton.modules.phone.PhoneOptions;
import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent;
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 {
LOGGER.debug("Connection status event. status=[{0}]", [event.status]);
if (event.status == FlashVoiceConnectionStatusEvent.CONNECTED) {
switch (state) {
case JOIN_VOICE_CONFERENCE:
callIntoVoiceConference();
break;
case DO_ECHO_TEST:
callIntoEchoTest();
break;
case CALL_TO_LISTEN_ONLY_STREAM:
callToListenOnlyStream();
break;
default:
LOGGER.debug("unhandled state: {0}", [state]);
break;
}
switch (event.status) {
case FlashVoiceConnectionStatusEvent.CONNECTED:
handleFlashVoiceConnected();
break;
case FlashVoiceConnectionStatusEvent.FAILED:
case FlashVoiceConnectionStatusEvent.DISCONNECTED:
// If reconnection is under way the state should de kept
if(!event.reconnecting) {
state = INITED;
}
dispatcher.dispatchEvent(new FlashLeftVoiceConferenceEvent());
break;
default:
LOGGER.debug("unhandled state: {0}", [state]);
}
}
@ -392,5 +419,12 @@
usingFlash = 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.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.main.api.JSAPI;
import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.main.events.ClientStatusEvent;
import org.bigbluebutton.modules.phone.PhoneModel;
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]);
}
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);
}

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.FlashEchoTestHasAudioEvent;
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.FlashStopEchoTestCommand;
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}">
<MethodInvoker generator="{FlashCallManager}" method="handleLeaveVoiceConferenceCommand" arguments="{event}"/>
</EventHandlers>
<EventHandlers type="{FlashLeftVoiceConferenceEvent.LEFT_VOICE_CONFERENCE}">
<MethodInvoker generator="{FlashCallManager}" method="handleFlashLeftVoiceConference"/>
</EventHandlers>
<EventHandlers type="{UseFlashModeCommand.USE_FLASH_MODE}">
<MethodInvoker generator="{FlashCallManager}" method="handleUseFlashModeCommand"/>
</EventHandlers>

View File

@ -1,16 +1,19 @@
package org.bigbluebutton.modules.polling.model
{
import mx.collections.ArrayCollection;
public class SimplePollResult
{
private var _id:String;
private var _answers: Array;
public function SimplePollResult(id:String, answers:Array)
private var _numRespondents: int;
private var _numResponders: int;
public function SimplePollResult(id:String, answers:Array, numRespondents: int, numResponders: int)
{
_id = id;
_answers = answers;
_numRespondents = numRespondents;
_numResponders = numResponders;
}
public function get id():String {
@ -22,5 +25,12 @@ package org.bigbluebutton.modules.polling.model
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 flash.accessibility.Accessibility;
import org.as3commons.logging.api.ILogger;
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.PollStartedEvent;
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.SimplePoll;
import org.bigbluebutton.modules.polling.model.SimplePollResult;
import org.bigbluebutton.util.i18n.ResourceUtil;
public class PollDataProcessor
{
@ -61,19 +67,43 @@ package org.bigbluebutton.modules.polling.service
var map:Object = JSON.parse(msg.msg);
if (map.hasOwnProperty("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 answers:Array = poll.answers as Array;
var accessibleAnswers:String = ResourceUtil.getInstance().getString("bbb.polling.results.accessible.header") + "<br />";
var ans:Array = new Array();
for (var j:int = 0; j < answers.length; j++) {
var a:Object = answers[j];
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);
if (map.hasOwnProperty("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 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));
}
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 {
var tFormat:TextFormat = new TextFormat();
tFormat.size = defaultSize;
tFormat.font = "arial";
tFormat.align = TextFormatAlign.CENTER;
textField.setTextFormat(tFormat);
var size:Number = defaultSize;

View File

@ -8,6 +8,10 @@ package org.bigbluebutton.modules.polling.views
import mx.containers.HBox;
import mx.containers.TitleWindow;
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 org.bigbluebutton.modules.polling.events.PollVotedEvent;
@ -21,6 +25,7 @@ package org.bigbluebutton.modules.polling.views
public class PollResultsModal extends TitleWindow {
private var _voteListener:Listener;
private var _respondersLabel:Label;
private var _pollGraphic:PollGraphic;
private var _publishBtn:Button;
private var _closeBtn:Button;
@ -28,23 +33,33 @@ package org.bigbluebutton.modules.polling.views
public function PollResultsModal() {
super();
styleName = "micSettingsWindowStyle";
width = 300;
height = 300;
height = 300;
setStyle("verticalGap", 15);
showCloseButton = false;
layout = "vertical";
setStyle("horizontalAlign", "center");
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();
_publishBtn = new Button();
_publishBtn.label = ResourceUtil.getInstance().getString('bbb.polling.publishButton.label');
_publishBtn.addEventListener(MouseEvent.CLICK, handlePublishClick);
topBox.addChild(_publishBtn);
_closeBtn = new Button();
_closeBtn.label = ResourceUtil.getInstance().getString('bbb.polling.closeButton.label');
_closeBtn.addEventListener(MouseEvent.CLICK, handleCloseClick);
topBox.addChild(_closeBtn);
addChild(topBox);
var hrule:HRule = new HRule();
hrule.percentWidth = 100;
addChild(hrule);
_respondersLabel = new Label();
_respondersLabel.styleName = "pollResondersLabelStyle";
_respondersLabel.text = " ";// ResourceUtil.getInstance().getString('bbb.polling.respondersLabel.novotes');
addChild(_respondersLabel);
_pollGraphic = new PollGraphic();
_pollGraphic.data = null;
@ -52,6 +67,23 @@ package org.bigbluebutton.modules.polling.views
_pollGraphic.minWidth = 130;
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.type = PollVotedEvent.POLL_VOTED;
_voteListener.method = handlePollVotedEvent;
@ -62,14 +94,14 @@ package org.bigbluebutton.modules.polling.views
var answers:Array = poll.answers;
for (var j:int = 0; j < answers.length; j++) {
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.height = ((23+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 {
@ -77,10 +109,11 @@ package org.bigbluebutton.modules.polling.views
var answers:Array = e.result.answers;
for (var j:int = 0; j < answers.length; j++) {
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;
_respondersLabel.text = ResourceUtil.getInstance().getString('bbb.polling.respondersLabel.text', [e.result.numResponders + "/" + e.result.numRespondents]);
}
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[
import mx.events.FlexEvent;
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.model.users.events.RoleChangeEvent;
import org.bigbluebutton.modules.present.api.PresentationAPI;
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" />
</EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT}" >
<MethodInvoker generator="{PresentProxy}" method="getCurrentPresentationInfo" />
</EventHandlers>
<EventHandlers type="{UploadEvent.OPEN_UPLOAD_WINDOW}" >
<MethodInvoker generator="{PresentManager}" method="handleOpenUploadWindow" arguments="{event}" />
</EventHandlers>

View File

@ -11,7 +11,6 @@ package org.bigbluebutton.modules.present.model
private static var instance:PresentationModel = null;
private var _pages:ArrayCollection = new ArrayCollection();
private var _presentations:ArrayCollection = new ArrayCollection();
private var _presenter: Presenter;
@ -48,10 +47,6 @@ package org.bigbluebutton.modules.present.model
return _presenter;
}
public function addPage(page: Page):void {
_pages.addItem(page);
}
public function addPresentation(p: Presentation):void {
_presentations.addItem(p);
}
@ -68,6 +63,10 @@ package org.bigbluebutton.modules.present.model
return null;
}
public function removeAllPresentations():void {
_presentations.removeAll();
}
public function replacePresentation(p: Presentation):void {
var oldPres:Presentation = removePresentation(p.id);
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()]);
}
}
public function removeAllPresentations():void {
model.removeAllPresentations();
}
public function removePresentation(presentationID:String):void {
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);
}
service.removeAllPresentations();
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}"
x="{DEFAULT_X_POSITION}" y="{DEFAULT_Y_POSITION}"
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" />
@ -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 {
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 {
if(pollMenuData[e.index] != undefined) {
// start the requested poll
var pollEvent:StartPollEvent = null;
switch (e.index) {
case 0:
pollEvent = new StartPollEvent("YN");
sendStartPollEvent("YN");
break;
case 1:
pollEvent = new StartPollEvent("TF");
sendStartPollEvent("TF");
break;
case 2:
pollEvent = new StartPollEvent("A-2");
sendStartPollEvent("A-2");
break;
case 3:
pollEvent = new StartPollEvent("A-3");
sendStartPollEvent("A-3");
break;
case 4:
pollEvent = new StartPollEvent("A-4");
sendStartPollEvent("A-4");
break;
case 5:
pollEvent = new StartPollEvent("A-5");
sendStartPollEvent("A-5");
break;
case 6:
pollEvent = new StartPollEvent("A-6");
sendStartPollEvent("A-6");
break;
case 7:
pollEvent = new StartPollEvent("A-7");
sendStartPollEvent("A-7");
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"
toolTip="{ResourceUtil.getInstance().getString('bbb.polling.startButton.tooltip')}"
click="onPollStartButtonClicked()" tabIndex="{baseIndex+6}"/>
<poll:QuickPollButton id="quickPollBtn" height="30" tabIndex="{baseIndex+7}"
click="quickPollClicked(event)" />
<mx:Spacer width="100%" id="spacer1"/>
<mx:Button id="backButton" visible="false" width="45" height="30" styleName="presentationBackButtonStyle"
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()"
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"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.forwardBtn.toolTip')}" click="gotoNextSlide()"
tabIndex="{baseIndex+9}"/>
tabIndex="{baseIndex+10}"/>
<mx:Spacer width="50%" id="spacer3"/>
<mx:HSlider id="zoomSlider" visible="false" value="{slideView.zoomPercentage}" styleName="presentationZoomSliderStyle"
minimum="100" maximum="400" dataTipPlacement="top" labels="['100%','400%']"
useHandCursor="true" snapInterval="5" allowTrackClick="true" liveDragging="true"
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"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToWidth.toolTip')}"
click="onFitToPage(false)"
tabIndex="{baseIndex+11}"/>
tabIndex="{baseIndex+12}"/>
<mx:Button id="btnFitToPage" visible="false" width="30" height="30" styleName="presentationFitToPageButtonStyle"
toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToPage.toolTip')}"
click="onFitToPage(true)"
tabIndex="{baseIndex+12}"/>
tabIndex="{baseIndex+13}"/>
<!-- This spacer is to prevent the whiteboard toolbar from overlapping the fit-ot-page button -->
<mx:Spacer width="30" height="30" id="spacer4"/>
</mx:HBox>

View File

@ -179,6 +179,14 @@ package org.bigbluebutton.modules.users.services
var e:UsersConnectionEvent = new UsersConnectionEvent(UsersConnectionEvent.CONNECTION_SUCCESS);
e.userid = userid;
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 {
@ -346,6 +354,7 @@ package org.bigbluebutton.modules.users.services
if (UsersUtil.hasUser(internUserID)) {
var bu:BBBUser = UsersUtil.getUser(internUserID);
bu.talking = voiceUser.talking;
bu.voiceMuted = voiceUser.muted;
bu.voiceJoined = true;
@ -409,6 +418,9 @@ package org.bigbluebutton.modules.users.services
LOGGER.debug("*** handleGetUsersReply {0} **** \n", [msg.msg]);
var map:Object = JSON.parse(msg.msg);
var users:Object = map.users as Array;
// since might be a reconnection, clean up users list
UserManager.getInstance().getConference().removeAllParticipants();
if (map.count > 0) {
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().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.bigbluebutton.core.BBB;
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.StartBroadcastEvent;
import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent;
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
import org.bigbluebutton.main.api.JSLog;
public class VideoProxy
@ -48,15 +52,12 @@ package org.bigbluebutton.modules.videoconf.business
public var videoOptions:VideoConfOptions;
private var nc:NetConnection;
private var ns:NetStream;
private var _url:String;
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 autoReconnectTimer:Timer = new Timer(1000, 1);
private var dispatcher:Dispatcher = new Dispatcher();
private function parseOptions():void {
videoOptions = new VideoConfOptions();
videoOptions.parseOptions();
@ -91,32 +92,70 @@ package org.bigbluebutton.modules.videoconf.business
LOGGER.debug("VIDEO WEBCAM onIOError");
}
private function onConnectedToVideoApp():void{
var dispatcher:Dispatcher = new Dispatcher();
dispatcher.dispatchEvent(new ConnectedEvent(reconnecting));
reconnecting = false;
}
private function onConnectedToVideoApp():void{
dispatcher.dispatchEvent(new ConnectedEvent(reconnecting));
if (reconnecting) {
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{
LOGGER.debug("[{0}] for [{1}]", [event.info.code, _url]);
var logData:Object = new Object();
logData.user = UsersUtil.getUserData();
switch(event.info.code){
case "NetConnection.Connect.Success":
connected = true;
//ns = new NetStream(nc);
onConnectedToVideoApp();
break;
case "NetStream.Play.Failed":
disconnect();
if (reconnect) {
JSLog.warn("NetStream.Play.Failed from bbb-video", logData);
}
break;
case "NetStream.Play.Stop":
disconnect();
if (reconnect) {
JSLog.warn("NetStream.Play.Stop from bbb-video", logData);
}
break;
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();
break;
break;
case "NetConnection.Connect.NetworkChange":
JSLog.warn("Detected network change on bbb-video", logData);
break;
default:
LOGGER.debug("[{0}] for [{1}]", [event.info.code, _url]);
connected = false;
break;
}
}
@ -129,17 +168,14 @@ package org.bigbluebutton.modules.videoconf.business
}
public function startPublishing(e:StartBroadcastEvent):void{
ns = new NetStream(nc);
var ns:NetStream = new NetStream(nc);
ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus );
ns.addEventListener( IOErrorEvent.IO_ERROR, onIOError );
ns.addEventListener( AsyncErrorEvent.ASYNC_ERROR, onAsyncError );
ns.client = this;
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) {
LOGGER.info("Using H264 codec for video.");
var h264:H264VideoStreamSettings = new H264VideoStreamSettings();
var h264profile:String = 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;
}
LOGGER.info("Codec used: {0}", [h264Level]);
h264.setProfileLevel(h264profile, h264Level);
ns.videoStreamSettings = h264;
@ -200,18 +235,6 @@ package org.bigbluebutton.modules.videoconf.business
LOGGER.debug("VideoProxy:: disconnecting from Video application");
stopAllBroadcasting();
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 {

View File

@ -23,7 +23,7 @@ package org.bigbluebutton.modules.videoconf.events
public class ConnectedEvent extends Event
{
public static const VIDEO_CONNECTED:String = "connected to video app event";
public var reconnection:Boolean = false;
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}">
<MethodInvoker generator="{VideoEventMapDelegate}" method="handleCamSettingsClosedEvent" arguments="{event}"/>
</EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_DISCONNECTED_EVENT}">
<MethodInvoker generator="{VideoEventMapDelegate}" method="closeAllWindows"/>
</EventHandlers>
<!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
</EventMap>

View File

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

View File

@ -1,287 +1,289 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* 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
* 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.modules.whiteboard.business.shapes
{
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.as3commons.logging.util.jsonXify;
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* 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
* 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.modules.whiteboard.business.shapes
{
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.as3commons.logging.util.jsonXify;
import org.bigbluebutton.modules.whiteboard.models.Annotation;
public class PollResultObject extends DrawObject {
private static const LOGGER:ILogger = getClassLogger(PollResultObject);
//private const h:uint = 100;
//private const w:uint = 280;
private const bgFill:uint = 0XCECECE; //0xFFFFFF;
private const colFill:uint = 0x000000;
private const vpadding:Number = 10;
private const hpadding:Number = 5;
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 _textFields:Array;
public function PollResultObject(id:String, type:String, status:String) {
super(id, type, status)
_textFields = new Array();
data = null;
// temp setter for testing purposes
//data = sampledata;
}
public function set data(d:Array):void {
_data = d;
}
public function get data():Array {
return _data;
}
private function makeTextFields(num:int):void {
if (num > _textFields.length) {
var textField:TextField;
for (var i:int=_textFields.length; i < num; i++) {
textField = new TextField();
addChild(textField);
_textFields.push(textField);
}
} else if (num < _textFields.length) {
for (var j:int=_textFields.length; i > num; i--) {
removeChild(_textFields.pop());
}
}
}
private function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
// graphics.clear();
if (_data != null && _data.length > 0) {
graphics.lineStyle(2);
graphics.beginFill(bgFill, 1.0);
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)]);
// Current problem is that the rowHeight is truncated. It would be nice if the extra pixels
// could be distributed for a more even look.
var avgRowHeight:int = (unscaledHeight-vpadding*(_data.length+1)) / _data.length;
var extraVPixels:int = unscaledHeight - (_data.length * (avgRowHeight+vpadding) + vpadding);
LOGGER.debug("PollGraphic - extraVPixels {0}", [extraVPixels]);
var largestVal:int = -1;
var totalCount:Number = 0;
//find largest value
for (var i:int=0; i<_data.length; i++) {
if (_data[i].v > largestVal) largestVal = _data[i].v;
totalCount += _data[i].v;
}
var currTFIdx:int = 0;
var answerText:TextField;
var percentText:TextField;
var answerArray:Array = new Array();
var percentArray:Array = new Array();
var minFontSize:int = 20;
var currFontSize:int;
graphics.lineStyle(2);
graphics.beginFill(colFill, 1.0);
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 += 1;
vp--;
}
ry += curRowHeight/2;
//ry += curRowHeight * (j+0.5) + vpadding*(j+1);
// add row label
answerText = _textFields[currTFIdx++];
answerText.text = _data[j].a;
answerText.width = labelStartWidth;
answerText.height = curRowHeight;
answerText.selectable = false;
//addChild(answerText);
answerArray.push(answerText);
currFontSize = findFontSize(answerText, 20);
if (currFontSize < minFontSize) minFontSize = currFontSize;
//rowText.height = rowText.textHeight;
answerText.x = hpadding;
//rowText.y = ry-rowText.height/2;
// add percentage
percentText = _textFields[currTFIdx++];;// new TextField();
var percentNum:Number = (totalCount == 0 ? 0 : ((_data[j].v/totalCount)*100));
percentText.text = Math.round(percentNum).toString() + "%";
percentText.width = percentStartWidth;
percentText.height = curRowHeight;
percentText.selectable = false;
//addChild(percentText);
percentArray.push(percentText);
currFontSize = findFontSize(percentText, 20);
if (currFontSize < minFontSize) minFontSize = currFontSize;
//percentText.height = percentText.textHeight;
//percentText.x = unscaledWidth-percentStartWidth/2-percentText.width/2;
//percentText.y = ry-percentText.height/2;
}
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
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) {
curRowHeight += 1;
vp--;
}
ry += curRowHeight/2;
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
answerText = TextField(answerArray[j]);
findFontSize(answerText, minFontSize);
answerText.width = answerText.textWidth+4;
answerText.height = answerText.textHeight+4;
answerText.y = ry-answerText.height/2;
if (answerText.width > maxAnswerWidth) maxAnswerWidth = answerText.width;
percentText = TextField(percentArray[j]);
findFontSize(percentText, minFontSize);
percentText.width = percentText.textWidth+4;
percentText.height = percentText.textHeight+4;
percentText.x = unscaledWidth - hpadding - 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 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
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) {
curRowHeight += 1;
vp--;
}
ry += curRowHeight/2;
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
// draw rect
var rectWidth:int = maxBarWidth*(_data[j].v/largestVal);
graphics.drawRect(barStartX, ry-curRowHeight/2, rectWidth, curRowHeight);
// add vote count in middle of rect
countText = _textFields[currTFIdx++]; // new TextField();
countText.text = _data[j].v;
countText.width = rectWidth;
countText.height = curRowHeight;
countText.textColor = 0xFFFFFF;
countText.selectable = false;
//addChild(countText);
findFontSize(countText, minFontSize);
countText.width = countText.textWidth+4;
countText.height = countText.textHeight+4;
countText.x = barStartX+rectWidth/2-countText.width/2;
countText.y = ry-countText.height/2;
}
graphics.endFill();
}
}
private function findFontSize(textField:TextField, defaultSize:Number):int {
var tFormat:TextFormat = new TextFormat();
tFormat.size = defaultSize;
tFormat.align = TextFormatAlign.CENTER;
textField.setTextFormat(tFormat);
var size:Number = defaultSize;
while((textField.textWidth+4 > textField.width || textField.textHeight+4 > textField.height) && size > 0) {
size = size - 1;
tFormat.size = size;
textField.setTextFormat(tFormat);
}
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);
var arrayEnd:Number = (ao.points as Array).length;
var startX:Number = denormalize(21.845575, parentWidth);
var startY:Number = denormalize(23.145401, parentHeight);
var width:Number = denormalize(46.516006, parentWidth) - startX;
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)]);
var arrayEnd:Number = (ao.points as Array).length;
var startX:Number = denormalize((ao.points as Array)[0], parentWidth);
var startY:Number = denormalize((ao.points as Array)[1], parentHeight);
var pwidth:Number = denormalize((ao.points as Array)[2], parentWidth) - startX;
var pheight:Number = denormalize((ao.points as Array)[3], parentHeight) - startY;
var answers:Array = ao.result as Array;
var ans:Array = new Array();
for (var j:int = 0; j < answers.length; j++) {
var ar:Object = answers[j];
var rs:Object = {a: 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));
this.x = startX;
this.y = startY;
updateDisplayList(pwidth, pheight);
}
override public function redraw(a:Annotation, parentWidth:Number, parentHeight:Number, zoom:Number):void {
draw(a, parentWidth, parentHeight, zoom);
}
}
import org.bigbluebutton.util.i18n.ResourceUtil;
public class PollResultObject extends DrawObject {
private static const LOGGER:ILogger = getClassLogger(PollResultObject);
//private const h:uint = 100;
//private const w:uint = 280;
private const bgFill:uint = 0xFFFFFF;
private const colFill:uint = 0x000000;
private const vpadding:Number = 10;
private const hpadding:Number = 5;
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 _textFields:Array;
public function PollResultObject(id:String, type:String, status:String) {
super(id, type, status)
_textFields = new Array();
data = null;
// temp setter for testing purposes
//data = sampledata;
}
public function set data(d:Array):void {
_data = d;
}
public function get data():Array {
return _data;
}
private function makeTextFields(num:int):void {
if (num > _textFields.length) {
var textField:TextField;
for (var i:int=_textFields.length; i < num; i++) {
textField = new TextField();
addChild(textField);
_textFields.push(textField);
}
} else if (num < _textFields.length) {
for (var j:int=_textFields.length; i > num; i--) {
removeChild(_textFields.pop());
}
}
}
private function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
// graphics.clear();
if (_data != null && _data.length > 0) {
graphics.lineStyle(2);
graphics.beginFill(bgFill, 1.0);
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)]);
// Current problem is that the rowHeight is truncated. It would be nice if the extra pixels
// could be distributed for a more even look.
var avgRowHeight:int = (unscaledHeight-vpadding*(_data.length+1)) / _data.length;
var extraVPixels:int = unscaledHeight - (_data.length * (avgRowHeight+vpadding) + vpadding);
LOGGER.debug("PollGraphic - extraVPixels {0}", [extraVPixels]);
var largestVal:int = -1;
var totalCount:Number = 0;
//find largest value
for (var i:int=0; i<_data.length; i++) {
if (_data[i].v > largestVal) largestVal = _data[i].v;
totalCount += _data[i].v;
}
var currTFIdx:int = 0;
var answerText:TextField;
var percentText:TextField;
var answerArray:Array = new Array();
var percentArray:Array = new Array();
var minFontSize:int = 20;
var currFontSize:int;
graphics.lineStyle(2);
graphics.beginFill(colFill, 1.0);
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 += 1;
vp--;
}
ry += curRowHeight/2;
//ry += curRowHeight * (j+0.5) + vpadding*(j+1);
// add row label
answerText = _textFields[currTFIdx++];
answerText.text = _data[j].a;
answerText.width = labelStartWidth;
answerText.height = curRowHeight;
answerText.selectable = false;
//addChild(answerText);
answerArray.push(answerText);
currFontSize = findFontSize(answerText, 20);
if (currFontSize < minFontSize) minFontSize = currFontSize;
//rowText.height = rowText.textHeight;
answerText.x = hpadding;
//rowText.y = ry-rowText.height/2;
// add percentage
percentText = _textFields[currTFIdx++];;// new TextField();
var percentNum:Number = (totalCount == 0 ? 0 : ((_data[j].v/totalCount)*100));
percentText.text = Math.round(percentNum).toString() + "%";
percentText.width = percentStartWidth;
percentText.height = curRowHeight;
percentText.selectable = false;
//addChild(percentText);
percentArray.push(percentText);
currFontSize = findFontSize(percentText, 20);
if (currFontSize < minFontSize) minFontSize = currFontSize;
//percentText.height = percentText.textHeight;
//percentText.x = unscaledWidth-percentStartWidth/2-percentText.width/2;
//percentText.y = ry-percentText.height/2;
}
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
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) {
curRowHeight += 1;
vp--;
}
ry += curRowHeight/2;
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
answerText = TextField(answerArray[j]);
findFontSize(answerText, minFontSize);
answerText.width = answerText.textWidth+4;
answerText.height = answerText.textHeight+4;
answerText.y = ry-answerText.height/2;
if (answerText.width > maxAnswerWidth) maxAnswerWidth = answerText.width;
percentText = TextField(percentArray[j]);
findFontSize(percentText, minFontSize);
percentText.width = percentText.textWidth+4;
percentText.height = percentText.textHeight+4;
percentText.x = unscaledWidth - hpadding - 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 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
curRowHeight = avgRowHeight;
if (j%2==0 && vp > 0) {
curRowHeight += 1;
vp--;
}
ry += curRowHeight/2;
//ry = curRowHeight * (j+0.5) + vpadding*(j+1);
// draw rect
var rectWidth:int = maxBarWidth*(_data[j].v/largestVal);
graphics.drawRect(barStartX, ry-curRowHeight/2, rectWidth, curRowHeight);
// add vote count in middle of rect
countText = _textFields[currTFIdx++]; // new TextField();
countText.text = _data[j].v;
countText.width = rectWidth;
countText.height = curRowHeight;
countText.textColor = 0xFFFFFF;
countText.selectable = false;
//addChild(countText);
findFontSize(countText, minFontSize);
countText.width = countText.textWidth+4;
countText.height = countText.textHeight+4;
countText.x = barStartX+rectWidth/2-countText.width/2;
countText.y = ry-countText.height/2;
}
graphics.endFill();
}
}
private function findFontSize(textField:TextField, defaultSize:Number):int {
var tFormat:TextFormat = new TextFormat();
tFormat.size = defaultSize;
tFormat.font = "arial";
tFormat.align = TextFormatAlign.CENTER;
textField.setTextFormat(tFormat);
var size:Number = defaultSize;
while((textField.textWidth+4 > textField.width || textField.textHeight+4 > textField.height) && size > 0) {
size = size - 1;
tFormat.size = size;
textField.setTextFormat(tFormat);
}
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);
var arrayEnd:Number = (ao.points as Array).length;
var startX:Number = denormalize(21.845575, parentWidth);
var startY:Number = denormalize(23.145401, parentHeight);
var width:Number = denormalize(46.516006, parentWidth) - startX;
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)]);
var arrayEnd:Number = (ao.points as Array).length;
var startX:Number = denormalize((ao.points as Array)[0], parentWidth);
var startY:Number = denormalize((ao.points as Array)[1], parentHeight);
var pwidth:Number = denormalize((ao.points as Array)[2], parentWidth);
var pheight:Number = denormalize((ao.points as Array)[3], parentHeight);
var answers:Array = ao.result as Array;
var ans:Array = new Array();
for (var j:int = 0; j < answers.length; j++) {
var ar:Object = answers[j];
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));
this.x = startX;
this.y = startY;
updateDisplayList(pwidth, pheight);
}
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 {
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.*">
<mx:Script>
<![CDATA[
import org.bigbluebutton.main.events.BBBEvent;
import org.bigbluebutton.main.events.ModuleStartedEvent;
import org.bigbluebutton.modules.present.events.AddOverlayCanvasEvent;
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}" />
</EventHandlers>
<EventHandlers type="{BBBEvent.RECONNECT_BIGBLUEBUTTON_SUCCEEDED_EVENT}" >
<MethodInvoker generator="{WhiteboardManager}" method="removeAnnotationsHistory" />
</EventHandlers>
<Injectors target="{WhiteboardManager}">
<ObjectBuilder generator="{WhiteboardModel}" cache="global" constructorArguments="{scope.dispatcher}"/>
<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");
var wb:Whiteboard = getWhiteboard(wbId);
if (wb != null) {
wb.clear();
if (wbId != null) {
var wb:Whiteboard = getWhiteboard(wbId);
if (wb != null) {
wb.clear();
_dispatcher.dispatchEvent(new WhiteboardUpdate(WhiteboardUpdate.CLEAR_ANNOTATIONS));
}
} else {
_whiteboards.removeAll();
_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 {
// LogUtil.debug("WB: received message " + messageName);
// trace("WB: received message " + messageName);
switch (messageName) {
case "WhiteboardRequestAnnotationHistoryReply":

View File

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

View File

@ -1,5 +1,6 @@
agnito:raphael@0.1.0
aldeed:simple-schema@1.3.2
aldeed:template-extension@3.4.3
amplify@1.0.0
arunoda:npm@0.2.6
autoupdate@1.2.1
@ -12,6 +13,7 @@ brentjanderson:winston-client@0.2.1
callback-hook@1.0.3
cfs:http-methods@0.0.27
check@1.0.5
chriswessels:hammer@3.1.1
clinical:nightwatch@2.0.1
coffeescript@1.0.6
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)
@colourToHex = (value) ->
hex = parseInt(value).toString(16)
hex = "0" + hex while hex.length < 6
"##{hex}"
hex = parseInt(value).toString(16)
hex = "0" + hex while hex.length < 6
"##{hex}"
# color can be a number (a hex converted to int) or a string (e.g. "#ffff00")
@formatColor = (color) ->
@ -34,6 +34,10 @@
@getTime = -> # returns epoch in ms
(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
Handlebars.registerHelper "amIInAudio", ->
BBB.amIInAudio()
@ -212,11 +216,55 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
$('.sl-left-drawer').addClass('hiddenInLandscape')
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 = ->
if $('.shield').hasClass('darken')
$('.shield').removeClass('darken')
else
if parseFloat($('.shield').css('opacity')) is 0.5 # triggered during a pan gesture
$('.shield').css('opacity', '')
if !$('.shield').hasClass('darken') and !$('.shield').hasClass('animatedShield')
$('.shield').addClass('darken')
else
$('.shield').removeClass('darken')
$('.shield').removeClass('animatedShield')
@removeFullscreenStyles = ->
$('#whiteboard-paper').removeClass('verticallyCentered')
@ -327,7 +375,7 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
@clearSessionVar = (callback) ->
amplify.store('authToken', null)
amplify.store('bbbServerVersion', null)
amplify.store('chatTabs', null)
amplify.store('chats', null)
amplify.store('dateOfBuild', null)
amplify.store('display_chatPane', null)
amplify.store('display_chatbar', null)
@ -351,9 +399,11 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
setInSession "display_chatbar", true
setInSession "display_whiteboard", 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()
setInSession "messageFontSize", Meteor.config.app.mobileFont
#if this is a mobile version of the client
else
setInSession "messageFontSize", Meteor.config.app.desktopFont
setInSession 'display_slidingMenu', false
@ -363,8 +413,34 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
else
setInSession 'display_usersList', 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
#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 = ->
document.title = "BigBlueButton #{BBB.getMeetingName() ? 'HTML5'}"
setDefaultSettings()
@ -386,7 +462,14 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
navigator.userAgent.match(/webOS/i)
@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
@isPortraitMobile = () ->
@ -397,8 +480,8 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
# Checks if the view is landscape and mobile device is being used
@isLandscapeMobile = () ->
isMobile() and
window.matchMedia('(orientation: landscape)').matches and # browser is landscape
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches # device is landscape
window.matchMedia('(orientation: landscape)').matches and # browser 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
@isOnlyOnePanelOpen = () ->
@ -417,3 +500,22 @@ Handlebars.registerHelper 'whiteboardSize', (section) ->
return 'IE'
else
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()
$("#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 = ->
$('button[rel=tooltip]').tooltip()

View File

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

View File

@ -11,9 +11,21 @@
height: 45px;
min-height: 45px;
.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;
margin: 0;
height: 45px;
outline: none;
min-width: 50px;
}
span {
position: relative;
@ -29,23 +41,40 @@
}
}
.privateChatName {
width: calc(~'100% - 105px');
min-width: 101px;
text-align: right;
float: right;
padding: 0.65rem 1.25rem 0.6rem 0.3rem;
font-family: "Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;
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 {
@media @landscape {
-webkit-flex: 3 3 30%;
-moz-flex: 3 3 30%;
-ms-flex: 3 3 30%;
flex: 3 3 30%;
-webkit-flex: 1 1;
-moz-flex: 1 1;
-ms-flex: 1 1;
flex: 1 1;
height: 100%;
border-top: 0px;
border-right: 0px;
border-top-left-radius: 0px;
overflow: hidden;
}
order: 3;
}
@ -136,23 +165,19 @@
}
}
.gotUnreadMail {
background: extract(@yellow, 2) !important;
}
#newMessageInput {
display: block;
float: left;
width: 75%;
resize: none;
padding-top: 5px;
padding-top: 0px;
padding-bottom: 0px;
border:1px solid extract(@lightGrey, 3);
margin: 0px;
@media @landscape {
height: 40px; /* same height as send button */
height: 100%; /* same height as send button */
}
@media @mobile-portrait {
font-size: 4vw;
@ -173,22 +198,24 @@
position: relative;
background: extract(@white, 1);
padding: 0;
border-top: 1px solid #E5E5E5;
border-top: 1px solid extract(@lightGrey, 3);
}
#sendMessageButton {
width: 20%; /* 75% for the message input, 5% margin between the two */
color: extract(@black, 1);
background-color: extract(@white, 1);
background-color: #3896D3;
font-weight: bold;
height: 50px;
margin: 0px;
border: 1px solid extract(@lightGrey, 3);
@media @desktop-portrait {
width: 20%; /* 75% for the message input, 5% margin between the two */
font-size: 30px;
height: 60px;
&:hover {
background: #3A82D4;
}
}
@media @mobile-portrait {
width: 15vw;
@ -203,6 +230,11 @@
@media @landscape {
height: 50px;
padding: 0px;
position: absolute;
bottom: 10px;
&:hover {
background: #3A82D4;
}
}
}
@ -215,3 +247,16 @@
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
}
@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 {
border-bottom: 0px;
&.gradientBar {
border-bottom: 1px;
@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));
}
.btn {
@ -45,13 +78,15 @@ body {
top: 0 !important;
padding-left: 1% !important;
padding-right: 1% !important;
background-color: white;
border-bottom: 1px solid extract(@lightGrey, 1);
}
@media @mobile-portrait-with-keyboard, @mobile-portrait {
height: 100px !important;
width: 10%;
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 {
.icon-bar {
margin-left: auto;
@ -71,10 +106,9 @@ body {
&.toggleUserlistButton, &.toggleMenuButton {
background: transparent;
}
span { background-color: white; }
span { background-color: #469DCF; }
}
@media @landscape {
min-width: 768px;
height: 50px;
}
@media @mobile-portrait-with-keyboard, @mobile-portrait {
@ -87,28 +121,35 @@ body {
}
.navbarTitle {
color: extract(@white, 1);
font-weight: bold;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@media @landscape {
font-size: 16px;
width: 30% !important;
}
position: absolute;
@media @mobile-portrait-with-keyboard, @mobile-portrait {
font-size: 30px;
padding-top: 30px;
padding-left: 5px;
color: extract(@white, 1);
font-size: 35px;
height: 72px;
width: 70%;
max-width: 70%;
margin-left: auto;
margin-right: auto;
height: 110px;
padding-top: 55px; // half the height
padding-left: 30px;
}
@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;
width: calc(~'100% - 102.4px');
height: 50px;
padding-top: 25px; // half the height
padding-left: 15px;
}
}
}
@ -141,30 +182,32 @@ body {
}
.meetingTitle {
text-align: center;
font-weight: bold;
line-height: 2em;
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));
padding-left: 140px;
padding-top: 20px;
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 {
border-bottom: 1px solid extract(@lightGrey, 1);
color: extract(@darkGrey, 1);
padding-bottom: 5px;
padding-left: 10px;
padding-top: 5px;
}
@media @desktop-portrait, @landscape {
font-size: 18px;
height: 50px;
}
@media @mobile-portrait, @mobile-portrait-with-keyboard {
font-size: 4vw;
height: 110px;
}
}
#container {
@ -272,10 +315,10 @@ body {
#main {
@media @landscape {
-webkit-flex: 4 4 80%;
-moz-flex: 4 4 80%;
-ms-flex: 4 4 80%;
flex: 4 4 80%;
-webkit-flex: 1 1;
-moz-flex: 1 1;
-ms-flex: 1 1;
flex: 1 1;
height: 100%;
}
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
@ -303,6 +346,12 @@ body {
.signOutIcon {
@media @landscape {
&:hover {
color: #2A5E7C;
}
&:focus {
color: #2A5E7C;
}
height: 28px;
width: 75px;
margin-left: 10px;
@ -333,8 +382,18 @@ body {
z-index: 1001;
}
.toggleUserlistButton {
outline: none;
}
.settingsIcon {
@media @landscape {
&:hover {
color: #2A5E7C;
}
&:focus {
color: #2A5E7C;
}
width: 57px;
margin-right: 5px;
}
@ -459,7 +518,7 @@ body {
z-index: 1000;
position: fixed;
left: -400px;
width: 400px;
width: 400px !important; // overrides any width value set manually in landscape
&.sl-left-drawer-out {
left: 0px;
}
@ -478,10 +537,7 @@ body {
.sl-left-drawer {
height: 100%;
@media @landscape {
-webkit-flex: 1 1 20%;
-moz-flex: 1 1 20%;
-ms-flex: 1 1 20%;
flex: 1 1 20%;
width: 300px;
}
}
@ -514,7 +570,7 @@ body {
@media @landscape {
display: none;
}
&:not(.darken) {
&:not(.darken):not(.animatedShield) {
display: none;
}
}
@ -532,3 +588,28 @@ body {
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 "mixins";
.usericon {
font-size: 16px;
.statusIcon {
font-size: 18px;
}
#usericons {
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 {
cursor: pointer;
line-height: 1.1;
float:left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 60%;
width: calc(~'100% - 130px');
@media @landscape {
height: 20px;
height: 27px;
font-size: 4.5mm;
}
@media @desktop-portrait {
height: 25px;
height: 27px;
font-size: 4.5mm;
}
@media @mobile-portrait, @mobile-portrait-with-keyboard {
@ -34,7 +56,6 @@
border-left: 0px;
border-top: 0px;
border-top-right-radius: 0px;
background-color: #FAFAFA;
}
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
width: 100%;
@ -62,15 +83,24 @@
}
#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 {
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 {
span:hover {
/*background-color: #EEEEEE;*/
background: transparent;
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
background: transparent;
}
}
margin-top: 10px;
&:first-child { margin-top: 0px; }
@ -103,17 +133,13 @@
font-weight: bold;
}
.closeUserlistIcon {
float: right;
margin-right: 5px;
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
display: none;
}
.closeSettings {
@media @landscape {
color: #aaa;
font-style: normal;
font-size: 30px;
.toggleUserlistButton, .toggleMenuButton {
span {
@media @landscape, @desktop-portrait {
width: 26px;
margin-left: auto;
margin-right: auto;
height: 8%;
}
}
}
@ -128,3 +154,18 @@
.muteIcon .ion-ios-mic-off:hover:before {
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-left: 0px;
border-right: 0px;
-webkit-order: 2;
order: 2;
-webkit-flex: 7 7 70%;
-moz-flex: 7 7 70%;
-ms-flex: 7 7 70%;
flex: 7 7 70%;
min-width: 50%;
border-top-left-radius: 0;
border-top-right-radius: 0;
width: 70%;
height: 100%;
}
@media @mobile-portrait-with-keyboard, @desktop-portrait, @mobile-portrait {
-webkit-order: 1;
order: 1;
width: 100%;
width: 100% !important; // overrides any width value set manually in landscape
}
&:-webkit-full-screen {
width: 100%;
@ -126,6 +122,7 @@
}
.switchSlideButton {
outline: none;
width: 50px;
height: 100%;
margin-bottom: 0;
@ -200,11 +197,12 @@
margin-bottom: 0;
padding: 0;
border-radius: 50%;
background: rgba(0, 0, 0, 0.2);
background: #3896D3;
&:hover {
background: rgba(0, 0, 0, 0.4);
background: #3A82D4;
}
&:focus {
background: #3896D3;
outline:0;
}
i {

View File

@ -22,13 +22,15 @@
else
chatMessage.message?.from_userid
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
populateChatTabs(chatMessage) # check if we need to open a new tab
populateNotifications(chatMessage) # check if we need to show a new notification
destinationTab = findDestinationTab()
if destinationTab isnt getInSession "inChatWith"
setInSession 'chatTabs', getInSession('chatTabs').map((tab) ->
tab.gotMail = true if tab.userId is destinationTab
setInSession 'chats', getInSession('chats').map((tab) ->
if tab.userId is destinationTab
tab.gotMail = true
tab.number++
tab
)
comp.stop()
@ -120,22 +122,51 @@ Template.chatbar.helpers
return Meteor.Users.findOne({userId: getInSession('inChatWith')})?
# 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
'click .toPublic': (event) ->
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
Template.message.rendered = ->
$('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight)
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
'click #sendMessageButton': (event) ->
$('#sendMessageButton').blur()
sendMessage()
adjustChatInputHeight()
'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))
@ -152,6 +183,11 @@ Template.chatInput.events
$('#newMessageInput').val("")
return false
Template.chatInputControls.rendered = ->
$('#newMessageInput').on('keydown paste cut', () -> setTimeout(() ->
adjustChatInputHeight()
, 0))
Template.message.helpers
sanitizeAndFormat: (str) ->
if typeof str is 'string'

View File

@ -2,13 +2,7 @@
<div id="{{id}}" {{visibility name}} class="component">
<div class="chatBodyContainer">
{{#if inPrivateChat}}
<div class="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>
{{> privateChatTab}}
{{/if}}
<div id="chatbody">
<ul class="chat" {{messageFontSize}}>
@ -27,6 +21,16 @@
</div>
</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">
<div id="chatInput" class="chat-input-wrapper">
{{#if inPrivateChat}}

View File

@ -46,12 +46,12 @@
<div class="bar bottomBar">
<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>
</template>
<template name="logoutModal">
<p>Are you sure you want to logout?</p>
{{> makeButton id="yes" btn_class="logoutButton" rel="tooltip" title="Logout" text="Yes"}}
{{> makeButton id="no" btn_class="logoutButton" rel="tooltip" title="Logout" text="No"}}
{{> makeButton id="yes" btn_class="logoutButton" rel="tooltip" title="Logout" label="Yes"}}
{{> makeButton id="no" btn_class="logoutButton" rel="tooltip" title="Logout" label="No"}}
</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">
<button type="submit" id="{{id}}" class="btn {{btn_class}}" {{isDisabled}} rel="{{rel}}" data-placement="{{data_placement}}" title="{{title}}" style="{{style}}">
{{#if text}}
<span>{{text}}</span>
{{else}}
{{#if i_class}}
<i class="{{i_class}}"></i><span>{{label}}</span>
{{/if}}
<button type="submit" id="{{id}}" class="btn {{btn_class}}" {{isDisabled}} rel="{{rel}}" data-placement="{{data_placement}}" title="{{title}}" style="{{style}}">
{{#if notification}}
{{#if hasGotUnreadMail notification }}
<div class="{{getNotificationClass notification}}">{{getNumberOfUnreadMessages notification}}</div>
{{/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>

View File

@ -26,5 +26,49 @@ Template.displayUserIcons.helpers
Template.usernameEntry.events
'click .usernameEntry': (event) ->
userIdSelected = @.userId
unless userIdSelected is null or userIdSelected is BBB.getCurrentUser()?.userId
setInSession "inChatWith", userIdSelected
unless userIdSelected is null
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>
</span>
{{/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 name="usernameEntry">
{{#if isCurrentUser userId}}
<span class="userCurrent usernameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}} (you)">
{{user.name}} {{#if user.presenter}} (presenter) {{/if}} (you)
</span>
{{else}}
<span class="usernameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}}">
{{user.name}} {{#if user.presenter}} (presenter) {{/if}}
</span>
{{/if}}
<div class="status">
{{#if user.raise_hand}}
{{#if isCurrentUser userId}}
<span rel="tooltip" data-placement="bottom" title="Lower your hand">
<i class="icon ion-android-hand statusIcon"></i>
</span>
{{else}}
<span rel="tooltip" data-placement="bottom" title="{{user.name}} has raised their hand">
<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 name="userItem">

View File

@ -5,6 +5,14 @@ Template.usersList.helpers
return "Users: #{numberUsers}"
# do not display the label if there are just a few users
Template.usersList.events
"click .closeUserlistIcon": (event, template) ->
toggleUsersList()
Template.usersList.rendered = ->
$('.sl-left-drawer').resizable
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}}">
<h3 class="meetingTitle">
{{getMeetingName}}
<span class="closeUserlistIcon">
<a href="#">
<i class="closeSettings close-reveal-modal">&#215;</i>
</a>
</span>
</h3>
<div id="user-contents">
<div class="userlist ScrollableWindowY">

View File

@ -6,7 +6,8 @@ Template.slide.rendered = ->
setInSession 'slideOriginalHeight', this.height
$(window).resize( ->
# 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?
createWhiteboardPaper (wpm) ->

View File

@ -46,3 +46,18 @@ Template.whiteboard.events
'click .lowerHand': (event) ->
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">
<div id="{{id}}" {{visibility name}} class="component gradientBar">
<div id="{{id}}" {{visibility name}} class="component">
{{#each getCurrentSlide}}
{{> slide}}
{{/each}}

View File

@ -103,8 +103,12 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
r.setPlaybackFormat(rec.playback.format.text());
r.setPlaybackLink(rec.playback.link.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>();
rec.meta.children().each { anode ->
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) {
// determine if client is disconnected _PTS_272_
client.stop();
/*
if ( ExitCode.CONNECTION_TO_DESKSHARE_SERVER_DROPPED == reason ){
JFrame pframe = new JFrame("Desktop Sharing Disconneted");
if ( null != pframe ){
@ -215,6 +217,7 @@ public class DeskShareApplet extends JApplet implements ClientListener {
}else{
client.stop();
}
*/
}
}