diff --git a/README.md b/README.md index bcd9aad86f0c83bf29c400c3449a0a44a2525a96..0f59ec0810e5e606e30e3d3012cdd1edc48d8b2c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,36 @@ BigBlueButton ============= -BigBlueButton is an open source web conferencing system for on-line learning. +BigBlueButton is an open source web conferencing system for on-line learning. We believe that every student with a web browser should have access to a high-quality on-line learning experience. We intend to make that possible with BigBlueButton. -We believe that every student with a web browser should have access to a high-quality on-line learning experience. We intend to make that possible with BigBlueButton. - -BigBlueButton supports real-time sharing of slides (PDF and any document readable by LibreOffice), webcams, whiteboard, chat, voice over IP (using FreeSWITCH), and desktop. It can record and playback all content shared in a session. The use cases for BigBlueButton are +BigBlueButton supports real-time sharing of slides (PDF and any document readable by OpenOffice), webcams, whiteboard, chat, voice over IP (using FreeSWITCH), and desktop. It can record and playback all content shared in a session. The BigBlueButton project is is supported by a [community of developers] (http://www.bigbluebutton.org/support/) that care about good design and a streamlined user experience. +The use cases for BigBlueButton are * One-to-one on-line tutoring * Small group collaboration - * On-line classes (50 or less) + * On-line classes (25 or less) + +BigBlueButton is built on the [shoulders of giants] (http://www.bigbluebutton.org/components/). + +Getting Started +=============== +All the core information is at the [Google Code project page] (http://code.google.com/p/bigbluebutton/). Here are a few quick links + +If you want to see how BigBlueButton works, check out these two videos + * [presenter] (http://www.bigbluebutton.org/videos/) + * [viewer] (http://www.bigbluebutton.org/videos/) + +After watching the videos, if you want to immediately try out BigBlueButton, the project maintains a live [demo server] (http://demo.bigbluebutton.org) that anyone can join. + +To quickly get started running your own BigBlueButton server + * [Install on Ubunt 10.04 64-bit] (http://code.google.com/p/bigbluebutton/wiki/InstallationUbuntu) + * [Download the BigBlueButton 0.81 Virtual Machine] (http://code.google.com/p/bigbluebutton/wiki/BigBlueButtonVM) + +See also [history of project] (http://www.bigbluebutton.org/history/). + +If you like the work we've done with BigBlueButton and would like to contribute improvemets to the project, see [Contribute to BigBlueButton] (http://code.google.com/p/bigbluebutton/wiki/FAQ#Contributing_to_BigBlueButton). -For more information on the latest release -- including installation instructions, demo server, API, and overview of architecture -- see [http://docs.bigbluebutton.org/](http://docs.bigbluebutton.org/). +License +======= +BigBlueButton is licensed under the LGPL 3.0. BigBlueButton and the BigBlueButton Logo are trademarks of [BigBlueButton Inc] (http://bigbluebutton.org) . diff --git a/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java b/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java index 147e96fc7ac3214e0ceef55ded889b7b8f7eed5e..3ae50c5bb609ab6e4a6faed6038264639fd005b1 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java +++ b/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java @@ -25,14 +25,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.Timer; import java.util.TimerTask; +import org.apache.commons.lang3.StringUtils; import org.bigbluebutton.app.video.h263.H263Converter; import org.red5.logging.Red5LoggerFactory; import org.red5.server.adapter.MultiThreadedApplicationAdapter; import org.red5.server.api.IConnection; import org.red5.server.api.Red5; -import org.red5.server.api.scope.IScope; import org.red5.server.api.scope.IBasicScope; import org.red5.server.api.scope.IBroadcastScope; +import org.red5.server.api.scope.IScope; import org.red5.server.api.scope.ScopeType; import org.red5.server.api.stream.IBroadcastStream; import org.red5.server.api.stream.IPlayItem; @@ -41,7 +42,6 @@ import org.red5.server.api.stream.IStreamListener; import org.red5.server.api.stream.ISubscriberStream; import org.red5.server.stream.ClientBroadcastStream; import org.slf4j.Logger; -import org.apache.commons.lang3.StringUtils; import com.google.gson.Gson; public class VideoApplication extends MultiThreadedApplicationAdapter { @@ -53,15 +53,15 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { private boolean recordVideoStream = false; private EventRecordingService recordingService; private final Map<String, IStreamListener> streamListeners = new HashMap<String, IStreamListener>(); - - private Map<String, CustomStreamRelay> remoteStreams = new ConcurrentHashMap<String, CustomStreamRelay>(); - private Map<String, Integer> listenersOnRemoteStream = new ConcurrentHashMap<String, Integer>(); + + private Map<String, CustomStreamRelay> remoteStreams = new ConcurrentHashMap<String, CustomStreamRelay>(); + private Map<String, Integer> listenersOnRemoteStream = new ConcurrentHashMap<String, Integer>(); // Proxy disconnection timer private Timer timer; // Proxy disconnection timeout private long relayTimeout; - + private final Map<String, H263Converter> h263Converters = new HashMap<String, H263Converter>(); @Override @@ -191,16 +191,14 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { super.streamPublishStart(stream); } - public IBroadcastScope getBroadcastScope(IScope scope, String name) { - IBasicScope basicScope = scope.getBasicScope(ScopeType.BROADCAST, name); - if (!(basicScope instanceof IBroadcastScope)) { - return null; - } else { - return (IBroadcastScope) basicScope; + IBasicScope basicScope = scope.getBasicScope(ScopeType.BROADCAST, name); + if (basicScope instanceof IBroadcastScope) { + return (IBroadcastScope) basicScope; + } else { + return null; + } } -} - @Override public void streamBroadcastStart(IBroadcastStream stream) { @@ -220,14 +218,11 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { private Long genTimestamp() { return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); } - - private boolean isH263Stream(ISubscriberStream stream) { - String streamName = stream.getBroadcastStreamPublishName(); - if(streamName.startsWith(H263Converter.H263PREFIX)) { - return true; - } - return false; - } + + private boolean isH263Stream(ISubscriberStream stream) { + String streamName = stream.getBroadcastStreamPublishName(); + return streamName.startsWith(H263Converter.H263PREFIX); + } @Override public void streamBroadcastClose(IBroadcastStream stream) { @@ -258,10 +253,10 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { recordingService.record(scopeName, event); } - if(h263Converters.containsKey(stream.getName())) { - // Stop converter - h263Converters.remove(stream.getName()).stopConverter(); - } + if (h263Converters.containsKey(stream.getName())) { + // Stop converter + h263Converters.remove(stream.getName()).stopConverter(); + } } /** @@ -290,15 +285,16 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { public void setEventRecordingService(EventRecordingService s) { recordingService = s; } - + public void setRelayTimeout(long timeout) { this.relayTimeout = timeout; } - @Override - public void streamPlayItemPlay(ISubscriberStream stream, IPlayItem item, boolean isLive) { - // log w3c connect event - String streamName = item.getName(); - streamName = streamName.replaceAll(H263Converter.H263PREFIX, ""); + + @Override + public void streamPlayItemPlay(ISubscriberStream stream, IPlayItem item, boolean isLive) { + // log w3c connect event + String streamName = item.getName(); + streamName = streamName.replaceAll(H263Converter.H263PREFIX, ""); if(isH263Stream(stream)) { log.trace("Detected H263 stream request [{}]", streamName); @@ -315,7 +311,7 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { } } } - if(streamName.contains("/")) { + if(streamName.contains("/")) { synchronized(remoteStreams) { if(remoteStreams.containsKey(streamName) == false) { String[] parts = streamName.split("/"); @@ -346,9 +342,8 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { log.info("W3C x-category:stream x-event:play c-ip:{} x-sname:{} x-name:{}", new Object[] { Red5.getConnectionLocal().getRemoteAddress(), stream.getName(), item.getName() }); } - @Override - public void streamSubscriberClose(ISubscriberStream stream) { - + @Override + public void streamSubscriberClose(ISubscriberStream stream) { String streamName = stream.getBroadcastStreamPublishName(); streamName = streamName.replaceAll(H263Converter.H263PREFIX, ""); @@ -382,7 +377,7 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { } } } - } + } private final class DisconnectProxyTask extends TimerTask { // Stream name that should be disconnected diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonApplication.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonApplication.java index ef1602b6cddc74a32690a647aba53c0d502c747e..206fe69b5e4ea39b7838e5ffc546bef8f983ac13 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonApplication.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonApplication.java @@ -91,14 +91,12 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter { @Override public boolean roomStart(IScope room) { - connInvokerService.addScope(room.getName(), room); return super.roomStart(room); } @Override public void roomStop(IScope room) { recorderApplication.destroyRecordSession(room.getName()); - connInvokerService.removeScope(room.getName()); super.roomStop(room); } @@ -154,8 +152,6 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter { bbbGW.initAudioSettings(room, internalUserID, muted); - boolean success = connInvokerService.addConnection(internalUserID, connection); - String meetingId = bbbSession.getRoom(); String connType = getConnectionType(Red5.getConnectionLocal().getType()); @@ -182,7 +178,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter { log.info("User joining bbb-apps: data={}", logStr); - return success && super.roomConnect(connection, params); + return super.roomConnect(connection, params); } @@ -204,8 +200,6 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter { String clientId = Red5.getConnectionLocal().getClient().getId(); log.info("***** " + APP + "[clientid=" + clientId + "] disconnnected from " + remoteHost + ":" + remotePort + "."); - connInvokerService.removeConnection(getBbbSession().getInternalUserID()); - BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION); String meetingId = bbbSession.getRoom(); diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/meeting/messaging/red5/ConnectionInvokerService.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/meeting/messaging/red5/ConnectionInvokerService.java index 2ed65996ae4ba4cb0405949c5dbaa23f8519ff05..9988ef9e794f3955fb2725a1639ef42f63455490 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/meeting/messaging/red5/ConnectionInvokerService.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/meeting/messaging/red5/ConnectionInvokerService.java @@ -49,38 +49,17 @@ public class ConnectionInvokerService { private BlockingQueue<ClientMessage> messages; - private ConcurrentHashMap<String, IConnection> connections; - private ConcurrentHashMap<String, IScope> scopes; - private volatile boolean sendMessages = false; private IScope bbbAppScope; public ConnectionInvokerService() { messages = new LinkedBlockingQueue<ClientMessage>(); - - connections = new ConcurrentHashMap<String, IConnection>(); - scopes = new ConcurrentHashMap<String, IScope>(); } public void setAppScope(IScope scope) { bbbAppScope = scope; } - public boolean addConnection(String id, IConnection conn) { - if (connections == null) { - System.out.println("Connections is null!!!!"); - return false; - } - if (id == null) { - System.out.println("CONN ID IS NULL!!!"); - - } - if (conn == null) { - System.out.println("CONN IS NULL"); - } - return connections.putIfAbsent(id, conn) == null; - } - public void start() { sendMessages = true; Runnable sender = new Runnable() { @@ -105,18 +84,6 @@ public class ConnectionInvokerService { sendMessages = false; } - public void removeConnection(String id) { - connections.remove(id); - } - - public void addScope(String id, IScope scope) { - scopes.putIfAbsent(id, scope); - } - - public void removeScope(String id) { - scopes.remove(id); - } - public void sendMessage(final ClientMessage message) { messages.offer(message); } diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/presentation/ConversionUpdatesProcessor.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/presentation/ConversionUpdatesProcessor.java index 78c58fa1023f7249ca43181de434980d7c57f227..33f022c4037b4d5789e54afedaed3f48605c732c 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/presentation/ConversionUpdatesProcessor.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/presentation/ConversionUpdatesProcessor.java @@ -19,8 +19,7 @@ package org.bigbluebutton.conference.service.presentation; import org.slf4j.Logger; -import org.red5.logging.Red5LoggerFactory; - +import org.red5.logging.Red5LoggerFactory; public class ConversionUpdatesProcessor { private static Logger log = Red5LoggerFactory.getLogger(ConversionUpdatesProcessor.class, "bigbluebutton"); diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java index 8f80bdb8bd8eb1f8a8d9a0cdac0c9151e3830b0e..47bc7b68c60f50e5301787ac2ac46fa8150a62b3 100644 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java @@ -20,6 +20,7 @@ public class WhiteboardListener implements MessageHandler{ @Override public void handleMessage(String pattern, String channel, String message) { if (channel.equalsIgnoreCase(MessagingConstants.TO_WHITEBOARD_CHANNEL)) { + System.out.println("AntonChannel=(whiteboard)" + channel); JsonParser parser = new JsonParser(); JsonObject obj = (JsonObject) parser.parse(message); @@ -43,7 +44,7 @@ public class WhiteboardListener implements MessageHandler{ else { System.out.println("\n DID NOT FIND A whiteboardID \n"); } - System.out.println("\n user<" + requesterID + "> requested the shapes.\n"); + System.out.println("\n\n\n user<" + requesterID + "> requested the shapes.\n\n"); } } } diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala index 3b3702d983e303d6aa4eacd0e8dfdd68e82e9f00..9980fda2e5471b0db2b45cab71cc8833279ed060 100755 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala @@ -158,9 +158,6 @@ class BigBlueButtonActor(outGW: MessageOutGateway) extends Actor with LogHelper //send chat history this ! (new GetChatHistoryRequest(id, "nodeJSapp", "nodeJSapp")) - - //send lock settings - this ! (new GetLockSettings(id, "nodeJSapp")) } outGW.send(new GetAllMeetingsReply(resultArray)) diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonInGW.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonInGW.scala index a360550d2f7a40b186765e0a48086134121146cc..ffcede9aed2188cf8ccc78526269f17c612738b9 100755 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonInGW.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonInGW.scala @@ -197,14 +197,10 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen } def userConnectedToGlobalAudio(voiceConf: String, userid: String, name: String) { - // we are required to pass the meeting_id as first parameter (just to satisfy trait) - // but it's not used anywhere. That's why we pass voiceConf twice instead bbbGW.accept(new UserConnectedToGlobalAudio(voiceConf, voiceConf, userid, name)) } def userDisconnectedFromGlobalAudio(voiceConf: String, userid: String, name: String) { - // we are required to pass the meeting_id as first parameter (just to satisfy trait) - // but it's not used anywhere. That's why we pass voiceConf twice instead bbbGW.accept(new UserDisconnectedFromGlobalAudio(voiceConf, voiceConf, userid, name)) } diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala index 96dd63e40b910ab637d829aa32d3dea912fde6da..9a7ba08db674443002dd98e4faf95fa3aa18b697 100755 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala @@ -93,7 +93,6 @@ class MeetingActor(val meetingID: String, val externalMeetingID: String, val mee case msg: MuteUserRequest => handleMuteUserRequest(msg) case msg: EjectUserFromVoiceRequest => handleEjectUserRequest(msg) case msg: SetLockSettings => handleSetLockSettings(msg) - case msg: GetLockSettings => handleGetLockSettings(msg) case msg: LockUserRequest => handleLockUserRequest(msg) case msg: InitLockSettings => handleInitLockSettings(msg) case msg: InitAudioSettings => handleInitAudioSettings(msg) diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/layout/red5/LayoutClientMessageSender.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/layout/red5/LayoutClientMessageSender.scala index 190167883f8ae228613d8c8a3e876a0ce0da9362..9f1820498c362508273e6517d8f8a7e9ff7e52eb 100755 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/layout/red5/LayoutClientMessageSender.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/layout/red5/LayoutClientMessageSender.scala @@ -30,10 +30,10 @@ class LayoutClientMessageSender(service: ConnectionInvokerService) extends OutMe private def handleBroadcastLayoutEvent(msg: BroadcastLayoutEvent) { val message = new java.util.HashMap[String, Object]() message.put("locked", msg.locked:java.lang.Boolean); - message.put("setByUserID", msg.requesterID); + message.put("setByUserID", msg.setByUserID); message.put("layout", msg.layoutID); - msg.applyTo.filter(_.userID != msg.requesterID) foreach {u => + msg.applyTo foreach {u => var m = new DirectClientMessage(msg.meetingID, u.userID, "syncLayout", message); service.sendMessage(m); } diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala index 53334d73a55923b0191fbccff2ed0872d98da4f1..4311242bdd2a2337e919fd92e9e92c11763cf318 100644 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala @@ -82,11 +82,18 @@ trait UsersApp { //send the reply outGW.send(new ValidateAuthTokenReply(meetingID, msg.userId, msg.token, true, msg.correlationId, msg.sessionId)) + //send the list of users in the meeting + outGW.send(new GetUsersReply(meetingID, msg.userId, users.getUsers, msg.sessionId)) + + //send chat history + this ! (new GetChatHistoryRequest(meetingID, msg.userId, msg.userId)) + //join the user handleUserJoin(new UserJoining(meetingID, msg.userId, msg.token)) //send the presentation logger.info("ValidateToken success: mid=[" + meetingID + "] uid=[" + msg.userId + "]") + this ! (new GetPresentationInfo(meetingID, msg.userId, msg.userId)) } case None => { logger.info("ValidateToken failed: mid=[" + meetingID + "] uid=[" + msg.userId + "]") @@ -97,7 +104,7 @@ trait UsersApp { /** * Send a reply to BigBlueButtonActor to let it know this MeetingActor hasn't hung! * Sometimes, the actor seems to hang and doesn't anymore accept messages. This is a simple - * audit to check whether the actor is still alive. (ralam feb 25, 2015) + * audit to check whether the actor is still alive. (ralam feb 25, 2015) */ reply(new ValidateAuthTokenReply(meetingID, msg.userId, msg.token, false, msg.correlationId)) } @@ -147,14 +154,11 @@ trait UsersApp { case None => // do nothing } } - + def handleGetLockSettings(msg: GetLockSettings) { - //println("*************** Reply with current lock settings ********************") - - //reusing the existing handle for NewPermissionsSettings to reply to the GetLockSettings request - outGW.send(new NewPermissionsSetting(meetingID, msg.userId, permissions, users.getUsers)) + logger.info("Not implemented: handleGetLockSettings") } - + def handleSetLockSettings(msg: SetLockSettings) { // println("*************** Received new lock settings ********************") if (!permissionsEqual(msg.settings)) { @@ -316,7 +320,7 @@ trait UsersApp { if (u.presenter) { /* 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 = users.findAModerator() moderator.foreach { mod => diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/redis/UsersMessageToJsonConverter.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/redis/UsersMessageToJsonConverter.scala index ae70ada5d8b37fbde94695a615a4126f0fd8ef23..693c75c283e55dfeed45db62f5718537a3d8a4f2 100644 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/redis/UsersMessageToJsonConverter.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/redis/UsersMessageToJsonConverter.scala @@ -137,7 +137,7 @@ object UsersMessageToJsonConverter { payload.put(Constants.STATUS, msg.status) payload.put(Constants.VALUE, msg.value.toString) - val header = Util.buildHeader(MessageNames.USER_STATUS_CHANGED, msg.version, None) + val header = Util.buildHeader(MessageNames.USER_STATUS_CHANGED, msg.version, None) Util.buildJson(header, payload) } diff --git a/bigbluebutton-client/branding/default/style/css/BBBBlack.css b/bigbluebutton-client/branding/default/style/css/BBBBlack.css index 10d9f8d963f4e2a7fb5acba0b3bad44fc5f0f456..af1050468f696c7d82e06ae9a9ba8736debd3af6 100755 --- a/bigbluebutton-client/branding/default/style/css/BBBBlack.css +++ b/bigbluebutton-client/branding/default/style/css/BBBBlack.css @@ -46,7 +46,7 @@ ToolTip { paddingRight: 3; } -Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .bandwidthButtonStyle { +Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle { textIndent: 0; paddingLeft: 10; paddingRight: 10; @@ -643,11 +643,4 @@ MDIWindow { /*None of the following properties are overridden by the MDIWindow c fontFamily: Arial; fontSize: 20; fontWeight: bold; -} - -.bandwidthButtonStyle { - paddingTop: 0; - paddingBottom: 0; - height: 22; - icon: Embed('assets/images/bandwidth.png'); -} +} \ No newline at end of file diff --git a/bigbluebutton-client/locale/en_US/bbbResources.properties b/bigbluebutton-client/locale/en_US/bbbResources.properties index cc2875fd2db577c9c05e3d9b1aaca156fa169c88..b401d55f497bc61d5514f7146ab74deb73203a62 100755 --- a/bigbluebutton-client/locale/en_US/bbbResources.properties +++ b/bigbluebutton-client/locale/en_US/bbbResources.properties @@ -394,7 +394,7 @@ bbb.logout.appshutdown = The server app has been shut down bbb.logout.asyncerror = An Async Error occured bbb.logout.connectionclosed = The connection to the server has been closed bbb.logout.connectionfailed = The connection to the server has failed -bbb.logout.rejected = The connection to the server has been rejected\n\nIt may occur when you try to use the same session in multiple tabs in your browser +bbb.logout.rejected = The connection to the server has been rejected bbb.logout.invalidapp = The red5 app does not exist bbb.logout.unknown = Your client has lost connection with the server bbb.logout.guestkickedout = The moderator didn't allow you to join this meeting diff --git a/bigbluebutton-client/locale/pt_BR/bbbResources.properties b/bigbluebutton-client/locale/pt_BR/bbbResources.properties index ab62ac3bc1cf4bca773d760faf9e45853a2869f1..66d4bb885b31cf1a34650ef98555d02629854c04 100755 --- a/bigbluebutton-client/locale/pt_BR/bbbResources.properties +++ b/bigbluebutton-client/locale/pt_BR/bbbResources.properties @@ -393,7 +393,7 @@ bbb.logout.appshutdown = A aplicação no servidor foi interrompida bbb.logout.asyncerror = Um erro assÃncrono ocorreu bbb.logout.connectionclosed = A conexão com o servidor foi fechada bbb.logout.connectionfailed = A conexão com o servidor falhou -bbb.logout.rejected = A conexão com o servidor foi rejeitada\n\nIsso pode ocorrer quando você tenta utilizar a mesma sessão em múltiplas abas no seu navegador +bbb.logout.rejected = A conexão com o servidor foi rejeitada bbb.logout.invalidapp = O aplicativo red5 não existe bbb.logout.unknown = Seu cliente perdeu conexão com o servidor bbb.logout.guestkickedout = O moderador não permitiu sua entrada na sala diff --git a/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js b/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js index 0a5eb75c596717292650e718ad33f2be41ba081f..245ba860eefa73f063fd1990c5a5db9a85ff62ce 100755 --- a/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js +++ b/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js @@ -37,26 +37,15 @@ function webRTCCallback(message) { } } -function callIntoConference(voiceBridge, callback, isListenOnly) { - // root of the call initiation process from the html5 client - // Flash will not pass in the listen only field. For html5 it is optional. Assume NOT listen only if no state passed - if (isListenOnly == null) { - isListenOnly = false; - } - +function callIntoConference(voiceBridge, callback) { if (!callerIdName) { BBB.getMyUserInfo(function(userInfo) { console.log("User info callback [myUserID=" + userInfo.myUserID + ",myUsername=" + userInfo.myUsername + ",myAvatarURL=" + userInfo.myAvatarURL + ",myRole=" + userInfo.myRole + ",amIPresenter=" + userInfo.amIPresenter - + ",dialNumber=" + userInfo.dialNumber + ",voiceBridge=" + userInfo.voiceBridge - + ",isListenOnly=" + isListenOnly + "]."); + + ",dialNumber=" + userInfo.dialNumber + ",voiceBridge=" + userInfo.voiceBridge + "]."); userID = userInfo.myUserID; callerIdName = userInfo.myUserID + "-bbbID-" + userInfo.myUsername; - if (isListenOnly) { - //prepend the callerIdName so it is recognized as a global audio user - callerIdName = "GLOBAL_AUDIO_" + callerIdName; - } conferenceVoiceBridge = userInfo.voiceBridge if (voiceBridge === "9196") { voiceBridge = voiceBridge + conferenceVoiceBridge; @@ -64,7 +53,7 @@ function callIntoConference(voiceBridge, callback, isListenOnly) { voiceBridge = conferenceVoiceBridge; } console.log(callerIdName); - webrtc_call(callerIdName, voiceBridge, callback, isListenOnly); + webrtc_call(callerIdName, voiceBridge, callback); }); } else { if (voiceBridge === "9196") { @@ -72,7 +61,7 @@ function callIntoConference(voiceBridge, callback, isListenOnly) { } else { voiceBridge = conferenceVoiceBridge; } - webrtc_call(callerIdName, voiceBridge, callback, isListenOnly); + webrtc_call(callerIdName, voiceBridge, callback); } } @@ -220,30 +209,25 @@ function getUserMicMedia(getUserMicMediaSuccess, getUserMicMediaFailure) { } }; -function webrtc_call(username, voiceBridge, callback, isListenOnly) { +function webrtc_call(username, voiceBridge, callback) { if (!isWebRTCAvailable()) { callback({'status': 'failed', 'errorcode': 1003}); // Browser version not supported return; } - if (isListenOnly == null) { // assume NOT listen only unless otherwise stated - isListenOnly = false; - } - + var server = window.document.location.hostname; console.log("user " + username + " calling to " + voiceBridge); - + var makeCallFunc = function() { - // only make the call when both microphone and useragent have been created - // for listen only, stating listen only is a viable substitute for acquiring user media control - if ((isListenOnly||userMicMedia) && userAgent) - make_call(username, voiceBridge, server, callback, false, isListenOnly); + if (userMicMedia && userAgent) // only make the call when both microphone and useragent have been created + make_call(username, voiceBridge, server, callback, false); }; + if (!userAgent) { createUA(username, server, callback, makeCallFunc); } - // if the user requests to proceed as listen only (does not require media) or media is already acquired, - // proceed with making the call - if (isListenOnly || userMicMedia !== undefined) { + + if (userMicMedia !== undefined) { makeCallFunc(); } else { callback({'status':'mediarequest'}); @@ -260,15 +244,11 @@ function webrtc_call(username, voiceBridge, callback, isListenOnly) { } } -function make_call(username, voiceBridge, server, callback, recall, isListenOnly) { - if (isListenOnly == null) { - isListenOnly = false; - } - +function make_call(username, voiceBridge, server, callback, recall) { if (userAgent == null) { console.log("userAgent is still null. Delaying call"); var callDelayTimeout = setTimeout( function() { - make_call(username, voiceBridge, server, callback, recall, isListenOnly); + make_call(username, voiceBridge, server, callback, recall); }, 100); return; } @@ -277,7 +257,7 @@ function make_call(username, voiceBridge, server, callback, recall, isListenOnly console.log("Trying to make call, but UserAgent hasn't connected yet. Delaying call"); userAgent.once('connected', function() { console.log("UserAgent has now connected, retrying the call"); - make_call(username, voiceBridge, server, callback, recall, isListenOnly); + make_call(username, voiceBridge, server, callback, recall); }); return; } @@ -289,53 +269,16 @@ function make_call(username, voiceBridge, server, callback, recall, isListenOnly // Make an audio/video call: console.log("Setting options.. "); - - var options = {}; - if (isListenOnly) { - // create necessary options for a listen only stream - var stream = null; - // handle the web browser - // create a stream object through the browser separated from user media - if (typeof webkitMediaStream !== 'undefined') { - // Google Chrome - stream = new webkitMediaStream; - } else { - // Firefox - audioContext = new window.AudioContext; - stream = audioContext.createMediaStreamDestination().stream; - } - - options = { - media: { - stream: stream, // use the stream created above - render: { - remote: { - // select an element to render the incoming stream data - audio: document.getElementById('remote-media') - } - } - }, - // a list of our RTC Connection constraints - RTCConstraints: { - // our constraints are mandatory. We must received audio and must not receive audio - mandatory: { - OfferToReceiveAudio: true, - OfferToReceiveVideo: false - } - } - }; - } else { - options = { - media: { - stream: userMicMedia, - render: { - remote: { - audio: document.getElementById('remote-media') - } + var options = { + media: { + stream: userMicMedia, + render: { + remote: { + audio: document.getElementById('remote-media') } } - }; - } + } + }; callTimeout = setTimeout(function() { console.log('Ten seconds without updates sending timeout code'); @@ -489,7 +432,3 @@ function webrtc_hangup(callback) { function isWebRTCAvailable() { return SIP.WebRTC.isSupported(); } - -function getCallStatus() { - return currentSession; -} diff --git a/bigbluebutton-client/resources/prod/lib/deployJava.js b/bigbluebutton-client/resources/prod/lib/deployJava.js index 8cfb0afcc17cec02a76399ad7b9402745be9cde6..7d06cb5ca13b4a0b22cd28cbce230d4d5b8bd1b5 100755 --- a/bigbluebutton-client/resources/prod/lib/deployJava.js +++ b/bigbluebutton-client/resources/prod/lib/deployJava.js @@ -1,2 +1,2 @@ -/* Copy of the file found at http://www.java.com/js/deployJava.js - January 18, 2015 */ +/* Copy of the file found at http://www.java.com/js/deployJava.js - January 18, 2015 */ var deployJava=function(){var l={core:["id","class","title","style"],i18n:["lang","dir"],events:["onclick","ondblclick","onmousedown","onmouseup","onmouseover","onmousemove","onmouseout","onkeypress","onkeydown","onkeyup"],applet:["codebase","code","name","archive","object","width","height","alt","align","hspace","vspace"],object:["classid","codebase","codetype","data","type","archive","declare","standby","height","width","usemap","name","tabindex","align","border","hspace","vspace"]};var b=l.object.concat(l.core,l.i18n,l.events);var m=l.applet.concat(l.core);function g(o){if(!d.debug){return}if(console.log){console.log(o)}else{alert(o)}}function k(p,o){if(p==null||p.length==0){return true}var r=p.charAt(p.length-1);if(r!="+"&&r!="*"&&(p.indexOf("_")!=-1&&r!="_")){p=p+"*";r="*"}p=p.substring(0,p.length-1);if(p.length>0){var q=p.charAt(p.length-1);if(q=="."||q=="_"){p=p.substring(0,p.length-1)}}if(r=="*"){return(o.indexOf(p)==0)}else{if(r=="+"){return p<=o}}return false}function e(){var o="//java.com/js/webstart.png";try{return document.location.protocol.indexOf("http")!=-1?o:"http:"+o}catch(p){return"http:"+o}}function n(p){var o="http://java.com/dt-redirect";if(p==null||p.length==0){return o}if(p.charAt(0)=="&"){p=p.substring(1,p.length)}return o+"?"+p}function j(q,p){var o=q.length;for(var r=0;r<o;r++){if(q[r]===p){return true}}return false}function c(o){return j(m,o.toLowerCase())}function i(o){return j(b,o.toLowerCase())}function a(o){if("MSIE"!=deployJava.browserName){return true}if(deployJava.compareVersionToPattern(deployJava.getPlugin().version,["10","0","0"],false,true)){return true}if(o==null){return false}return !k("1.6.0_33+",o)}var d={debug:null,version:"20120801",firefoxJavaVersion:null,myInterval:null,preInstallJREList:null,returnPage:null,brand:null,locale:null,installType:null,EAInstallEnabled:false,EarlyAccessURL:null,oldMimeType:"application/npruntime-scriptable-plugin;DeploymentToolkit",mimeType:"application/java-deployment-toolkit",launchButtonPNG:e(),browserName:null,browserName2:null,getJREs:function(){var t=new Array();if(this.isPluginInstalled()){var r=this.getPlugin();var o=r.jvms;for(var q=0;q<o.getLength();q++){t[q]=o.get(q).version}}else{var p=this.getBrowser();if(p=="MSIE"){if(this.testUsingActiveX("1.7.0")){t[0]="1.7.0"}else{if(this.testUsingActiveX("1.6.0")){t[0]="1.6.0"}else{if(this.testUsingActiveX("1.5.0")){t[0]="1.5.0"}else{if(this.testUsingActiveX("1.4.2")){t[0]="1.4.2"}else{if(this.testForMSVM()){t[0]="1.1"}}}}}}else{if(p=="Netscape Family"){this.getJPIVersionUsingMimeType();if(this.firefoxJavaVersion!=null){t[0]=this.firefoxJavaVersion}else{if(this.testUsingMimeTypes("1.7")){t[0]="1.7.0"}else{if(this.testUsingMimeTypes("1.6")){t[0]="1.6.0"}else{if(this.testUsingMimeTypes("1.5")){t[0]="1.5.0"}else{if(this.testUsingMimeTypes("1.4.2")){t[0]="1.4.2"}else{if(this.browserName2=="Safari"){if(this.testUsingPluginsArray("1.7.0")){t[0]="1.7.0"}else{if(this.testUsingPluginsArray("1.6")){t[0]="1.6.0"}else{if(this.testUsingPluginsArray("1.5")){t[0]="1.5.0"}else{if(this.testUsingPluginsArray("1.4.2")){t[0]="1.4.2"}}}}}}}}}}}}}if(this.debug){for(var q=0;q<t.length;++q){g("[getJREs()] We claim to have detected Java SE "+t[q])}}return t},installJRE:function(r,p){var o=false;if(this.isPluginInstalled()&&this.isAutoInstallEnabled(r)){var q=false;if(this.isCallbackSupported()){q=this.getPlugin().installJRE(r,p)}else{q=this.getPlugin().installJRE(r)}if(q){this.refresh();if(this.returnPage!=null){document.location=this.returnPage}}return q}else{return this.installLatestJRE()}},isAutoInstallEnabled:function(o){if(!this.isPluginInstalled()){return false}if(typeof o=="undefined"){o=null}return a(o)},isCallbackSupported:function(){return this.isPluginInstalled()&&this.compareVersionToPattern(this.getPlugin().version,["10","2","0"],false,true)},installLatestJRE:function(q){if(this.isPluginInstalled()&&this.isAutoInstallEnabled()){var r=false;if(this.isCallbackSupported()){r=this.getPlugin().installLatestJRE(q)}else{r=this.getPlugin().installLatestJRE()}if(r){this.refresh();if(this.returnPage!=null){document.location=this.returnPage}}return r}else{var p=this.getBrowser();var o=navigator.platform.toLowerCase();if((this.EAInstallEnabled=="true")&&(o.indexOf("win")!=-1)&&(this.EarlyAccessURL!=null)){this.preInstallJREList=this.getJREs();if(this.returnPage!=null){this.myInterval=setInterval("deployJava.poll()",3000)}location.href=this.EarlyAccessURL;return false}else{if(p=="MSIE"){return this.IEInstall()}else{if((p=="Netscape Family")&&(o.indexOf("win32")!=-1)){return this.FFInstall()}else{location.href=n(((this.returnPage!=null)?("&returnPage="+this.returnPage):"")+((this.locale!=null)?("&locale="+this.locale):"")+((this.brand!=null)?("&brand="+this.brand):""))}}return false}}},runApplet:function(p,u,r){if(r=="undefined"||r==null){r="1.1"}var t="^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?$";var o=r.match(t);if(this.returnPage==null){this.returnPage=document.location}if(o!=null){var q=this.getBrowser();if(q!="?"){if(this.versionCheck(r+"+")){this.writeAppletTag(p,u)}else{if(this.installJRE(r+"+")){this.refresh();location.href=document.location;this.writeAppletTag(p,u)}}}else{this.writeAppletTag(p,u)}}else{g("[runApplet()] Invalid minimumVersion argument to runApplet():"+r)}},writeAppletTag:function(r,w){var o="<"+"applet ";var q="";var t="<"+"/"+"applet"+">";var x=true;if(null==w||typeof w!="object"){w=new Object()}for(var p in r){if(!c(p)){w[p]=r[p]}else{o+=(" "+p+'="'+r[p]+'"');if(p=="code"){x=false}}}var v=false;for(var u in w){if(u=="codebase_lookup"){v=true}if(u=="object"||u=="java_object"||u=="java_code"){x=false}q+='<param name="'+u+'" value="'+w[u]+'"/>'}if(!v){q+='<param name="codebase_lookup" value="false"/>'}if(x){o+=(' code="dummy"')}o+=">";document.write(o+"\n"+q+"\n"+t)},versionCheck:function(p){var v=0;var x="^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?(\\*|\\+)?$";var y=p.match(x);if(y!=null){var r=false;var u=false;var q=new Array();for(var t=1;t<y.length;++t){if((typeof y[t]=="string")&&(y[t]!="")){q[v]=y[t];v++}}if(q[q.length-1]=="+"){u=true;r=false;q.length--}else{if(q[q.length-1]=="*"){u=false;r=true;q.length--}else{if(q.length<4){u=false;r=true}}}var w=this.getJREs();for(var t=0;t<w.length;++t){if(this.compareVersionToPattern(w[t],q,r,u)){return true}}return false}else{var o="Invalid versionPattern passed to versionCheck: "+p;g("[versionCheck()] "+o);alert(o);return false}},isWebStartInstalled:function(r){var q=this.getBrowser();if(q=="?"){return true}if(r=="undefined"||r==null){r="1.4.2"}var p=false;var t="^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?$";var o=r.match(t);if(o!=null){p=this.versionCheck(r+"+")}else{g("[isWebStartInstaller()] Invalid minimumVersion argument to isWebStartInstalled(): "+r);p=this.versionCheck("1.4.2+")}return p},getJPIVersionUsingMimeType:function(){for(var p=0;p<navigator.mimeTypes.length;++p){var q=navigator.mimeTypes[p].type;var o=q.match(/^application\/x-java-applet;jpi-version=(.*)$/);if(o!=null){this.firefoxJavaVersion=o[1];if("Opera"!=this.browserName2){break}}}},launchWebStartApplication:function(r){var o=navigator.userAgent.toLowerCase();this.getJPIVersionUsingMimeType();if(this.isWebStartInstalled("1.7.0")==false){if((this.installJRE("1.7.0+")==false)||((this.isWebStartInstalled("1.7.0")==false))){return false}}var u=null;if(document.documentURI){u=document.documentURI}if(u==null){u=document.URL}var p=this.getBrowser();var q;if(p=="MSIE"){q="<"+'object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" '+'width="0" height="0">'+"<"+'PARAM name="launchjnlp" value="'+r+'"'+">"+"<"+'PARAM name="docbase" value="'+u+'"'+">"+"<"+"/"+"object"+">"}else{if(p=="Netscape Family"){q="<"+'embed type="application/x-java-applet;jpi-version='+this.firefoxJavaVersion+'" '+'width="0" height="0" '+'launchjnlp="'+r+'"'+'docbase="'+u+'"'+" />"}}if(document.body=="undefined"||document.body==null){document.write(q);document.location=u}else{var t=document.createElement("div");t.id="div1";t.style.position="relative";t.style.left="-10000px";t.style.margin="0px auto";t.className="dynamicDiv";t.innerHTML=q;document.body.appendChild(t)}},createWebStartLaunchButtonEx:function(q,p){if(this.returnPage==null){this.returnPage=q}var o="javascript:deployJava.launchWebStartApplication('"+q+"');";document.write("<"+'a href="'+o+"\" onMouseOver=\"window.status=''; "+'return true;"><'+"img "+'src="'+this.launchButtonPNG+'" '+'border="0" /><'+"/"+"a"+">")},createWebStartLaunchButton:function(q,p){if(this.returnPage==null){this.returnPage=q}var o="javascript:"+"if (!deployJava.isWebStartInstalled(""+p+"")) {"+"if (deployJava.installLatestJRE()) {"+"if (deployJava.launch(""+q+"")) {}"+"}"+"} else {"+"if (deployJava.launch(""+q+"")) {}"+"}";document.write("<"+'a href="'+o+"\" onMouseOver=\"window.status=''; "+'return true;"><'+"img "+'src="'+this.launchButtonPNG+'" '+'border="0" /><'+"/"+"a"+">")},launch:function(o){document.location=o;return true},isPluginInstalled:function(){var o=this.getPlugin();if(o&&o.jvms){return true}else{return false}},isAutoUpdateEnabled:function(){if(this.isPluginInstalled()){return this.getPlugin().isAutoUpdateEnabled()}return false},setAutoUpdateEnabled:function(){if(this.isPluginInstalled()){return this.getPlugin().setAutoUpdateEnabled()}return false},setInstallerType:function(o){this.installType=o;if(this.isPluginInstalled()){return this.getPlugin().setInstallerType(o)}return false},setAdditionalPackages:function(o){if(this.isPluginInstalled()){return this.getPlugin().setAdditionalPackages(o)}return false},setEarlyAccess:function(o){this.EAInstallEnabled=o},isPlugin2:function(){if(this.isPluginInstalled()){if(this.versionCheck("1.6.0_10+")){try{return this.getPlugin().isPlugin2()}catch(o){}}}return false},allowPlugin:function(){this.getBrowser();var o=("Safari"!=this.browserName2&&"Opera"!=this.browserName2);return o},getPlugin:function(){this.refresh();var o=null;if(this.allowPlugin()){o=document.getElementById("deployJavaPlugin")}return o},compareVersionToPattern:function(v,p,r,t){if(v==undefined||p==undefined){return false}var w="^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:_(\\d+))?)?)?$";var x=v.match(w);if(x!=null){var u=0;var y=new Array();for(var q=1;q<x.length;++q){if((typeof x[q]=="string")&&(x[q]!="")){y[u]=x[q];u++}}var o=Math.min(y.length,p.length);if(t){for(var q=0;q<o;++q){if(y[q]<p[q]){return false}else{if(y[q]>p[q]){return true}}}return true}else{for(var q=0;q<o;++q){if(y[q]!=p[q]){return false}}if(r){return true}else{return(y.length==p.length)}}}else{return false}},getBrowser:function(){if(this.browserName==null){var o=navigator.userAgent.toLowerCase();g("[getBrowser()] navigator.userAgent.toLowerCase() -> "+o);if((o.indexOf("msie")!=-1)&&(o.indexOf("opera")==-1)){this.browserName="MSIE";this.browserName2="MSIE"}else{if(o.indexOf("trident")!=-1||o.indexOf("Trident")!=-1){this.browserName="MSIE";this.browserName2="MSIE"}else{if(o.indexOf("iphone")!=-1){this.browserName="Netscape Family";this.browserName2="iPhone"}else{if((o.indexOf("firefox")!=-1)&&(o.indexOf("opera")==-1)){this.browserName="Netscape Family";this.browserName2="Firefox"}else{if(o.indexOf("chrome")!=-1){this.browserName="Netscape Family";this.browserName2="Chrome"}else{if(o.indexOf("safari")!=-1){this.browserName="Netscape Family";this.browserName2="Safari"}else{if((o.indexOf("mozilla")!=-1)&&(o.indexOf("opera")==-1)){this.browserName="Netscape Family";this.browserName2="Other"}else{if(o.indexOf("opera")!=-1){this.browserName="Netscape Family";this.browserName2="Opera"}else{this.browserName="?";this.browserName2="unknown"}}}}}}}}g("[getBrowser()] Detected browser name:"+this.browserName+", "+this.browserName2)}return this.browserName},testUsingActiveX:function(o){var q="JavaWebStart.isInstalled."+o+".0";if(typeof ActiveXObject=="undefined"||!ActiveXObject){g("[testUsingActiveX()] Browser claims to be IE, but no ActiveXObject object?");return false}try{return(new ActiveXObject(q)!=null)}catch(p){return false}},testForMSVM:function(){var p="{08B0E5C0-4FCB-11CF-AAA5-00401C608500}";if(typeof oClientCaps!="undefined"){var o=oClientCaps.getComponentVersion(p,"ComponentID");if((o=="")||(o=="5,0,5000,0")){return false}else{return true}}else{return false}},testUsingMimeTypes:function(p){if(!navigator.mimeTypes){g("[testUsingMimeTypes()] Browser claims to be Netscape family, but no mimeTypes[] array?");return false}for(var q=0;q<navigator.mimeTypes.length;++q){s=navigator.mimeTypes[q].type;var o=s.match(/^application\/x-java-applet\x3Bversion=(1\.8|1\.7|1\.6|1\.5|1\.4\.2)$/);if(o!=null){if(this.compareVersions(o[1],p)){return true}}}return false},testUsingPluginsArray:function(p){if((!navigator.plugins)||(!navigator.plugins.length)){return false}var o=navigator.platform.toLowerCase();for(var q=0;q<navigator.plugins.length;++q){s=navigator.plugins[q].description;if(s.search(/^Java Switchable Plug-in (Cocoa)/)!=-1){if(this.compareVersions("1.5.0",p)){return true}}else{if(s.search(/^Java/)!=-1){if(o.indexOf("win")!=-1){if(this.compareVersions("1.5.0",p)||this.compareVersions("1.6.0",p)){return true}}}}}if(this.compareVersions("1.5.0",p)){return true}return false},IEInstall:function(){location.href=n(((this.returnPage!=null)?("&returnPage="+this.returnPage):"")+((this.locale!=null)?("&locale="+this.locale):"")+((this.brand!=null)?("&brand="+this.brand):""));return false},done:function(p,o){},FFInstall:function(){location.href=n(((this.returnPage!=null)?("&returnPage="+this.returnPage):"")+((this.locale!=null)?("&locale="+this.locale):"")+((this.brand!=null)?("&brand="+this.brand):"")+((this.installType!=null)?("&type="+this.installType):""));return false},compareVersions:function(r,t){var p=r.split(".");var o=t.split(".");for(var q=0;q<p.length;++q){p[q]=Number(p[q])}for(var q=0;q<o.length;++q){o[q]=Number(o[q])}if(p.length==2){p[2]=0}if(p[0]>o[0]){return true}if(p[0]<o[0]){return false}if(p[1]>o[1]){return true}if(p[1]<o[1]){return false}if(p[2]>o[2]){return true}if(p[2]<o[2]){return false}return true},enableAlerts:function(){this.browserName=null;this.debug=true},poll:function(){this.refresh();var o=this.getJREs();if((this.preInstallJREList.length==0)&&(o.length!=0)){clearInterval(this.myInterval);if(this.returnPage!=null){location.href=this.returnPage}}if((this.preInstallJREList.length!=0)&&(o.length!=0)&&(this.preInstallJREList[0]!=o[0])){clearInterval(this.myInterval);if(this.returnPage!=null){location.href=this.returnPage}}},writePluginTag:function(){var o=this.getBrowser();if(o=="MSIE"){document.write("<"+'object classid="clsid:CAFEEFAC-DEC7-0000-0001-ABCDEFFEDCBA" '+'id="deployJavaPlugin" width="0" height="0">'+"<"+"/"+"object"+">")}else{if(o=="Netscape Family"&&this.allowPlugin()){this.writeEmbedTag()}}},refresh:function(){navigator.plugins.refresh(false);var o=this.getBrowser();if(o=="Netscape Family"&&this.allowPlugin()){var p=document.getElementById("deployJavaPlugin");if(p==null){this.writeEmbedTag()}}},writeEmbedTag:function(){var o=false;if(navigator.mimeTypes!=null){for(var p=0;p<navigator.mimeTypes.length;p++){if(navigator.mimeTypes[p].type==this.mimeType){if(navigator.mimeTypes[p].enabledPlugin){document.write("<"+'embed id="deployJavaPlugin" type="'+this.mimeType+'" hidden="true" />');o=true}}}if(!o){for(var p=0;p<navigator.mimeTypes.length;p++){if(navigator.mimeTypes[p].type==this.oldMimeType){if(navigator.mimeTypes[p].enabledPlugin){document.write("<"+'embed id="deployJavaPlugin" type="'+this.oldMimeType+'" hidden="true" />')}}}}}}};d.writePluginTag();if(d.locale==null){var h=null;if(h==null){try{h=navigator.userLanguage}catch(f){}}if(h==null){try{h=navigator.systemLanguage}catch(f){}}if(h==null){try{h=navigator.language}catch(f){}}if(h!=null){h.replace("-","_");d.locale=h}}return d}(); \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/modules/ModulesDispatcher.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/modules/ModulesDispatcher.as index a682d081050d66db7b2cae10951bd2c63acf7900..2767503df12902a9b6b9d359e39964675e6904a5 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/modules/ModulesDispatcher.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/modules/ModulesDispatcher.as @@ -1,198 +1,198 @@ -/** - * 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.modules -{ - import com.asfusion.mate.events.Dispatcher; - import flash.events.TimerEvent; - import flash.utils.Timer; - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.core.BBB; - import org.bigbluebutton.core.UsersUtil; - import org.bigbluebutton.core.vo.Config; - import org.bigbluebutton.core.vo.ConfigBuilder; - import org.bigbluebutton.main.api.JSLog; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.ConfigEvent; - import org.bigbluebutton.main.events.ModuleLoadEvent; - import org.bigbluebutton.main.events.PortTestEvent; - import org.bigbluebutton.main.events.UserServicesEvent; - import org.bigbluebutton.main.model.ConfigParameters; - import org.bigbluebutton.main.model.modules.EnterApiService; - - public class ModulesDispatcher - { - private static const LOG:String = "Main::ModulesDispatcher - "; - private var dispatcher:Dispatcher; - private var enterApiService: EnterApiService; - private var meetingInfo:Object = new Object(); - private var enterApiUrl:String; - - public function ModulesDispatcher() - { - dispatcher = new Dispatcher(); - - } - - public function sendLoadProgressEvent(moduleName:String, loadProgress:Number):void{ - var loadEvent:ModuleLoadEvent = new ModuleLoadEvent(ModuleLoadEvent.MODULE_LOAD_PROGRESS); - loadEvent.moduleName = moduleName; - loadEvent.progress = loadProgress; - dispatcher.dispatchEvent(loadEvent); - } - - public function sendModuleLoadReadyEvent(moduleName:String):void{ - var loadReadyEvent:ModuleLoadEvent = new ModuleLoadEvent(ModuleLoadEvent.MODULE_LOAD_READY); - loadReadyEvent.moduleName = moduleName; - dispatcher.dispatchEvent(loadReadyEvent); - } - - public function sendAllModulesLoadedEvent():void{ - dispatcher.dispatchEvent(new ModuleLoadEvent(ModuleLoadEvent.ALL_MODULES_LOADED)); - - var loginEvent:BBBEvent = new BBBEvent(BBBEvent.LOGIN_EVENT); - dispatcher.dispatchEvent(loginEvent); - } - - public function sendStartUserServicesEvent(application:String, host:String, isTunnelling:Boolean):void{ - var e:UserServicesEvent = new UserServicesEvent(UserServicesEvent.START_USER_SERVICES); - e.applicationURI = application; - e.hostURI = host; - e.isTunnelling = isTunnelling; - dispatcher.dispatchEvent(e); - } - - public function sendPortTestEvent():void { - getMeetingAndUserInfo(); - } - - private function getMeetingAndUserInfo():void { - enterApiService = new EnterApiService(); - enterApiService.addResultListener(resultListener); - enterApiService.load(enterApiUrl); - } - - private function resultListener(success:Boolean, result:Object):void { - if (success) { - trace(LOG + "Saving meeting and user info " + JSON.stringify(result)); - - meetingInfo.username = result.username; - meetingInfo.userId = result.userId; - meetingInfo.meetingName = result.meetingName; - meetingInfo.meetingId = result.meetingId; - - doPortTesting(); - } else { - var logData:Object = new Object(); - JSLog.critical("Failed to get meeting and user info from Enter API", logData); - - dispatcher.dispatchEvent(new PortTestEvent(PortTestEvent.TUNNELING_FAILED)); - } - } - - private function doPortTesting():void { - trace(LOG + "Sending TEST_RTMP Event"); - var e:PortTestEvent = new PortTestEvent(PortTestEvent.TEST_RTMP); - dispatcher.dispatchEvent(e); - } - - private function timerHandler(e:TimerEvent):void{ - trace(LOG + "Sending PORT_TEST_UPDATE Event"); - var evt:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_UPDATE); - dispatcher.dispatchEvent(evt); - } - - public function sendTunnelingFailedEvent(server: String, app: String):void{ - trace(LOG + "Sending TunnelingFailed Event"); - var logData:Object = new Object(); - logData.server = server; - logData.app = app; - logData.userId = meetingInfo.userId; - logData.username = meetingInfo.username; - logData.meetingName = meetingInfo.meetingName; - logData.meetingId = meetingInfo.meetingId; - trace(LOG + "Cannot connect to Red5 using RTMP and RTMPT", JSON.stringify(logData)); - JSLog.critical("Cannot connect to Red5 using RTMP and RTMPT", logData); - - dispatcher.dispatchEvent(new PortTestEvent(PortTestEvent.TUNNELING_FAILED)); - } - - public function sendPortTestSuccessEvent(port:String, host:String, protocol:String, app:String):void{ - trace(LOG + "Sending PORT_TEST_SUCCESS Event"); - var logData:Object = new Object(); - logData.port = port; - logData.server = host; - logData.protocol = protocol; - logData.app = app; - logData.userId = meetingInfo.userId; - logData.username = meetingInfo.username; - logData.meetingName = meetingInfo.meetingName; - logData.meetingId = meetingInfo.meetingId; - JSLog.debug("Successfully connected on test connection.", logData); - - var portEvent:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_SUCCESS); - portEvent.port = port; - portEvent.hostname = host; - portEvent.protocol = protocol; - portEvent.app = app; - dispatcher.dispatchEvent(portEvent); - - } - - public function sendPortTestFailedEvent(port:String, host:String, protocol:String, app:String):void{ - trace(LOG + "Sending PORT_TEST_FAILED Event"); - var portFailEvent:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_FAILED); - portFailEvent.port = port; - portFailEvent.hostname = host; - portFailEvent.protocol = protocol; - portFailEvent.app = app; - dispatcher.dispatchEvent(portFailEvent); - - } - - public function sendModuleLoadingStartedEvent(modules:XMLList):void{ - var event:ModuleLoadEvent = new ModuleLoadEvent(ModuleLoadEvent.MODULE_LOADING_STARTED); - event.modules = modules; - dispatcher.dispatchEvent(event); - } - - public function sendConfigParameters(c:ConfigParameters):void{ - enterApiUrl = c.host; - - var event:ConfigEvent = new ConfigEvent(ConfigEvent.CONFIG_EVENT); - var config:Config; - config = new ConfigBuilder(c.version, c.localeVersion) - .withApplication(c.application) - .withHelpUrl(c.helpURL) - .withHost(c.host) - .withLanguageEnabled(c.languageEnabled) - .withShortcutKeysShowButton(c.shortcutKeysShowButton) - .withNumModule(c.numModules) - .withPortTestApplication(c.portTestApplication) - .withPortTestHost(c.portTestHost) - .withShowDebug(c.showDebug) - .withSkinning(c.skinning) - .withCopyright(c.copyright) - .withLogo(c.logo) - .withBackground(c.background) - .build() - event.config = config; - dispatcher.dispatchEvent(event); - } - } +/** + * 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.modules +{ + import com.asfusion.mate.events.Dispatcher; + import flash.events.TimerEvent; + import flash.utils.Timer; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.vo.Config; + import org.bigbluebutton.core.vo.ConfigBuilder; + import org.bigbluebutton.main.api.JSLog; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.ConfigEvent; + import org.bigbluebutton.main.events.ModuleLoadEvent; + import org.bigbluebutton.main.events.PortTestEvent; + import org.bigbluebutton.main.events.UserServicesEvent; + import org.bigbluebutton.main.model.ConfigParameters; + import org.bigbluebutton.main.model.modules.EnterApiService; + + public class ModulesDispatcher + { + private static const LOG:String = "Main::ModulesDispatcher - "; + private var dispatcher:Dispatcher; + private var enterApiService: EnterApiService; + private var meetingInfo:Object = new Object(); + private var enterApiUrl:String; + + public function ModulesDispatcher() + { + dispatcher = new Dispatcher(); + + } + + public function sendLoadProgressEvent(moduleName:String, loadProgress:Number):void{ + var loadEvent:ModuleLoadEvent = new ModuleLoadEvent(ModuleLoadEvent.MODULE_LOAD_PROGRESS); + loadEvent.moduleName = moduleName; + loadEvent.progress = loadProgress; + dispatcher.dispatchEvent(loadEvent); + } + + public function sendModuleLoadReadyEvent(moduleName:String):void{ + var loadReadyEvent:ModuleLoadEvent = new ModuleLoadEvent(ModuleLoadEvent.MODULE_LOAD_READY); + loadReadyEvent.moduleName = moduleName; + dispatcher.dispatchEvent(loadReadyEvent); + } + + public function sendAllModulesLoadedEvent():void{ + dispatcher.dispatchEvent(new ModuleLoadEvent(ModuleLoadEvent.ALL_MODULES_LOADED)); + + var loginEvent:BBBEvent = new BBBEvent(BBBEvent.LOGIN_EVENT); + dispatcher.dispatchEvent(loginEvent); + } + + public function sendStartUserServicesEvent(application:String, host:String, isTunnelling:Boolean):void{ + var e:UserServicesEvent = new UserServicesEvent(UserServicesEvent.START_USER_SERVICES); + e.applicationURI = application; + e.hostURI = host; + e.isTunnelling = isTunnelling; + dispatcher.dispatchEvent(e); + } + + public function sendPortTestEvent():void { + getMeetingAndUserInfo(); + } + + private function getMeetingAndUserInfo():void { + enterApiService = new EnterApiService(); + enterApiService.addResultListener(resultListener); + enterApiService.load(enterApiUrl); + } + + private function resultListener(success:Boolean, result:Object):void { + if (success) { + trace(LOG + "Saving meeting and user info " + JSON.stringify(result)); + + meetingInfo.username = result.username; + meetingInfo.userId = result.userId; + meetingInfo.meetingName = result.meetingName; + meetingInfo.meetingId = result.meetingId; + + doPortTesting(); + } else { + var logData:Object = new Object(); + JSLog.critical("Failed to get meeting and user info from Enter API", logData); + + dispatcher.dispatchEvent(new PortTestEvent(PortTestEvent.TUNNELING_FAILED)); + } + } + + private function doPortTesting():void { + trace(LOG + "Sending TEST_RTMP Event"); + var e:PortTestEvent = new PortTestEvent(PortTestEvent.TEST_RTMP); + dispatcher.dispatchEvent(e); + } + + private function timerHandler(e:TimerEvent):void{ + trace(LOG + "Sending PORT_TEST_UPDATE Event"); + var evt:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_UPDATE); + dispatcher.dispatchEvent(evt); + } + + public function sendTunnelingFailedEvent(server: String, app: String):void{ + trace(LOG + "Sending TunnelingFailed Event"); + var logData:Object = new Object(); + logData.server = server; + logData.app = app; + logData.userId = meetingInfo.userId; + logData.username = meetingInfo.username; + logData.meetingName = meetingInfo.meetingName; + logData.meetingId = meetingInfo.meetingId; + trace(LOG + "Cannot connect to Red5 using RTMP and RTMPT", JSON.stringify(logData)); + JSLog.critical("Cannot connect to Red5 using RTMP and RTMPT", logData); + + dispatcher.dispatchEvent(new PortTestEvent(PortTestEvent.TUNNELING_FAILED)); + } + + public function sendPortTestSuccessEvent(port:String, host:String, protocol:String, app:String):void{ + trace(LOG + "Sending PORT_TEST_SUCCESS Event"); + var logData:Object = new Object(); + logData.port = port; + logData.server = host; + logData.protocol = protocol; + logData.app = app; + logData.userId = meetingInfo.userId; + logData.username = meetingInfo.username; + logData.meetingName = meetingInfo.meetingName; + logData.meetingId = meetingInfo.meetingId; + JSLog.debug("Successfully connected on test connection.", logData); + + var portEvent:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_SUCCESS); + portEvent.port = port; + portEvent.hostname = host; + portEvent.protocol = protocol; + portEvent.app = app; + dispatcher.dispatchEvent(portEvent); + + } + + public function sendPortTestFailedEvent(port:String, host:String, protocol:String, app:String):void{ + trace(LOG + "Sending PORT_TEST_FAILED Event"); + var portFailEvent:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_FAILED); + portFailEvent.port = port; + portFailEvent.hostname = host; + portFailEvent.protocol = protocol; + portFailEvent.app = app; + dispatcher.dispatchEvent(portFailEvent); + + } + + public function sendModuleLoadingStartedEvent(modules:XMLList):void{ + var event:ModuleLoadEvent = new ModuleLoadEvent(ModuleLoadEvent.MODULE_LOADING_STARTED); + event.modules = modules; + dispatcher.dispatchEvent(event); + } + + public function sendConfigParameters(c:ConfigParameters):void{ + enterApiUrl = c.host; + + var event:ConfigEvent = new ConfigEvent(ConfigEvent.CONFIG_EVENT); + var config:Config; + config = new ConfigBuilder(c.version, c.localeVersion) + .withApplication(c.application) + .withHelpUrl(c.helpURL) + .withHost(c.host) + .withLanguageEnabled(c.languageEnabled) + .withShortcutKeysShowButton(c.shortcutKeysShowButton) + .withNumModule(c.numModules) + .withPortTestApplication(c.portTestApplication) + .withPortTestHost(c.portTestHost) + .withShowDebug(c.showDebug) + .withSkinning(c.skinning) + .withCopyright(c.copyright) + .withLogo(c.logo) + .withBackground(c.background) + .build() + event.config = config; + dispatcher.dispatchEvent(event); + } + } } \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as index 80cefc3ef989b4e47b26258fce789119ef14eb70..27b806cf04f6f6b87e4e39382103bfb2d9df9ea8 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as @@ -1,412 +1,412 @@ -/** -* 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.adobe.protocols.dict.events.ErrorEvent; - import com.asfusion.mate.events.Dispatcher; - - import flash.events.*; - import flash.net.NetConnection; - import flash.net.Responder; - import flash.utils.Timer; - - import org.bigbluebutton.common.LogUtil; - 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; - import org.bigbluebutton.main.model.users.events.UsersConnectionEvent; - - public class NetConnectionDelegate - { - public static const LOG:String = "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 guestKickedOutCommand: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; - - var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/; - var result:Array = pattern.exec(uri); - BandwidthMonitor.getInstance().serverURL = result.server; - } - - - 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 { - LogUtil.debug("Message name is undefined"); - } - } - - public function onMessageFromServer(messageName:String, msg:Object):void { - trace(LOG + "Got message from server [" + messageName + "]"); - if (!authenticated && (messageName == "validateAuthTokenReply")) { - handleValidateAuthTokenReply(msg) - } else if (messageName == "validateAuthTokenTimedOut") { - handleValidateAuthTokenTimedOut(msg) - } else if (authenticated) { - notifyListeners(messageName, msg); - } else { - trace(LOG + "Ignoring message=[" + messageName + "] as our token hasn't been validated yet."); - } - } - - 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 { - trace(LOG + "validating token for [" + _conferenceParameters.internalUserID + "]"); - }, - // status - On error occurred - function(status:Object):void { - LogUtil.error("Error occurred:"); - for (var x:Object in status) { - LogUtil.error(x + " : " + status[x]); - } - }, - message - ); //_netConnection.call - } - - private function handleValidateAuthTokenTimedOut(msg: Object):void { - trace(LOG + "*** handleValidateAuthTokenTimedOut " + msg.msg + " **** \n"); - 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; - trace(LOG + "*** handleValidateAuthTokenTimedOut. valid=[ " + tokenValid + "] **** \n"); - } else { - trace(LOG + "*** handleValidateAuthTokenTimedOut. valid=[ " + tokenValid + "] **** \n"); - dispatcher.dispatchEvent(new InvalidAuthTokenEvent()); - } - } - - private function handleValidateAuthTokenReply(msg: Object):void { - trace(LOG + "*** handleValidateAuthTokenReply " + msg.msg + " **** \n"); - 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; - trace(LOG + "*** handleValidateAuthTokenReply. valid=[ " + tokenValid + "] **** \n"); - } else { - trace(LOG + "*** handleValidateAuthTokenReply. valid=[ " + tokenValid + "] **** \n"); - 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 { - trace(LOG + "SENDING [" + 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; - - trace(LOG + "::Connecting to " + uri + " [" + _conferenceParameters.username + "," + _conferenceParameters.role + "," + - _conferenceParameters.conference + "," + _conferenceParameters.record + "," + _conferenceParameters.room + ", " + _conferenceParameters.lockSettings.lockOnJoin + "]"); - _netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role, - _conferenceParameters.room, _conferenceParameters.voicebridge, - _conferenceParameters.record, _conferenceParameters.externUserID, - _conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings, - _conferenceParameters.guest); - } catch(e:ArgumentError) { - // Invalid parameters. - switch (e.errorID) { - case 2004 : - LogUtil.debug("Error! Invalid server location: " + uri); - break; - default : - LogUtil.debug("UNKNOWN Error! Invalid server location: " + uri); - break; - } - } - } - - public function disconnect(logoutOnUserCommand:Boolean):void { - this.logoutOnUserCommand = logoutOnUserCommand; - _netConnection.close(); - } - - public function guestDisconnect() : void - { - this.guestKickedOutCommand = true; - _netConnection.close(); - } - - - public function forceClose():void { - _netConnection.close(); - } - - protected function netStatus(event:NetStatusEvent):void { - handleResult( event ); - } - - 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": - trace(LOG + ":Connection to viewers application succeeded."); - JSLog.debug("Successfully connected to BBB App.", logData); - - validateToken(); - - break; - - case "NetConnection.Connect.Failed": - if (tried_tunneling) { - trace(LOG + ":Connection to viewers application failed...even when tunneling"); - sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED); - } else { - disconnect(false); - trace(LOG + ":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": - trace(LOG + "Connection to viewers application closed"); - sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED); - - break; - - case "NetConnection.Connect.InvalidApp": - trace(LOG + ":viewers application not found on server"); - sendConnectionFailedEvent(ConnectionFailedEvent.INVALID_APP); - break; - - case "NetConnection.Connect.AppShutDown": - trace(LOG + ":viewers application has been shutdown"); - sendConnectionFailedEvent(ConnectionFailedEvent.APP_SHUTDOWN); - break; - - case "NetConnection.Connect.Rejected": - trace(LOG + ":Connection to the server rejected. Uri: " + _applicationURI + ". Check if the red5 specified in the uri exists and is running" ); - sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED); - break; - - case "NetConnection.Connect.NetworkChange": - JSLog.warn("Detected network change to BBB App", logData); - trace(LOG + "Detected network change. User might be on a wireless and temporarily dropped connection. Doing nothing. Just making a note."); - break; - - default : - trace(LOG + ":Default status to the viewers application" ); - sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); - break; - } - } - - private function autoReconnectTimerHandler(event:TimerEvent):void { - trace(LOG + "autoReconnectTimerHandler: " + event); - connect(_conferenceParameters, tried_tunneling); - } - - private function rtmptRetryTimerHandler(event:TimerEvent):void { - trace(LOG + "rtmptRetryTimerHandler: " + event); - connect(_conferenceParameters, true); - } - - protected function netSecurityError(event: SecurityErrorEvent):void { - trace(LOG + "Security error - " + event.text); - sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); - } - - protected function netIOError(event: IOErrorEvent):void { - trace(LOG + "Input/output error - " + event.text); - sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); - } - - protected function netASyncError(event: AsyncErrorEvent):void { - trace(LOG + "Asynchronous code error - " + event.toString() ); - - LogUtil.debug("Asynchronous code error - " + event.toString() ); - sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); - } - - private function sendConnectionFailedEvent(reason:String):void{ - var logData:Object = new Object(); - - if (this.guestKickedOutCommand) { - logData.reason = "Guest kicked out"; - logData.user = UsersUtil.getUserData(); - JSLog.warn("User disconnected from BBB App.", logData); - sendGuestUserKickedOutEvent(); - } else 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 sendGuestUserKickedOutEvent():void { - var e:ConnectionFailedEvent = new ConnectionFailedEvent(ConnectionFailedEvent.MODERATOR_DENIED_ME); - 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 - trace("bandwidth = " + p_bw + " Kbps."); - } - } -} +/** +* 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.adobe.protocols.dict.events.ErrorEvent; + import com.asfusion.mate.events.Dispatcher; + + import flash.events.*; + import flash.net.NetConnection; + import flash.net.Responder; + import flash.utils.Timer; + + import org.bigbluebutton.common.LogUtil; + 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; + import org.bigbluebutton.main.model.users.events.UsersConnectionEvent; + + public class NetConnectionDelegate + { + public static const LOG:String = "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 guestKickedOutCommand: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; + + var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/; + var result:Array = pattern.exec(uri); + BandwidthMonitor.getInstance().serverURL = result.server; + } + + + 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 { + LogUtil.debug("Message name is undefined"); + } + } + + public function onMessageFromServer(messageName:String, msg:Object):void { + trace(LOG + "Got message from server [" + messageName + "]"); + if (!authenticated && (messageName == "validateAuthTokenReply")) { + handleValidateAuthTokenReply(msg) + } else if (messageName == "validateAuthTokenTimedOut") { + handleValidateAuthTokenTimedOut(msg) + } else if (authenticated) { + notifyListeners(messageName, msg); + } else { + trace(LOG + "Ignoring message=[" + messageName + "] as our token hasn't been validated yet."); + } + } + + 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 { + trace(LOG + "validating token for [" + _conferenceParameters.internalUserID + "]"); + }, + // status - On error occurred + function(status:Object):void { + LogUtil.error("Error occurred:"); + for (var x:Object in status) { + LogUtil.error(x + " : " + status[x]); + } + }, + message + ); //_netConnection.call + } + + private function handleValidateAuthTokenTimedOut(msg: Object):void { + trace(LOG + "*** handleValidateAuthTokenTimedOut " + msg.msg + " **** \n"); + 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; + trace(LOG + "*** handleValidateAuthTokenTimedOut. valid=[ " + tokenValid + "] **** \n"); + } else { + trace(LOG + "*** handleValidateAuthTokenTimedOut. valid=[ " + tokenValid + "] **** \n"); + dispatcher.dispatchEvent(new InvalidAuthTokenEvent()); + } + } + + private function handleValidateAuthTokenReply(msg: Object):void { + trace(LOG + "*** handleValidateAuthTokenReply " + msg.msg + " **** \n"); + 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; + trace(LOG + "*** handleValidateAuthTokenReply. valid=[ " + tokenValid + "] **** \n"); + } else { + trace(LOG + "*** handleValidateAuthTokenReply. valid=[ " + tokenValid + "] **** \n"); + 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 { + trace(LOG + "SENDING [" + 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; + + trace(LOG + "::Connecting to " + uri + " [" + _conferenceParameters.username + "," + _conferenceParameters.role + "," + + _conferenceParameters.conference + "," + _conferenceParameters.record + "," + _conferenceParameters.room + ", " + _conferenceParameters.lockSettings.lockOnJoin + "]"); + _netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role, + _conferenceParameters.room, _conferenceParameters.voicebridge, + _conferenceParameters.record, _conferenceParameters.externUserID, + _conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings, + _conferenceParameters.guest); + } catch(e:ArgumentError) { + // Invalid parameters. + switch (e.errorID) { + case 2004 : + LogUtil.debug("Error! Invalid server location: " + uri); + break; + default : + LogUtil.debug("UNKNOWN Error! Invalid server location: " + uri); + break; + } + } + } + + public function disconnect(logoutOnUserCommand:Boolean):void { + this.logoutOnUserCommand = logoutOnUserCommand; + _netConnection.close(); + } + + public function guestDisconnect() : void + { + this.guestKickedOutCommand = true; + _netConnection.close(); + } + + + public function forceClose():void { + _netConnection.close(); + } + + protected function netStatus(event:NetStatusEvent):void { + handleResult( event ); + } + + 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": + trace(LOG + ":Connection to viewers application succeeded."); + JSLog.debug("Successfully connected to BBB App.", logData); + + validateToken(); + + break; + + case "NetConnection.Connect.Failed": + if (tried_tunneling) { + trace(LOG + ":Connection to viewers application failed...even when tunneling"); + sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED); + } else { + disconnect(false); + trace(LOG + ":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": + trace(LOG + "Connection to viewers application closed"); + sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED); + + break; + + case "NetConnection.Connect.InvalidApp": + trace(LOG + ":viewers application not found on server"); + sendConnectionFailedEvent(ConnectionFailedEvent.INVALID_APP); + break; + + case "NetConnection.Connect.AppShutDown": + trace(LOG + ":viewers application has been shutdown"); + sendConnectionFailedEvent(ConnectionFailedEvent.APP_SHUTDOWN); + break; + + case "NetConnection.Connect.Rejected": + trace(LOG + ":Connection to the server rejected. Uri: " + _applicationURI + ". Check if the red5 specified in the uri exists and is running" ); + sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED); + break; + + case "NetConnection.Connect.NetworkChange": + JSLog.warn("Detected network change to BBB App", logData); + trace(LOG + "Detected network change. User might be on a wireless and temporarily dropped connection. Doing nothing. Just making a note."); + break; + + default : + trace(LOG + ":Default status to the viewers application" ); + sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); + break; + } + } + + private function autoReconnectTimerHandler(event:TimerEvent):void { + trace(LOG + "autoReconnectTimerHandler: " + event); + connect(_conferenceParameters, tried_tunneling); + } + + private function rtmptRetryTimerHandler(event:TimerEvent):void { + trace(LOG + "rtmptRetryTimerHandler: " + event); + connect(_conferenceParameters, true); + } + + protected function netSecurityError(event: SecurityErrorEvent):void { + trace(LOG + "Security error - " + event.text); + sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); + } + + protected function netIOError(event: IOErrorEvent):void { + trace(LOG + "Input/output error - " + event.text); + sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); + } + + protected function netASyncError(event: AsyncErrorEvent):void { + trace(LOG + "Asynchronous code error - " + event.toString() ); + + LogUtil.debug("Asynchronous code error - " + event.toString() ); + sendConnectionFailedEvent(ConnectionFailedEvent.UNKNOWN_REASON); + } + + private function sendConnectionFailedEvent(reason:String):void{ + var logData:Object = new Object(); + + if (this.guestKickedOutCommand) { + logData.reason = "Guest kicked out"; + logData.user = UsersUtil.getUserData(); + JSLog.warn("User disconnected from BBB App.", logData); + sendGuestUserKickedOutEvent(); + } else 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 sendGuestUserKickedOutEvent():void { + var e:ConnectionFailedEvent = new ConnectionFailedEvent(ConnectionFailedEvent.MODERATOR_DENIED_ME); + 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 + trace("bandwidth = " + p_bw + " Kbps."); + } + } +} diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml index 2c4aa7cbc90a0908d1006d0b349a10256c4cfd77..698863eca218604bd6526020f8a89f49309158ce 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml @@ -160,7 +160,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:HBox width="100%" horizontalAlign="right" horizontalGap="18" paddingTop="20"> <mx:Button id="saveBtn" label="{ResourceUtil.getInstance().getString('bbb.lockSettings.save')}" click="onSaveClicked()" tabIndex="{baseIndex+8}" - toolTip="{ResourceUtil.getInstance().getString('bbb.lockSettings.save.tooltip')}"/> + toolTip="{ResourceUtil.getInstance().getString('bbb.lockSettings.save.toolTip')}"/> <mx:Button id="cancelBtn" label="{ResourceUtil.getInstance().getString('bbb.lockSettings.cancel')}" click="onCancelClicked()" tabIndex="{baseIndex+9}" diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/maps/ChatEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/maps/ChatEventMap.mxml index 4fec5fa0264c62da36642644adf7d8bb4c1450f6..c4f7100c6df3d68875381c722c18b629ee1809af 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/maps/ChatEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/maps/ChatEventMap.mxml @@ -1,109 +1,109 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - -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/>. - ---> - -<EventMap xmlns="http://mate.asfusion.com/" - xmlns:mx="http://www.adobe.com/2006/mxml"> - <mx:Script> - <![CDATA[ - import com.asfusion.mate.events.Dispatcher; - import mx.events.FlexEvent; - import org.bigbluebutton.common.events.OpenWindowEvent; - import org.bigbluebutton.core.EventConstants; - import org.bigbluebutton.main.events.ModuleStartedEvent; - import org.bigbluebutton.modules.chat.events.ChatCopyEvent; - import org.bigbluebutton.modules.chat.events.ChatEvent; - import org.bigbluebutton.modules.chat.events.ChatSaveEvent; - import org.bigbluebutton.modules.chat.events.SendPrivateChatMessageEvent; - import org.bigbluebutton.modules.chat.events.SendPublicChatMessageEvent; - import org.bigbluebutton.modules.chat.events.StartChatModuleEvent; - import org.bigbluebutton.modules.chat.events.StopChatModuleEvent; - import org.bigbluebutton.modules.chat.events.TranscriptEvent; - import org.bigbluebutton.modules.chat.services.ChatMessageService; - import org.bigbluebutton.modules.chat.services.MessageReceiver; - import org.bigbluebutton.modules.chat.services.MessageSender; - import org.bigbluebutton.modules.chat.services.ChatCopy; - import org.bigbluebutton.modules.chat.services.ChatSaver; - import org.bigbluebutton.modules.chat.views.ChatView; - import org.bigbluebutton.modules.chat.views.ChatWindow; - ]]> - </mx:Script> - - <EventHandlers type="{FlexEvent.PREINITIALIZE}"> - <ObjectBuilder generator="{ChatEventMapDelegate}" constructorArguments="{scope.dispatcher}"/> - </EventHandlers> - - <EventHandlers type="{StartChatModuleEvent.START_CHAT_MODULE_EVENT}"> - <MethodInvoker generator="{ChatEventMapDelegate}" method="openChatWindow" /> - <ObjectBuilder generator="{ChatMessageService}"/> - </EventHandlers> - - <EventHandlers type="{StopChatModuleEvent.STOP_CHAT_MODULE_EVENT}"> - <MethodInvoker generator="{ChatEventMapDelegate}" method="closeChatWindow" /> - </EventHandlers> - - <EventHandlers type="{EventConstants.SEND_PUBLIC_CHAT_REQ}"> - <MethodInvoker generator="{ChatMessageService}" method="sendPublicMessageFromApi" arguments="{event.message}"/> - </EventHandlers> - - <EventHandlers type="{EventConstants.SEND_PRIVATE_CHAT_REQ}"> - <MethodInvoker generator="{ChatMessageService}" method="sendPrivateMessageFromApi" arguments="{event.message}"/> - </EventHandlers> - - <EventHandlers type="{SendPublicChatMessageEvent.SEND_PUBLIC_CHAT_MESSAGE_EVENT}"> - <MethodInvoker generator="{ChatMessageService}" method="sendPublicMessage" arguments="{event.chatMessage}"/> - </EventHandlers> - - <EventHandlers type="{SendPrivateChatMessageEvent.SEND_PRIVATE_CHAT_MESSAGE_EVENT}"> - <MethodInvoker generator="{ChatMessageService}" method="sendPrivateMessage" arguments="{event.chatMessage}"/> - </EventHandlers> - - <EventHandlers type="{TranscriptEvent.LOAD_TRANSCRIPT}" > - <MethodInvoker generator="{ChatMessageService}" method="getPublicChatMessages"/> - </EventHandlers> - - <EventHandlers type="{TranscriptEvent.TRANSCRIPT_EVENT}" > - <MethodInvoker generator="{ChatMessageService}" method="sendWelcomeMessage"/> - </EventHandlers> - - <EventHandlers type="{ChatSaveEvent.SAVE_CHAT_EVENT}"> - <MethodInvoker generator="{ChatSaver}" method="saveChatToFile" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{ChatCopyEvent.COPY_CHAT_EVENT}"> - <MethodInvoker generator="{ChatCopy}" method="copyAllText" arguments="{event}"/> - </EventHandlers> - - <Injectors target="{ChatMessageService}"> - <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> - <PropertyInjector targetKey="receiver" source="{MessageReceiver}"/> - <PropertyInjector targetKey="sender" source="{MessageSender}"/> - </Injectors> - - <Injectors target="{MessageReceiver}"> - <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> - </Injectors> - - <Injectors target="{MessageSender}"> - <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> - </Injectors> - -</EventMap> +<?xml version="1.0" encoding="utf-8"?> + +<!-- + +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/>. + +--> + +<EventMap xmlns="http://mate.asfusion.com/" + xmlns:mx="http://www.adobe.com/2006/mxml"> + <mx:Script> + <![CDATA[ + import com.asfusion.mate.events.Dispatcher; + import mx.events.FlexEvent; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.core.EventConstants; + import org.bigbluebutton.main.events.ModuleStartedEvent; + import org.bigbluebutton.modules.chat.events.ChatCopyEvent; + import org.bigbluebutton.modules.chat.events.ChatEvent; + import org.bigbluebutton.modules.chat.events.ChatSaveEvent; + import org.bigbluebutton.modules.chat.events.SendPrivateChatMessageEvent; + import org.bigbluebutton.modules.chat.events.SendPublicChatMessageEvent; + import org.bigbluebutton.modules.chat.events.StartChatModuleEvent; + import org.bigbluebutton.modules.chat.events.StopChatModuleEvent; + import org.bigbluebutton.modules.chat.events.TranscriptEvent; + import org.bigbluebutton.modules.chat.services.ChatMessageService; + import org.bigbluebutton.modules.chat.services.MessageReceiver; + import org.bigbluebutton.modules.chat.services.MessageSender; + import org.bigbluebutton.modules.chat.services.ChatCopy; + import org.bigbluebutton.modules.chat.services.ChatSaver; + import org.bigbluebutton.modules.chat.views.ChatView; + import org.bigbluebutton.modules.chat.views.ChatWindow; + ]]> + </mx:Script> + + <EventHandlers type="{FlexEvent.PREINITIALIZE}"> + <ObjectBuilder generator="{ChatEventMapDelegate}" constructorArguments="{scope.dispatcher}"/> + </EventHandlers> + + <EventHandlers type="{StartChatModuleEvent.START_CHAT_MODULE_EVENT}"> + <MethodInvoker generator="{ChatEventMapDelegate}" method="openChatWindow" /> + <ObjectBuilder generator="{ChatMessageService}"/> + </EventHandlers> + + <EventHandlers type="{StopChatModuleEvent.STOP_CHAT_MODULE_EVENT}"> + <MethodInvoker generator="{ChatEventMapDelegate}" method="closeChatWindow" /> + </EventHandlers> + + <EventHandlers type="{EventConstants.SEND_PUBLIC_CHAT_REQ}"> + <MethodInvoker generator="{ChatMessageService}" method="sendPublicMessageFromApi" arguments="{event.message}"/> + </EventHandlers> + + <EventHandlers type="{EventConstants.SEND_PRIVATE_CHAT_REQ}"> + <MethodInvoker generator="{ChatMessageService}" method="sendPrivateMessageFromApi" arguments="{event.message}"/> + </EventHandlers> + + <EventHandlers type="{SendPublicChatMessageEvent.SEND_PUBLIC_CHAT_MESSAGE_EVENT}"> + <MethodInvoker generator="{ChatMessageService}" method="sendPublicMessage" arguments="{event.chatMessage}"/> + </EventHandlers> + + <EventHandlers type="{SendPrivateChatMessageEvent.SEND_PRIVATE_CHAT_MESSAGE_EVENT}"> + <MethodInvoker generator="{ChatMessageService}" method="sendPrivateMessage" arguments="{event.chatMessage}"/> + </EventHandlers> + + <EventHandlers type="{TranscriptEvent.LOAD_TRANSCRIPT}" > + <MethodInvoker generator="{ChatMessageService}" method="getPublicChatMessages"/> + </EventHandlers> + + <EventHandlers type="{TranscriptEvent.TRANSCRIPT_EVENT}" > + <MethodInvoker generator="{ChatMessageService}" method="sendWelcomeMessage"/> + </EventHandlers> + + <EventHandlers type="{ChatSaveEvent.SAVE_CHAT_EVENT}"> + <MethodInvoker generator="{ChatSaver}" method="saveChatToFile" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{ChatCopyEvent.COPY_CHAT_EVENT}"> + <MethodInvoker generator="{ChatCopy}" method="copyAllText" arguments="{event}"/> + </EventHandlers> + + <Injectors target="{ChatMessageService}"> + <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> + <PropertyInjector targetKey="receiver" source="{MessageReceiver}"/> + <PropertyInjector targetKey="sender" source="{MessageSender}"/> + </Injectors> + + <Injectors target="{MessageReceiver}"> + <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> + </Injectors> + + <Injectors target="{MessageSender}"> + <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> + </Injectors> + +</EventMap> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml index 21353a36eba7c081470da3b560dc845d5c8a8d70..cf3d4f34d527e2a993f0cdd95326b5e8d7a645a4 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml @@ -27,7 +27,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mate:Listener type="{LockControlEvent.CHANGED_LOCK_SETTINGS}" method="lockSettingsChanged" /> <mate:Listener type="{ChangeMyRole.CHANGE_MY_ROLE_EVENT}" method="refreshRole" /> - + <mx:Script> <![CDATA[ import com.asfusion.mate.events.Dispatcher; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml index 87bc8ec6a5c2c8c638241b888ae564dfea6aca6b..db0c33329623cbb8c6acef399c0d534f0be41391 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml @@ -27,7 +27,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. xmlns:flexlib="http://code.google.com/p/flexlib/" width="100%" height="100%" xmlns:containers="flexlib.containers.*" verticalScrollPolicy="off"> - + <mate:Listener type="{PrivateChatMessageEvent.PRIVATE_CHAT_MESSAGE_EVENT}" method="handlePrivateChatMessageEvent"/> <mate:Listener type="{PublicChatMessageEvent.PUBLIC_CHAT_MESSAGE_EVENT}" method="handlePublicChatMessageEvent"/> <mate:Listener type="{EventConstants.START_PRIVATE_CHAT}" method="handleStartPrivateChatMessageEvent"/> @@ -90,7 +90,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private static const PUBLIC_TAB_NEW:String = ResourceUtil.getInstance().getString("bbb.accessibility.chat.chatView.publicTabNew"); private var publicWaiting:Boolean = false; - private var publicFocus:Boolean = false; + private var publicFocus:Boolean = false; private var noticeLabel:String; [Embed(source="../sounds/notice.mp3")] @@ -98,7 +98,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private var noticeSound:Sound = new noticeSoundClass() as Sound; // All author and license information for the use of this sound can be found in: // src/org/bigbluebutton/modules/chat/sounds/license.txt - + // Initialization private function init():void { chatOptions = new ChatOptions(); @@ -107,7 +107,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. baseIndex = chatOptions.getBaseIndex() + 4; } - + private function onCreationComplete():void{ openChatBoxFor(PUBLIC_CHAT_USERID, true); makePublicChatUncloseable(); @@ -341,7 +341,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. // Activates an audio alert for screen-reader users on public message reception - private function publicNotification():void { + private function publicNotification():void { publicWaiting = true; if (Accessibility.active){ noticeSound.play(); @@ -359,7 +359,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. } } - public function publicChatFocus(event:FocusEvent):void{ + public function publicChatFocus(event:FocusEvent):void{ publicFocus = true; publicWaiting = false; } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/events/UploadEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/events/UploadEvent.as old mode 100644 new mode 100755 diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/managers/PresentManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/managers/PresentManager.as index 2af363e4fe7c9269754dbb097fb981b89675950a..d34439227d593e742c006cf208a523529c1f1000 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/managers/PresentManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/managers/PresentManager.as @@ -68,8 +68,8 @@ package org.bigbluebutton.modules.present.managers private function openWindow(window:IBbbModuleWindow):void{ var event:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); - event.window = window; - globalDispatcher.dispatchEvent(event); + event.window = window; + globalDispatcher.dispatchEvent(event); } public function handleOpenUploadWindow(e:UploadEvent):void{ diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as index 7033929c211aa57b2ef974cabac35925d1d3f1f5..50ea8fdd258754ddd97e1b3e3bfbecc7b1abfa39 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as @@ -1,382 +1,382 @@ -/** -* 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.videoconf.business -{ - import com.asfusion.mate.events.Dispatcher; - - import flash.events.AsyncErrorEvent; - import flash.events.IOErrorEvent; - import flash.events.NetStatusEvent; - import flash.events.SecurityErrorEvent; - import flash.media.H264Level; - import flash.media.H264Profile; - import flash.media.H264VideoStreamSettings; - import flash.net.NetConnection; - import flash.net.NetStream; - import flash.system.Capabilities; - import flash.utils.Dictionary; - - import mx.collections.ArrayCollection; - - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.core.BBB; - import org.bigbluebutton.core.UsersUtil; - import org.bigbluebutton.core.managers.UserManager; - import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; - import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; - import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; - import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady; - import org.bigbluebutton.modules.videoconf.services.messaging.MessageSender; - import org.bigbluebutton.modules.videoconf.services.messaging.MessageReceiver; - import org.bigbluebutton.modules.videoconf.events.PlayConnectionClosedEvent; - - - public class VideoProxy - { - public var videoOptions:VideoConfOptions; - - // NetConnection used for stream publishing - private var nc:NetConnection; - // NetStream used for stream publishing - private var ns:NetStream; - private var _url:String; - private var camerasPublishing:Object = new Object(); - private var connected:Boolean = false; - - // Message sender to request stream path - private var msgSender:MessageSender; - // Message receiver to receive the stream path - private var msgReceiver:MessageReceiver; - - // Dictionary<url,NetConnection> used for stream playing - private var playConnectionDict:Dictionary; - // Dictionary<url,Array<streamName>> used to keep track of streams using a URL - private var urlStreamsDict:Dictionary; - // Dictionary<streamName,streamNamePrefix> used for stream playing - private var streamNamePrefixDict:Dictionary; - // Dictionary<streamName,url> - private var streamUrlDict:Dictionary; - - private function parseOptions():void { - videoOptions = new VideoConfOptions(); - videoOptions.parseOptions(); - } - - public function VideoProxy(url:String) - { - _url = url; - parseOptions(); - nc = new NetConnection(); - nc.proxyType = "best"; - nc.client = this; - nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); - nc.addEventListener(IOErrorEvent.IO_ERROR, onIOError); - nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); - nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); - playConnectionDict = new Dictionary(); - urlStreamsDict = new Dictionary(); - streamNamePrefixDict = new Dictionary(); - streamUrlDict = new Dictionary(); - msgReceiver = new MessageReceiver(this); - msgSender = new MessageSender(); - } - - public function connect():void { - nc.connect(_url, UsersUtil.getInternalMeetingID(), UsersUtil.getMyUserID()); - playConnectionDict[_url] = nc; - urlStreamsDict[_url] = new Array(); - } - - private function onAsyncError(event:AsyncErrorEvent):void{ - } - - private function onIOError(event:NetStatusEvent):void{ - } - - private function onConnectedToVideoApp():void{ - var dispatcher:Dispatcher = new Dispatcher(); - dispatcher.dispatchEvent(new ConnectedEvent(ConnectedEvent.VIDEO_CONNECTED)); - } - - private function onNetStatus(event:NetStatusEvent):void{ - switch(event.info.code){ - case "NetConnection.Connect.Success": - connected = true; - //ns = new NetStream(nc); - onConnectedToVideoApp(); - break; - default: - LogUtil.debug("[" + event.info.code + "] for [" + _url + "]"); - connected = false; - break; - } - } - - private function onSecurityError(event:NetStatusEvent):void{ - } - - public function get publishConnection():NetConnection{ - return this.nc; - } - - private function onPlayNetStatus(event:NetStatusEvent):void { - var url:String = event.target.uri; - var streams:Array = urlStreamsDict[url]; - var dispatcher:Dispatcher = new Dispatcher(); - var prefix:String; - var stream:String; - switch(event.info.code){ - case "NetConnection.Connect.Success": - // Notify streams from this connection - var conn:NetConnection = playConnectionDict[url]; - for each (stream in streams) { - prefix = streamNamePrefixDict[stream]; - dispatcher.dispatchEvent(new PlayConnectionReady(stream, conn, prefix)); - } - break; - case "NetConnection.Connect.Failed": - case "NetConnection.Connect.Closed": - trace("[" + event.info.code + "] for a play connection at [" + url + "]"); - trace("Affected streams: ["+streams+"]"); - for each (stream in streams) { - prefix = streamNamePrefixDict[stream]; - delete streamNamePrefixDict[stream]; - delete streamUrlDict[stream]; - dispatcher.dispatchEvent(new PlayConnectionClosedEvent(stream, prefix)); - } - delete playConnectionDict[url]; - delete urlStreamsDict[url]; - break; - default: - LogUtil.debug("[" + event.info.code + "] for a play connection at [" + url + "]"); - break; - } - } - - public function createPlayConnectionFor(streamName:String):void { - LogUtil.debug("VideoProxy::createPlayConnectionFor:: Requesting path for stream [" + streamName + "]"); - // Check if a connection already exists - if(!streamUrlDict[streamName]) { - trace("VideoProxy::createPlayConnectionFor:: Requesting path for stream [" + streamName + "]"); - // Ask red5 the path to stream - msgSender.getStreamPath(streamName); - } - else { - trace("VideoProxy::createPlayConnectionFor:: Found connection for stream [" + streamName + "]"); - } - } - - public function handleStreamPathReceived(streamName:String, connectionPath:String):void { - trace("VideoProxy::handleStreamPathReceived:: Path for stream [" + streamName + "]: [" + connectionPath + "]"); - - var newUrl:String; - var streamPrefix:String; - - // Check whether the is through proxy servers or not - if(connectionPath == "") { - newUrl = _url; - streamPrefix = ""; - } - else { - var ipRegex:RegExp = /([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/; - var serverIp:String = connectionPath.split("/")[0]; - newUrl = _url.replace(ipRegex, serverIp); - streamPrefix = connectionPath.replace(serverIp, ""); - } - - if(streamPrefix != "") - streamPrefix = streamPrefix + "/"; - - // Store URL for this stream - streamUrlDict[streamName] = newUrl; - - // Set current streamPrefix to use the current path - streamNamePrefixDict[streamName] = streamPrefix; - - if(urlStreamsDict[newUrl] == null) { - urlStreamsDict[newUrl] = new Array(); - urlStreamsDict[streamPrefix+streamName] = urlStreamsDict[newUrl]; - } - urlStreamsDict[newUrl].push(streamName); - - // If connection with this URL does not exist - if(!playConnectionDict[newUrl]){ - // Create new NetConnection and store it - var connection:NetConnection = new NetConnection(); - connection.client = this; - connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); - connection.addEventListener(IOErrorEvent.IO_ERROR, onIOError); - connection.addEventListener(NetStatusEvent.NET_STATUS, onPlayNetStatus); - connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); - connection.connect(newUrl, UsersUtil.getInternalMeetingID(), UsersUtil.getMyUserID()); - trace("VideoProxy::handleStreamPathReceived:: Creating NetConnection for [" + newUrl + "]"); - playConnectionDict[newUrl] = connection; - } - else { - if(playConnectionDict[newUrl].connected) { - // Connection is ready, send event - var dispatcher:Dispatcher = new Dispatcher(); - dispatcher.dispatchEvent(new PlayConnectionReady(streamName, playConnectionDict[newUrl], streamPrefix)); - } - trace("VideoProxy::handleStreamPathReceived:: Found NetConnection for [" + newUrl + "]"); - } - } - - public function getConnectionForStream(stream:String):NetConnection { - var url:String = streamUrlDict[stream]; - return playConnectionDict[url]; - } - - public function getPrefixForStream(stream:String):String { - if(streamNamePrefixDict[stream]) - return streamNamePrefixDict[stream]; - else - return ""; - } - - public function closePlayConnectionFor(streamName:String):void { - var temp:Array = streamName.split("/"); - var stream:String = temp[temp.length-1]; - var streamUrl:String = streamUrlDict[stream]; - - // Remove the url entry for this stream - delete streamUrlDict[stream]; - - // Check if the connection should be closed - var streams:Array = urlStreamsDict[streamUrl]; - if(streams != null) { - streams = streams.filter(function(item:*, index:int, array:Array):Boolean { return item != stream }); - urlStreamsDict[streamUrl] = streams; - } - // Do not close publish connection, no matter what - if(playConnectionDict[streamUrl] == nc) - return; - if(streams == null || streams.length <= 0) { - trace("VideoProxy:: closePlayConnectionFor:: Closing connection with: [" + streamUrl + "]"); - // No one else is using this NetConnection - var connection:NetConnection = playConnectionDict[streamUrl]; - if(connection != null) connection.close(); - delete playConnectionDict[streamUrl]; - delete urlStreamsDict[streamUrl]; - } - else { - trace("VideoProxy:: closePlayConnectionFor:: Connection with: [" + streamUrl + "] has [" + streams.length + "] streams"); - } - } - - public function startPublishing(e:StartBroadcastEvent):void{ - 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) { - LogUtil.info("Using H264 codec for video."); - var h264:H264VideoStreamSettings = new H264VideoStreamSettings(); - var h264profile:String = H264Profile.MAIN; - if (e.videoProfile.h264Profile != "main") { - h264profile = H264Profile.BASELINE; - } - var h264Level:String = H264Level.LEVEL_4_1; - switch (e.videoProfile.h264Level) { - case "1": h264Level = H264Level.LEVEL_1; break; - case "1.1": h264Level = H264Level.LEVEL_1_1; break; - case "1.2": h264Level = H264Level.LEVEL_1_2; break; - case "1.3": h264Level = H264Level.LEVEL_1_3; break; - case "1b": h264Level = H264Level.LEVEL_1B; break; - case "2": h264Level = H264Level.LEVEL_2; break; - case "2.1": h264Level = H264Level.LEVEL_2_1; break; - case "2.2": h264Level = H264Level.LEVEL_2_2; break; - case "3": h264Level = H264Level.LEVEL_3; break; - case "3.1": h264Level = H264Level.LEVEL_3_1; break; - case "3.2": h264Level = H264Level.LEVEL_3_2; break; - case "4": h264Level = H264Level.LEVEL_4; break; - case "4.1": h264Level = H264Level.LEVEL_4_1; break; - case "4.2": h264Level = H264Level.LEVEL_4_2; break; - case "5": h264Level = H264Level.LEVEL_5; break; - case "5.1": h264Level = H264Level.LEVEL_5_1; break; - } - - LogUtil.info("Codec used: " + h264Level); - - h264.setProfileLevel(h264profile, h264Level); - ns.videoStreamSettings = h264; - } - - ns.publish(e.stream); - camerasPublishing[e.stream] = ns; - } - - public function stopBroadcasting(stream:String):void{ - trace("Closing netstream for webcam publishing"); - if (camerasPublishing[stream] != null) { - var ns:NetStream = camerasPublishing[stream]; - ns.attachCamera(null); - ns.close(); - ns = null; - delete camerasPublishing[stream]; - } - } - - public function stopAllBroadcasting():void { - for each (var ns:NetStream in camerasPublishing) - { - ns.attachCamera(null); - ns.close(); - ns = null; - } - camerasPublishing = new Object(); - } - - public function disconnect():void { - trace("VideoProxy:: disconnecting from Video application"); - stopAllBroadcasting(); - // Close publish NetConnection - if (nc != null) nc.close(); - // Close play NetConnections - for (var k:Object in playConnectionDict) { - var connection:NetConnection = playConnectionDict[k]; - connection.close(); - } - // Reset dictionaries - playConnectionDict = new Dictionary(); - streamNamePrefixDict = new Dictionary(); - urlStreamsDict = new Dictionary(); - streamUrlDict = new Dictionary(); - } - - 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 - trace("bandwidth = " + p_bw + " Kbps."); - } - - - } -} +/** +* 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.videoconf.business +{ + import com.asfusion.mate.events.Dispatcher; + + import flash.events.AsyncErrorEvent; + import flash.events.IOErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.SecurityErrorEvent; + import flash.media.H264Level; + import flash.media.H264Profile; + import flash.media.H264VideoStreamSettings; + import flash.net.NetConnection; + import flash.net.NetStream; + import flash.system.Capabilities; + import flash.utils.Dictionary; + + import mx.collections.ArrayCollection; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; + import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; + import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; + import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady; + import org.bigbluebutton.modules.videoconf.services.messaging.MessageSender; + import org.bigbluebutton.modules.videoconf.services.messaging.MessageReceiver; + import org.bigbluebutton.modules.videoconf.events.PlayConnectionClosedEvent; + + + public class VideoProxy + { + public var videoOptions:VideoConfOptions; + + // NetConnection used for stream publishing + private var nc:NetConnection; + // NetStream used for stream publishing + private var ns:NetStream; + private var _url:String; + private var camerasPublishing:Object = new Object(); + private var connected:Boolean = false; + + // Message sender to request stream path + private var msgSender:MessageSender; + // Message receiver to receive the stream path + private var msgReceiver:MessageReceiver; + + // Dictionary<url,NetConnection> used for stream playing + private var playConnectionDict:Dictionary; + // Dictionary<url,Array<streamName>> used to keep track of streams using a URL + private var urlStreamsDict:Dictionary; + // Dictionary<streamName,streamNamePrefix> used for stream playing + private var streamNamePrefixDict:Dictionary; + // Dictionary<streamName,url> + private var streamUrlDict:Dictionary; + + private function parseOptions():void { + videoOptions = new VideoConfOptions(); + videoOptions.parseOptions(); + } + + public function VideoProxy(url:String) + { + _url = url; + parseOptions(); + nc = new NetConnection(); + nc.proxyType = "best"; + nc.client = this; + nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); + nc.addEventListener(IOErrorEvent.IO_ERROR, onIOError); + nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); + nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + playConnectionDict = new Dictionary(); + urlStreamsDict = new Dictionary(); + streamNamePrefixDict = new Dictionary(); + streamUrlDict = new Dictionary(); + msgReceiver = new MessageReceiver(this); + msgSender = new MessageSender(); + } + + public function connect():void { + nc.connect(_url, UsersUtil.getInternalMeetingID(), UsersUtil.getMyUserID()); + playConnectionDict[_url] = nc; + urlStreamsDict[_url] = new Array(); + } + + private function onAsyncError(event:AsyncErrorEvent):void{ + } + + private function onIOError(event:NetStatusEvent):void{ + } + + private function onConnectedToVideoApp():void{ + var dispatcher:Dispatcher = new Dispatcher(); + dispatcher.dispatchEvent(new ConnectedEvent(ConnectedEvent.VIDEO_CONNECTED)); + } + + private function onNetStatus(event:NetStatusEvent):void{ + switch(event.info.code){ + case "NetConnection.Connect.Success": + connected = true; + //ns = new NetStream(nc); + onConnectedToVideoApp(); + break; + default: + LogUtil.debug("[" + event.info.code + "] for [" + _url + "]"); + connected = false; + break; + } + } + + private function onSecurityError(event:NetStatusEvent):void{ + } + + public function get publishConnection():NetConnection{ + return this.nc; + } + + private function onPlayNetStatus(event:NetStatusEvent):void { + var url:String = event.target.uri; + var streams:Array = urlStreamsDict[url]; + var dispatcher:Dispatcher = new Dispatcher(); + var prefix:String; + var stream:String; + switch(event.info.code){ + case "NetConnection.Connect.Success": + // Notify streams from this connection + var conn:NetConnection = playConnectionDict[url]; + for each (stream in streams) { + prefix = streamNamePrefixDict[stream]; + dispatcher.dispatchEvent(new PlayConnectionReady(stream, conn, prefix)); + } + break; + case "NetConnection.Connect.Failed": + case "NetConnection.Connect.Closed": + trace("[" + event.info.code + "] for a play connection at [" + url + "]"); + trace("Affected streams: ["+streams+"]"); + for each (stream in streams) { + prefix = streamNamePrefixDict[stream]; + delete streamNamePrefixDict[stream]; + delete streamUrlDict[stream]; + dispatcher.dispatchEvent(new PlayConnectionClosedEvent(stream, prefix)); + } + delete playConnectionDict[url]; + delete urlStreamsDict[url]; + break; + default: + LogUtil.debug("[" + event.info.code + "] for a play connection at [" + url + "]"); + break; + } + } + + public function createPlayConnectionFor(streamName:String):void { + LogUtil.debug("VideoProxy::createPlayConnectionFor:: Requesting path for stream [" + streamName + "]"); + // Check if a connection already exists + if(!streamUrlDict[streamName]) { + trace("VideoProxy::createPlayConnectionFor:: Requesting path for stream [" + streamName + "]"); + // Ask red5 the path to stream + msgSender.getStreamPath(streamName); + } + else { + trace("VideoProxy::createPlayConnectionFor:: Found connection for stream [" + streamName + "]"); + } + } + + public function handleStreamPathReceived(streamName:String, connectionPath:String):void { + trace("VideoProxy::handleStreamPathReceived:: Path for stream [" + streamName + "]: [" + connectionPath + "]"); + + var newUrl:String; + var streamPrefix:String; + + // Check whether the is through proxy servers or not + if(connectionPath == "") { + newUrl = _url; + streamPrefix = ""; + } + else { + var ipRegex:RegExp = /([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/; + var serverIp:String = connectionPath.split("/")[0]; + newUrl = _url.replace(ipRegex, serverIp); + streamPrefix = connectionPath.replace(serverIp, ""); + } + + if(streamPrefix != "") + streamPrefix = streamPrefix + "/"; + + // Store URL for this stream + streamUrlDict[streamName] = newUrl; + + // Set current streamPrefix to use the current path + streamNamePrefixDict[streamName] = streamPrefix; + + if(urlStreamsDict[newUrl] == null) { + urlStreamsDict[newUrl] = new Array(); + urlStreamsDict[streamPrefix+streamName] = urlStreamsDict[newUrl]; + } + urlStreamsDict[newUrl].push(streamName); + + // If connection with this URL does not exist + if(!playConnectionDict[newUrl]){ + // Create new NetConnection and store it + var connection:NetConnection = new NetConnection(); + connection.client = this; + connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); + connection.addEventListener(IOErrorEvent.IO_ERROR, onIOError); + connection.addEventListener(NetStatusEvent.NET_STATUS, onPlayNetStatus); + connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + connection.connect(newUrl, UsersUtil.getInternalMeetingID(), UsersUtil.getMyUserID()); + trace("VideoProxy::handleStreamPathReceived:: Creating NetConnection for [" + newUrl + "]"); + playConnectionDict[newUrl] = connection; + } + else { + if(playConnectionDict[newUrl].connected) { + // Connection is ready, send event + var dispatcher:Dispatcher = new Dispatcher(); + dispatcher.dispatchEvent(new PlayConnectionReady(streamName, playConnectionDict[newUrl], streamPrefix)); + } + trace("VideoProxy::handleStreamPathReceived:: Found NetConnection for [" + newUrl + "]"); + } + } + + public function getConnectionForStream(stream:String):NetConnection { + var url:String = streamUrlDict[stream]; + return playConnectionDict[url]; + } + + public function getPrefixForStream(stream:String):String { + if(streamNamePrefixDict[stream]) + return streamNamePrefixDict[stream]; + else + return ""; + } + + public function closePlayConnectionFor(streamName:String):void { + var temp:Array = streamName.split("/"); + var stream:String = temp[temp.length-1]; + var streamUrl:String = streamUrlDict[stream]; + + // Remove the url entry for this stream + delete streamUrlDict[stream]; + + // Check if the connection should be closed + var streams:Array = urlStreamsDict[streamUrl]; + if(streams != null) { + streams = streams.filter(function(item:*, index:int, array:Array):Boolean { return item != stream }); + urlStreamsDict[streamUrl] = streams; + } + // Do not close publish connection, no matter what + if(playConnectionDict[streamUrl] == nc) + return; + if(streams == null || streams.length <= 0) { + trace("VideoProxy:: closePlayConnectionFor:: Closing connection with: [" + streamUrl + "]"); + // No one else is using this NetConnection + var connection:NetConnection = playConnectionDict[streamUrl]; + if(connection != null) connection.close(); + delete playConnectionDict[streamUrl]; + delete urlStreamsDict[streamUrl]; + } + else { + trace("VideoProxy:: closePlayConnectionFor:: Connection with: [" + streamUrl + "] has [" + streams.length + "] streams"); + } + } + + public function startPublishing(e:StartBroadcastEvent):void{ + 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) { + LogUtil.info("Using H264 codec for video."); + var h264:H264VideoStreamSettings = new H264VideoStreamSettings(); + var h264profile:String = H264Profile.MAIN; + if (e.videoProfile.h264Profile != "main") { + h264profile = H264Profile.BASELINE; + } + var h264Level:String = H264Level.LEVEL_4_1; + switch (e.videoProfile.h264Level) { + case "1": h264Level = H264Level.LEVEL_1; break; + case "1.1": h264Level = H264Level.LEVEL_1_1; break; + case "1.2": h264Level = H264Level.LEVEL_1_2; break; + case "1.3": h264Level = H264Level.LEVEL_1_3; break; + case "1b": h264Level = H264Level.LEVEL_1B; break; + case "2": h264Level = H264Level.LEVEL_2; break; + case "2.1": h264Level = H264Level.LEVEL_2_1; break; + case "2.2": h264Level = H264Level.LEVEL_2_2; break; + case "3": h264Level = H264Level.LEVEL_3; break; + case "3.1": h264Level = H264Level.LEVEL_3_1; break; + case "3.2": h264Level = H264Level.LEVEL_3_2; break; + case "4": h264Level = H264Level.LEVEL_4; break; + case "4.1": h264Level = H264Level.LEVEL_4_1; break; + case "4.2": h264Level = H264Level.LEVEL_4_2; break; + case "5": h264Level = H264Level.LEVEL_5; break; + case "5.1": h264Level = H264Level.LEVEL_5_1; break; + } + + LogUtil.info("Codec used: " + h264Level); + + h264.setProfileLevel(h264profile, h264Level); + ns.videoStreamSettings = h264; + } + + ns.publish(e.stream); + camerasPublishing[e.stream] = ns; + } + + public function stopBroadcasting(stream:String):void{ + trace("Closing netstream for webcam publishing"); + if (camerasPublishing[stream] != null) { + var ns:NetStream = camerasPublishing[stream]; + ns.attachCamera(null); + ns.close(); + ns = null; + delete camerasPublishing[stream]; + } + } + + public function stopAllBroadcasting():void { + for each (var ns:NetStream in camerasPublishing) + { + ns.attachCamera(null); + ns.close(); + ns = null; + } + camerasPublishing = new Object(); + } + + public function disconnect():void { + trace("VideoProxy:: disconnecting from Video application"); + stopAllBroadcasting(); + // Close publish NetConnection + if (nc != null) nc.close(); + // Close play NetConnections + for (var k:Object in playConnectionDict) { + var connection:NetConnection = playConnectionDict[k]; + connection.close(); + } + // Reset dictionaries + playConnectionDict = new Dictionary(); + streamNamePrefixDict = new Dictionary(); + urlStreamsDict = new Dictionary(); + streamUrlDict = new Dictionary(); + } + + 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 + trace("bandwidth = " + p_bw + " Kbps."); + } + + + } +} diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml index 3e5f8d31eac28d0666c6a0069fb4b4bd5e92bcc7..62f23acc6cbc00dbb39825c7d490a8665be05717 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml @@ -1,144 +1,144 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - -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/>. - ---> - -<EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/"> - <mx:Script> - <![CDATA[ - import org.bigbluebutton.core.events.ConnectAppEvent; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.MadePresenterEvent; - import org.bigbluebutton.main.events.StoppedViewingWebcamEvent; - import org.bigbluebutton.main.events.UserJoinedEvent; - import org.bigbluebutton.main.events.UserLeftEvent; - import org.bigbluebutton.main.model.users.events.StreamStartedEvent; - import org.bigbluebutton.main.model.users.events.StreamStoppedEvent; - import org.bigbluebutton.modules.users.events.ViewCameraEvent; - import org.bigbluebutton.modules.videoconf.events.ClosePublishWindowEvent; - import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; - import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; - import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; - import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent; - import org.bigbluebutton.modules.videoconf.events.StopShareCameraRequestEvent; - import org.bigbluebutton.modules.videoconf.events.VideoModuleStartEvent; - import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent; - import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady; - import org.bigbluebutton.modules.videoconf.events.PlayConnectionClosedEvent; - ]]> - </mx:Script> - - <EventHandlers type="{VideoModuleStartEvent.START}"> - <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> - <MethodInvoker generator="{VideoEventMapDelegate}" method="start" arguments="{event.uri}"/> - <EventAnnouncer generator="{ConnectAppEvent}" type="{ConnectAppEvent.CONNECT_VIDEO_APP}" /> - </EventHandlers> - - <EventHandlers type="{VideoModuleStopEvent.STOP}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="stopModule"/> - </EventHandlers> - - <EventHandlers type="{BBBEvent.CAMERA_SETTING}" > - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleCameraSetting" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{ConnectAppEvent.CONNECT_VIDEO_APP}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="connectToVideoApp" /> - </EventHandlers> - - <EventHandlers type="{ShareCameraRequestEvent.SHARE_CAMERA_REQUEST}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleShareCameraRequestEvent" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{StopShareCameraRequestEvent.STOP_SHARE_CAMERA_REQUEST}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStopShareCameraRequestEvent" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{StopShareCameraRequestEvent.STOP_SHARE_ALL_CAMERA_REQUEST}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStopAllShareCameraRequestEvent" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{StartBroadcastEvent.START_BROADCAST}" > - <MethodInvoker generator="{VideoEventMapDelegate}" method="startPublishing" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{StopBroadcastEvent.STOP_BROADCASTING}" > - <MethodInvoker generator="{VideoEventMapDelegate}" method="stopPublishing" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{StreamStartedEvent.STREAM_STARTED}"> - <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> - <MethodInvoker generator="{VideoEventMapDelegate}" method="viewCamera" arguments="{[event.userID, event.stream, event.user]}" /> - </EventHandlers> - - <EventHandlers type="{StreamStoppedEvent.STREAM_STOPPED}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStreamStoppedEvent" arguments="{[event.userID, event.stream]}" /> - </EventHandlers> - - <EventHandlers type="{ViewCameraEvent.VIEW_CAMERA_EVENT}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="viewCamera" arguments="{[event.userID, event.stream, event.viewedName]}" /> - </EventHandlers> - - <EventHandlers type="{UserJoinedEvent.JOINED}"> - <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleUserJoinedEvent" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{UserLeftEvent.LEFT}"> - <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleUserLeftEvent" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" > - <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> - <MethodInvoker generator="{VideoEventMapDelegate}" method="switchToPresenter" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}"> - <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> - <MethodInvoker generator="{VideoEventMapDelegate}" method="switchToViewer" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{ConnectedEvent.VIDEO_CONNECTED}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="connectedToVideoApp" /> - </EventHandlers> - - <EventHandlers type="{ClosePublishWindowEvent.CLOSE_PUBLISH_WINDOW}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleClosePublishWindowEvent" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{StoppedViewingWebcamEvent.STOPPED_VIEWING_WEBCAM}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStoppedViewingWebcamEvent" arguments="{[event.webcamUserID, event.streamName]}"/> - </EventHandlers> - - <EventHandlers type="{BBBEvent.CAM_SETTINGS_CLOSED}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handleCamSettingsClosedEvent" arguments="{event}"/> - </EventHandlers> - - <EventHandlers type="{PlayConnectionReady.PLAY_CONNECTION_READY}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handlePlayConnectionReady" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{PlayConnectionClosedEvent.PLAY_CONNECTION_CLOSED_EVENT}"> - <MethodInvoker generator="{VideoEventMapDelegate}" method="handlePlayConnectionClosed" arguments="{[event.streamName, event.prefix]}" /> - </EventHandlers> - <!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - -</EventMap> +<?xml version="1.0" encoding="utf-8"?> + +<!-- + +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/>. + +--> + +<EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/"> + <mx:Script> + <![CDATA[ + import org.bigbluebutton.core.events.ConnectAppEvent; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.MadePresenterEvent; + import org.bigbluebutton.main.events.StoppedViewingWebcamEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; + import org.bigbluebutton.main.model.users.events.StreamStartedEvent; + import org.bigbluebutton.main.model.users.events.StreamStoppedEvent; + import org.bigbluebutton.modules.users.events.ViewCameraEvent; + import org.bigbluebutton.modules.videoconf.events.ClosePublishWindowEvent; + import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; + import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; + import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; + import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent; + import org.bigbluebutton.modules.videoconf.events.StopShareCameraRequestEvent; + import org.bigbluebutton.modules.videoconf.events.VideoModuleStartEvent; + import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent; + import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady; + import org.bigbluebutton.modules.videoconf.events.PlayConnectionClosedEvent; + ]]> + </mx:Script> + + <EventHandlers type="{VideoModuleStartEvent.START}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="start" arguments="{event.uri}"/> + <EventAnnouncer generator="{ConnectAppEvent}" type="{ConnectAppEvent.CONNECT_VIDEO_APP}" /> + </EventHandlers> + + <EventHandlers type="{VideoModuleStopEvent.STOP}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="stopModule"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.CAMERA_SETTING}" > + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleCameraSetting" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{ConnectAppEvent.CONNECT_VIDEO_APP}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="connectToVideoApp" /> + </EventHandlers> + + <EventHandlers type="{ShareCameraRequestEvent.SHARE_CAMERA_REQUEST}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleShareCameraRequestEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{StopShareCameraRequestEvent.STOP_SHARE_CAMERA_REQUEST}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStopShareCameraRequestEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{StopShareCameraRequestEvent.STOP_SHARE_ALL_CAMERA_REQUEST}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStopAllShareCameraRequestEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{StartBroadcastEvent.START_BROADCAST}" > + <MethodInvoker generator="{VideoEventMapDelegate}" method="startPublishing" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{StopBroadcastEvent.STOP_BROADCASTING}" > + <MethodInvoker generator="{VideoEventMapDelegate}" method="stopPublishing" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{StreamStartedEvent.STREAM_STARTED}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="viewCamera" arguments="{[event.userID, event.stream, event.user]}" /> + </EventHandlers> + + <EventHandlers type="{StreamStoppedEvent.STREAM_STOPPED}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStreamStoppedEvent" arguments="{[event.userID, event.stream]}" /> + </EventHandlers> + + <EventHandlers type="{ViewCameraEvent.VIEW_CAMERA_EVENT}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="viewCamera" arguments="{[event.userID, event.stream, event.viewedName]}" /> + </EventHandlers> + + <EventHandlers type="{UserJoinedEvent.JOINED}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleUserJoinedEvent" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{UserLeftEvent.LEFT}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleUserLeftEvent" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" > + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="switchToPresenter" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="switchToViewer" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{ConnectedEvent.VIDEO_CONNECTED}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="connectedToVideoApp" /> + </EventHandlers> + + <EventHandlers type="{ClosePublishWindowEvent.CLOSE_PUBLISH_WINDOW}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleClosePublishWindowEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{StoppedViewingWebcamEvent.STOPPED_VIEWING_WEBCAM}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStoppedViewingWebcamEvent" arguments="{[event.webcamUserID, event.streamName]}"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.CAM_SETTINGS_CLOSED}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleCamSettingsClosedEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{PlayConnectionReady.PLAY_CONNECTION_READY}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handlePlayConnectionReady" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{PlayConnectionClosedEvent.PLAY_CONNECTION_CLOSED_EVENT}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handlePlayConnectionClosed" arguments="{[event.streamName, event.prefix]}" /> + </EventHandlers> + <!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> + +</EventMap> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as index 2998091b912b54f91805e763a97b43ffdb1f382f..52873d7eb653c81b5cf68ccc7078fe7889249700 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as @@ -79,7 +79,8 @@ package org.bigbluebutton.modules.videoconf.model [Bindable] public var glowBlurSize:Number = 30.0; - [Bindable] + + [Bindable] public var priorityRatio:Number = 2/3; public function VideoConfOptions() { @@ -165,7 +166,7 @@ package org.bigbluebutton.modules.videoconf.model glowBlurSize = Number(vxml.@glowBlurSize.toString()); } if (vxml.@priorityRatio != undefined) { - priorityRatio = Number(vxml.@priorityRatio.toString()); + priorityRatio = Number(vxml.@priorityRatio.toString()); } } } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml index 5e547d63b8024b81ad4aaed14c3b2e1b16c173a6..4214254a5fa35dae9d2b97e9b4d325a022522375 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml @@ -36,39 +36,37 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mate:Listener type="{BBBEvent.USER_VOICE_MUTED}" method="handleUserVoiceMutedEvent" /> <mate:Listener type="{EventConstants.USER_TALKING}" method="handleUserTalkingEvent" /> - <mate:Listener type="{SwitchedPresenterEvent.SWITCHED_PRESENTER}" method="handleSwitchedPresenterEvent" /> - <mate:Listener type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" method="handleMadePresenterEvent" /> - <mate:Listener type="{BBBEvent.USER_VOICE_JOINED}" method="handleNewRoleEvent" /> - <mate:Listener type="{BBBEvent.USER_VOICE_LEFT}" method="handleNewRoleEvent" /> + <mate:Listener type="{SwitchedPresenterEvent.SWITCHED_PRESENTER}" method="handleSwitchedPresenterEvent" /> + <mate:Listener type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" method="handleMadePresenterEvent" /> + <mate:Listener type="{BBBEvent.USER_VOICE_JOINED}" method="handleNewRoleEvent" /> + <mate:Listener type="{BBBEvent.USER_VOICE_LEFT}" method="handleNewRoleEvent" /> <mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" /> <mx:Script> <![CDATA[ - import flexlib.mdi.events.MDIWindowEvent; + import flexlib.mdi.events.MDIWindowEvent; - import mx.core.UIComponent; - import mx.events.ResizeEvent; - - import org.bigbluebutton.common.Images; - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.common.Role; - import org.bigbluebutton.common.events.CloseWindowEvent; - import org.bigbluebutton.common.events.LocaleChangeEvent; - import org.bigbluebutton.core.EventConstants; - import org.bigbluebutton.core.UsersUtil; - import org.bigbluebutton.core.events.CoreEvent; - import org.bigbluebutton.core.events.SwitchedLayoutEvent; - import org.bigbluebutton.core.managers.UserManager; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.MadePresenterEvent; - import org.bigbluebutton.main.events.SwitchedPresenterEvent; - import org.bigbluebutton.main.views.MainCanvas; - import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay; - import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; - import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; - import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; - import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; - import org.bigbluebutton.util.i18n.ResourceUtil; + import mx.core.UIComponent; + import mx.events.ResizeEvent; + import org.bigbluebutton.common.Images; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.Role; + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.LocaleChangeEvent; + import org.bigbluebutton.core.EventConstants; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.events.CoreEvent; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.MadePresenterEvent; + import org.bigbluebutton.main.events.SwitchedPresenterEvent; + import org.bigbluebutton.main.views.MainCanvas; + import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay; + import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; + import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; + import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; + import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; + import org.bigbluebutton.util.i18n.ResourceUtil; [Bindable] private var defaultWidth:Number = 320; [Bindable] private var defaultHeight:Number = 240; diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index 3356e7cc65b1385060d0806c04803c63fe33ec8f..ef39c792a36e9e1b9a07c2c7b4e3455f3cee5455 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1 +1 @@ -BIGBLUEBUTTON_RELEASE=0.9.0-RC +BIGBLUEBUTTON_RELEASE=0.9.0 diff --git a/bigbluebutton-config/web/default.pdf b/bigbluebutton-config/web/default.pdf old mode 100644 new mode 100755 diff --git a/bigbluebutton-html5/app/client/globals.coffee b/bigbluebutton-html5/app/client/globals.coffee index a81f92b2eb199c3aa0aac2f9c27ff07771542a22..766906ff0b0db52fc1a0b9d7d4105a19ecacda4b 100755 --- a/bigbluebutton-html5/app/client/globals.coffee +++ b/bigbluebutton-html5/app/client/globals.coffee @@ -61,14 +61,6 @@ currentPresentation = Meteor.Presentations.findOne({"presentation.current": true}) currentPresentation?.presentation?.name -# helper to determine whether user has joined any type of audio -Handlebars.registerHelper "amIInAudio", -> - BBB.amIInAudio() - -# helper to determine whether the user is in the listen only audio stream -Handlebars.registerHelper "amIListenOnlyAudio", -> - BBB.amIListenOnlyAudio() - Handlebars.registerHelper "colourToHex", (value) => @window.colourToHex(value) @@ -112,7 +104,7 @@ Handlebars.registerHelper "getUsersInMeeting", -> raised.concat lowered Handlebars.registerHelper "getWhiteboardTitle", -> - (getPresentationFilename() or "Loading presentaion...") + "Presentation: " + (getPresentationFilename() or "Loading...") Handlebars.registerHelper "isCurrentUser", (userId) -> userId is null or userId is BBB.getCurrentUser()?.userId @@ -124,6 +116,9 @@ Handlebars.registerHelper "isCurrentUserRaisingHand", -> user = BBB.getCurrentUser() user?.user?.raise_hand +Handlebars.registerHelper "isCurrentUserSharingAudio", -> + BBB.amISharingAudio() + Handlebars.registerHelper "isCurrentUserSharingVideo", -> BBB.amISharingVideo() @@ -133,15 +128,16 @@ Handlebars.registerHelper "isCurrentUserTalking", -> Handlebars.registerHelper "isDisconnected", -> return !Meteor.status().connected -Handlebars.registerHelper "isUserInAudio", (userId) -> - BBB.isUserInAudio(userId) - -Handlebars.registerHelper "isUserListenOnlyAudio", (userId) -> - BBB.isUserListenOnlyAudio(userId) +Handlebars.registerHelper "isUserListenOnly", (userId) -> + user = Meteor.Users.findOne({userId:userId}) + return user?.user?.listenOnly Handlebars.registerHelper "isUserMuted", (userId) -> BBB.isUserMuted(userId) +Handlebars.registerHelper "isUserSharingAudio", (userId) -> + BBB.isUserSharingAudio(userId) + Handlebars.registerHelper "isUserSharingVideo", (userId) -> BBB.isUserSharingWebcam(userId) @@ -188,11 +184,6 @@ Handlebars.registerHelper "visibility", (section) -> str = str.replace http, "<a href='event:$1'><u>$1</u></a>" str = str.replace www, "$1<a href='event:http://$2'><u>$2</u></a>" -@introToAudio = (event, {isListenOnly} = {}) -> - isListenOnly ?= true - joinVoiceCall event, isListenOnly: isListenOnly - displayWebRTCNotification() - # check the chat history of the user and add tabs for the private chats @populateChatTabs = (msg) -> myUserId = getInSession "userId" @@ -262,45 +253,16 @@ Handlebars.registerHelper "visibility", (section) -> setInSession "display_usersList", !getInSession "display_usersList" setTimeout(redrawWhiteboard, 0) -# Periodically check the status of the WebRTC call, when a call has been established attempt to hangup, -# retry if a call is in progress, send the leave voice conference message to BBB -@exitVoiceCall = (event) -> - # To be called when the hangup is initiated - hangupCallback = -> - console.log "Exiting Voice Conference" - - # Checks periodically until a call is established so we can successfully end the call - # clean state - getInSession("triedHangup", false) - # function to initiate call - (checkToHangupCall = (context) -> - # if an attempt to hang up the call is made when the current session is not yet finished, the request has no effect - # keep track in the session if we haven't tried a hangup - if BBB.getCallStatus() isnt null and !getInSession("triedHangup") - console.log "Attempting to hangup on WebRTC call" - if BBB.amIListenOnlyAudio() # notify BBB-apps we are leaving the call call if we are listen only - Meteor.call('listenOnlyRequestToggle', getInSession("meetingId"), getInSession("userId"), getInSession("authToken"), false) - BBB.leaveVoiceConference hangupCallback - getInSession("triedHangup", true) # we have hung up, prevent retries - else - console.log "RETRYING hangup on WebRTC call in #{Meteor.config.app.WebRTCHangupRetryInterval} ms" - setTimeout checkToHangupCall, Meteor.config.app.WebRTCHangupRetryInterval # try again periodically - )(@) # automatically run function - return false - -# close the daudio UI, then join the conference. If listen only send the request to the server -@joinVoiceCall = (event, {isListenOnly} = {}) -> - $('#joinAudioDialog').dialog('close') - isListenOnly ?= true - - # create voice call params - joinCallback = (message) -> - console.log "Beginning WebRTC Conference Call" - - if isListenOnly - Meteor.call('listenOnlyRequestToggle', getInSession("meetingId"), getInSession("userId"), getInSession("authToken"), true) - BBB.joinVoiceConference joinCallback, isListenOnly # make the call #TODO should we apply role permissions to this action? - +@toggleVoiceCall = (event) -> + if BBB.amISharingAudio() + hangupCallback = -> + console.log "left voice conference" + BBB.leaveVoiceConference hangupCallback #TODO should we apply role permissions to this action? + else + # create voice call params + joinCallback = (message) -> + console.log "started webrtc_call" + BBB.joinVoiceConference joinCallback # make the call #TODO should we apply role permissions to this action? return false @toggleWhiteBoard = -> diff --git a/bigbluebutton-html5/app/client/lib/bbb_api_bridge.coffee b/bigbluebutton-html5/app/client/lib/bbb_api_bridge.coffee old mode 100755 new mode 100644 index 26aa86c2caf527605b607327e36aae45b2d773d7..18747f6a8267d0616e203b78aaf545b8c7de3928 --- a/bigbluebutton-html5/app/client/lib/bbb_api_bridge.coffee +++ b/bigbluebutton-html5/app/client/lib/bbb_api_bridge.coffee @@ -57,48 +57,17 @@ https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/ BBB.isUserSharingWebcam = (userId, callback) -> BBB.getUser(userId)?.user?.webcam_stream?.length isnt 0 - # returns whether the user has joined any type of audio - BBB.amIInAudio = (callback) -> - user = BBB.getCurrentUser() - user?.user?.listenOnly or user?.user?.voiceUser?.joined - - # returns true if the user has joined the listen only audio stream - BBB.amIListenOnlyAudio = (callback) -> - BBB.isUserListenOnlyAudio BBB.getCurrentUser()?.userId - - # returns whether the user has joined the voice conference and is sharing audio through a microphone - BBB.amISharingAudio = (callback) -> - BBB.isUserSharingAudio BBB.getCurrentUser()?.userId - - # returns whether the user is currently talking BBB.amITalking = (callback) -> BBB.isUserTalking BBB.getCurrentUser()?.userId - BBB.isUserInAudio = (userId, callback) -> - user = BBB.getUser(userId) - user?.user?.listenOnly or user?.user?.voiceUser?.joined - - BBB.isUserListenOnlyAudio = (userId, callback) -> - BBB.getUser(userId)?.user?.listenOnly - - BBB.isUserSharingAudio = (userId, callback) -> - BBB.getUser(userId)?.user?.voiceUser?.joined - BBB.isUserTalking = (userId, callback) -> BBB.getUser(userId)?.user?.voiceUser?.talking - # returns true if the current user is marked as locked - BBB.amILocked = () -> - return BBB.getCurrentUser()?.user.locked - - # check whether the user is locked AND the current lock settings for the room - # includes locking the microphone of viewers (listenOnly is still alowed) - BBB.isMyMicLocked = () -> - lockedMicForRoom = Meteor.Meetings.findOne()?.roomLockSettings.disableMic - # note that voiceUser.locked is not used in BigBlueButton at this stage (April 2015) - - return lockedMicForRoom and BBB.amILocked() + BBB.amISharingAudio = (callback) -> + BBB.isUserSharingAudio BBB.getCurrentUser()?.userId + BBB.isUserSharingAudio = (userId) -> + BBB.getUser(userId)?.user?.voiceUser?.joined ### Raise user's hand. @@ -218,12 +187,9 @@ https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/ ### Join the voice conference. - isListenOnly: signifies whether the user joining the conference audio requests to join the listen only stream ### - BBB.joinVoiceConference = (callback, isListenOnly) -> - if BBB.isMyMicLocked() - callIntoConference(BBB.getMyVoiceBridge(), callback, true) #true because we force isListenOnly mode - callIntoConference(BBB.getMyVoiceBridge(), callback, isListenOnly) + BBB.joinVoiceConference = (callback) -> + callIntoConference(BBB.getMyVoiceBridge(), callback) ### Leave the voice conference. @@ -231,12 +197,6 @@ https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/ BBB.leaveVoiceConference = (callback) -> webrtc_hangup callback # sign out of call - ### - Get a hold of the object containing the call information - ### - BBB.getCallStatus = -> - getCallStatus() - ### Share user's webcam. @@ -380,7 +340,7 @@ https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/ listeners = {} ### - 3rd-party apps should use this method to register to listen for events. + 3rd-party apps should user this method to register to listen for events. ### BBB.listen = (eventName, handler) -> diff --git a/bigbluebutton-html5/app/client/main.coffee b/bigbluebutton-html5/app/client/main.coffee index 0bb320728738052fdfc111dc98fb15dd8954e0dd..cec262d87a475ac47589c3e228d73c349f2ebfd6 100755 --- a/bigbluebutton-html5/app/client/main.coffee +++ b/bigbluebutton-html5/app/client/main.coffee @@ -7,84 +7,6 @@ safariIconPath = 'M16.154,5.135c-0.504,0-1,0.031-1.488,0.089l-0.036-0.18c-0.021- # RaphaelJS "Internet Explorer" icon ieIconPath = 'M27.998,2.266c-2.12-1.91-6.925,0.382-9.575,1.93c-0.76-0.12-1.557-0.185-2.388-0.185c-3.349,0-6.052,0.985-8.106,2.843c-2.336,2.139-3.631,4.94-3.631,8.177c0,0.028,0.001,0.056,0.001,0.084c3.287-5.15,8.342-7.79,9.682-8.487c0.212-0.099,0.338,0.155,0.141,0.253c-0.015,0.042-0.015,0,0,0c-2.254,1.35-6.434,5.259-9.146,10.886l-0.003-0.007c-1.717,3.547-3.167,8.529-0.267,10.358c2.197,1.382,6.13-0.248,9.295-2.318c0.764,0.108,1.567,0.165,2.415,0.165c5.84,0,9.937-3.223,11.399-7.924l-8.022-0.014c-0.337,1.661-1.464,2.548-3.223,2.548c-2.21,0-3.729-1.211-3.828-4.012l15.228-0.014c0.028-0.578-0.042-0.985-0.042-1.436c0-5.251-3.143-9.355-8.255-10.663c2.081-1.294,5.974-3.209,7.848-1.681c1.407,1.14,0.633,3.533,0.295,4.518c-0.056,0.254,0.24,0.296,0.296,0.057C28.814,5.573,29.026,3.194,27.998,2.266zM13.272,25.676c-2.469,1.475-5.873,2.539-7.539,1.289c-1.243-0.935-0.696-3.468,0.398-5.938c0.664,0.992,1.495,1.886,2.473,2.63C9.926,24.651,11.479,25.324,13.272,25.676zM12.714,13.046c0.042-2.435,1.787-3.49,3.617-3.49c1.928,0,3.49,1.112,3.49,3.49H12.714z' -@displayWebRTCNotification = -> - if getInSession('webrtc_notification_is_displayed') is false # prevents the notification from displaying until the previous one is hidden - if !isWebRTCAvailable() # verifies if the browser supports WebRTC - $('.notification').addClass('webrtc-support-notification') - setInSession 'webrtc_notification_is_displayed', true - pp = new Raphael('browser-icon-container', 35, 35) - if getBrowserName() is 'Safari' - pp.path(safariIconPath).attr({fill: "#000", stroke: "none"}) - $('#notification-text').html("Sorry,<br/>Safari doesn't support WebRTC") - else if getBrowserName() is 'IE' - pp.path(ieIconPath).attr({fill: "#000", stroke: "none"}) - $('#notification-text').html("Sorry,<br/>IE doesn't support WebRTC") - else - pp.path(settingsIconPath).attr({fill: "#000", stroke: "none"}) - $('.notification.ui-widget-content p').css('font-size', '11px') # to make sure the text fits the dialog box - $('#notification-text').html("Sorry,<br/>your browser doesn't support WebRTC") - $('#notification').dialog('open') - setTimeout () -> # waits 2 sec, then hides the notification - $('#notification').dialog('close') - $('.joinAudioButton').blur() - setTimeout () -> # waits 0.5 sec (time to hide the notification), then removes the icons - pp.remove() - $('.notification').removeClass('webrtc-support-notification') - setInSession 'webrtc_notification_is_displayed', false - , 500 - , 2000 - else - if !BBB.amIInAudio() - Tracker.autorun (comp) -> - if BBB.amIInAudio() # display notification when you are in audio - $('#notification').addClass('joined-audio-notification') - $("#browser-icon-container").remove() # remove the space taken up by the unused icon - setInSession 'webrtc_notification_is_displayed', true - - if BBB.amIListenOnlyAudio() # notify the type of audio joined - $('#notification-text').html("You've joined the Listen Only Audio") - else - $('#notification-text').html("You've joined the audio") - - $('#notification').dialog('open') - setTimeout () -> - $('#notification').dialog('close') # close the entire notification - $('.joinAudioButton').blur() - setTimeout () -> - setInSession 'webrtc_notification_is_displayed', false - , 500 - , 3000 - comp.stop() - -# this method gets called from either mobile or desktop UI -# this method will adjust the UI to compensate for the new audio button -displayAudioSelectionMenu = ({isMobile} = {}) -> - isMobile ?= false - $('.joinAudioButton').blur() - - if isMobile - toggleSlidingMenu() - $('.navbarTitle').css('width', '55%') - - # pop open the dialog allowing users to choose the audio options - if isLandscapeMobile() - $('.joinAudio-dialog').addClass('landscape-mobile-joinAudio-dialog') - else - $('.joinAudio-dialog').addClass('desktop-joinAudio-dialog') - - $("#joinAudioDialog").dialog("open") - - -# helper function to reuse some code for the handling of audio join -onAudioJoinHelper = () -> - # if the microphone is locked (lock settings), the viewer is only - # allowed to join the audio as listenOnly. - if BBB.isMyMicLocked() - introToAudio(null, isListenOnly: true) - else - displayAudioSelectionMenu(isMobile: isMobile()) - - # Helper to load javascript libraries from the BBB server loadLib = (libname) -> successCallback = -> @@ -97,6 +19,8 @@ loadLib = (libname) -> # These settings can just be stored locally in session, created at start up Meteor.startup -> + + # Load SIP libraries before the application starts loadLib('sip.js') loadLib('bbb_webrtc_bridge_sip.js') @@ -121,8 +45,55 @@ Template.footer.helpers foot = "(c) #{info.copyrightYear} BigBlueButton Inc. [build #{info.bbbServerVersion} - #{info.dateOfBuild}] - For more information visit #{info.link}" Template.header.events - "click .joinAudioButton": (event) -> - onAudioJoinHelper() + "click .audioFeedIcon": (event) -> + if getInSession('webrtc_notification_is_displayed') is false # prevents the notification from displaying until the previous one is hidden + if !isWebRTCAvailable() # verifies if the browser supports WebRTC + $('.notification').addClass('webrtc-support-notification') + setInSession 'webrtc_notification_is_displayed', true + pp = new Raphael('browser-icon-container', 35, 35) + if getBrowserName() is 'Safari' + pp.path(safariIconPath).attr({fill: "#000", stroke: "none"}) + $('#notification-text').html("Sorry,<br/>Safari doesn't support WebRTC") + else if getBrowserName() is 'IE' + pp.path(ieIconPath).attr({fill: "#000", stroke: "none"}) + $('#notification-text').html("Sorry,<br/>IE doesn't support WebRTC") + else + pp.path(settingsIconPath).attr({fill: "#000", stroke: "none"}) + $('.notification.ui-widget-content p').css('font-size', '11px') # to make sure the text fits the dialog box + $('#notification-text').html("Sorry,<br/>your browser doesn't support WebRTC") + $('#notification').dialog('open') + setTimeout () -> # waits 2 sec, then hides the notification + $('#notification').dialog('close') + $('.audioFeedIcon').blur() + setTimeout () -> # waits 0.5 sec (time to hide the notification), then removes the icons + pp.remove() + $('.notification').removeClass('webrtc-support-notification') + setInSession 'webrtc_notification_is_displayed', false + , 500 + , 2000 + else + if !BBB.amISharingAudio() + Tracker.autorun (comp) -> + if BBB.amISharingAudio() + $('.notification').addClass('joined-audio-notification') + setInSession 'webrtc_notification_is_displayed', true + $('#notification-text').html("You've joined the audio") + $('#notification').dialog('open') + setTimeout () -> + $('#notification').dialog('close') + $('.audioFeedIcon').blur() + setTimeout () -> + $('.notification').removeClass('joined-audio-notification') + setInSession 'webrtc_notification_is_displayed', false + , 500 + , 2000 + comp.stop() + $('.audioFeedIcon').blur() + toggleVoiceCall @ + if BBB.amISharingAudio() + $('.navbarTitle').css('width', $('#navbar').width() - 358.4) + else + $('.navbarTitle').css('width', $('#navbar').width() - 409.6) "click .chatBarIcon": (event) -> $(".tooltip").hide() @@ -143,9 +114,6 @@ Template.header.events $(".tooltip").hide() toggleNavbar() - "click .leaveAudioButton": (event) -> - exitVoiceCall event - "click .lowerHand": (event) -> $(".tooltip").hide() Meteor.call('userLowerHand', getInSession("meetingId"), getInSession("userId"), getInSession("userId"), getInSession("authToken")) @@ -155,10 +123,11 @@ Template.header.events toggleMic @ "click .raiseHand": (event) -> + #Meteor.log.info "navbar raise own hand from client" + console.log "navbar raise own hand from client" $(".tooltip").hide() Meteor.call('userRaiseHand', getInSession("meetingId"), getInSession("userId"), getInSession("userId"), getInSession("authToken")) - - # "click .settingsIcon": (event) -> + # "click .settingsIcon": (event) -> # alert "settings" "click .signOutIcon": (event) -> @@ -168,10 +137,11 @@ Template.header.events else $('.logout-dialog').addClass('desktop-logout-dialog') $("#dialog").dialog("open") - "click .hideNavbarIcon": (event) -> $(".tooltip").hide() toggleNavbar() + # "click .settingsIcon": (event) -> + # alert "settings" "click .usersListIcon": (event) -> $(".tooltip").hide() @@ -194,8 +164,14 @@ Template.header.events $("#navbarMinimizedButton").addClass("navbarMinimizedButtonLarge") Template.slidingMenu.events - 'click .joinAudioButton': (event) -> - onAudioJoinHelper() + 'click .audioFeedIcon': (event) -> + $('.audioFeedIcon').blur() + toggleSlidingMenu() + toggleVoiceCall @ + if BBB.amISharingAudio() + $('.navbarTitle').css('width', '70%') + else + $('.navbarTitle').css('width', '55%') 'click .chatBarIcon': (event) -> $('.tooltip').hide() @@ -228,46 +204,11 @@ Template.slidingMenu.events toggleSlidingMenu() $('.collapseButton').blur() - "click .leaveAudioButton": (event) -> - exitVoiceCall event - toggleSlidingMenu() - Template.main.helpers - setTitle: -> - document.title = "BigBlueButton #{window.getMeetingName() ? 'HTML5'}" + setTitle: -> + document.title = "BigBlueButton #{window.getMeetingName() ? 'HTML5'}" Template.main.rendered = -> - # the initialization code for the dialog presenting the user with microphone+listen only options - $("#joinAudioDialog").dialog( - modal: true - draggable: false - resizable: false - autoOpen: false - dialogClass: 'no-close logout-dialog joinAudioDialog' - buttons: [ - { - text: 'Cancel' - click: () -> - $(this).dialog("close") - $(".tooltip").hide() - class: 'btn btn-xs btn-default joinAudioDialogButton' - } - ] - open: (event, ui) -> - $('.ui-widget-overlay').bind 'click', () -> - if isMobile() - $("#joinAudioDialog").dialog('close') - position: { my: "center", at: "center", of: window } - ) - - # jQuery click events are handled here. Meteor click handlers don't get called. - # we pass in a named boolean parameter the whether we wish to join audio as listen only or not - $("#microphone").click -> - introToAudio @, isListenOnly: false - - $("#listen_only").click -> - introToAudio @, isListenOnly: true - $("#dialog").dialog( modal: true draggable: false @@ -316,20 +257,16 @@ Template.main.rendered = -> position: my: 'left top' at: 'left bottom' - of: '.joinAudioButton' + of: '.audioFeedIcon' ) $(window).resize( -> $('#dialog').dialog('close') - $('#joinAudioDialog').dialog('close') ) $('#shield').click () -> toggleSlidingMenu() - if Meteor.config.app.autoJoinAudio - onAudioJoinHelper() - Template.makeButton.rendered = -> $('button[rel=tooltip]').tooltip() diff --git a/bigbluebutton-html5/app/client/main.html b/bigbluebutton-html5/app/client/main.html index c9ca307b9bbda94ef203aded8b6f6728999787d0..aa5828201191b56ad85513d7f558cb5f01e0b71c 100755 --- a/bigbluebutton-html5/app/client/main.html +++ b/bigbluebutton-html5/app/client/main.html @@ -1,7 +1,7 @@ <template name="footer"> - <div id="footer" class="myFooter gradientBar navbar-default"> - {{{getFooterString}}} - </div> + <div id="footer" class="myFooter gradientBar navbar-default"> + {{{getFooterString}}} + </div> </template> <template name="header"> @@ -9,7 +9,7 @@ <div id="navbar" class="myNavbar gradientBar navbar navbar-default navbar-fixed-top" role="navigation"> <div class="navbarUserButtons navbarSection"> <div id="collapseButtonSection"> - {{#if isPortraitMobile}} + {{#if isMobile}} <button class="navbar-toggle btn navbarButton collapseSlidingMenuButton"> <div class="push-menu-icon"> <span class="icon-bar"></span> @@ -36,9 +36,9 @@ <!-- display/hide whiteboard toggle --> {{#if getInSession "display_whiteboard"}} - {{> makeButton btn_class="navbarIconToggleActive whiteboardIcon navbarButton collapseSectionButton" i_class="ion-easel" rel="tooltip" data_placement="bottom" title="Hide Whiteboard"}} + {{> makeButton btn_class="navbarIconToggleActive whiteboardIcon navbarButton collapseSectionButton" i_class="glyphicon glyphicon-pencil" rel="tooltip" data_placement="bottom" title="Hide Whiteboard"}} {{else}} - {{> makeButton btn_class="whiteboardIcon navbarButton collapseSectionButton" i_class="ion-easel" rel="tooltip" data_placement="bottom" title="Show Whiteboard"}} + {{> makeButton btn_class="whiteboardIcon navbarButton collapseSectionButton" i_class="glyphicon glyphicon-pencil" rel="tooltip" data_placement="bottom" title="Show Whiteboard"}} {{/if}} <!-- display/hide chat bar toggle --> @@ -56,26 +56,20 @@ {{/if}} --> </div> <div class='audioControllersSection'> - <!-- We are in a form of audio --> - {{#if amIInAudio}} + <!-- Join/hang up audio call --> + {{#if isCurrentUserSharingAudio}} <div class='hiddenNavbarSection'> - <!-- display the button for leaving audio --> {{> makeButton btn_class="navbarIconToggleActive audioFeedIcon navbarButton audioButton leaveAudioButton" i_class="ion-volume-mute" sharingAudio=true rel="tooltip" data_placement="bottom" title="Leave Audio Call"}} </div> - {{#unless amIListenOnlyAudio}} - {{#if isCurrentUserMuted}} - <!-- if you are muted the button representing your status will show volume off --> - {{> makeButton btn_class="muteIcon navbarButton audioButton" i_class="glyphicon glyphicon-volume-off" sharingAudio=true rel="tooltip" data_placement="bottom" title="Unmute"}} + {{#if isCurrentUserMuted}} + {{> makeButton btn_class="muteIcon navbarButton audioButton" i_class="glyphicon glyphicon-volume-off" sharingAudio=true rel="tooltip" data_placement="bottom" title="Unmute"}} + {{else}} + {{#if isCurrentUserTalking}} + {{> makeButton btn_class="navbarIconToggleActive muteIcon navbarButton audioButton" i_class="glyphicon glyphicon-volume-up" sharingAudio=true rel="tooltip" data_placement="bottom" title="Mute"}} {{else}} - {{#if isCurrentUserTalking}} - <!-- you are talking. Display a high volume/volume up representing voice activity --> - {{> makeButton btn_class="navbarIconToggleActive muteIcon navbarButton audioButton" i_class="glyphicon glyphicon-volume-up" sharingAudio=true rel="tooltip" data_placement="bottom" title="Mute"}} - {{else}} - <!-- you are not talking. Display low volume/volume down representing no voice activity --> - {{> makeButton btn_class="navbarIconToggleActive muteIcon navbarButton audioButton" i_class="glyphicon glyphicon-volume-down" sharingAudio=true rel="tooltip" data_placement="bottom" title="Mute"}} - {{/if}} + {{> makeButton btn_class="navbarIconToggleActive muteIcon navbarButton audioButton" i_class="glyphicon glyphicon-volume-down" sharingAudio=true rel="tooltip" data_placement="bottom" title="Mute"}} {{/if}} - {{/unless}} + {{/if}} {{else}} <div class='hiddenNavbarSection'> {{> makeButton btn_class="audioFeedIcon navbarButton audioButton joinAudioButton" i_class="glyphicon glyphicon-headphones" sharingAudio=false rel="tooltip" data_placement="bottom" title="Join Audio Call"}} @@ -96,6 +90,7 @@ <div class="navbarTitle navbarSection"><span>{{getMeetingName}}</span></div> <div class="navbarSettingsButtons navbarSection"> <!-- {{> makeButton id="userId" btn_class="settingsIcon navbarButton" i_class="glyphicon glyphicon-cog" rel="tooltip" data_placement="bottom" title="Settings"}} --> + <!-- {{> makeButton btn_class="hideNavbarIcon navbarButton" i_class="glyphicon glyphicon-chevron-up" rel="tooltip" data_placement="bottom" title="Hide Navbar"}} --> {{> makeButton btn_class="signOutIcon navbarButton" i_class="glyphicon glyphicon-log-out" rel="tooltip" data_placement="bottom" title="Logout"}} </div> </div> @@ -104,83 +99,56 @@ {{> makeButton id="navbarMinimizedButton" btn_class="hideNavbarIcon navbarMinimizedButtonSmall" i_class="glyphicon glyphicon-chevron-down" rel="tooltip" data_placement="bottom" title="Display Navbar"}} {{/if}} </template> -<!-- dialog that presents user with audio options - contains microphone and listen only with icons --> -<template name="joinAudioDialog"> - <div id="joinAudioDialog" title="How do you want to join the audio?"> - <hr class="joinAudioDialogHr"/> - <div style="float:left; border-right: 2px solid darkgrey; width: 50%; height: 100%"> - <i class="icon ion-mic-a joinAudioDialogIcon"></i> - <br/> - <button id="microphone" class="joinAudioDialogButton">Microphone</button> - </div> - <div style="float:left; width: 50%"> - <i class="icon ion-volume-high joinAudioDialogIcon"></i> - <br/> - <button id="listen_only" class="joinAudioDialogButton">Listen Only</button> - </div> - <br style="clear:both;"/><br/> - <hr style="margin: 10px; border: 1px solid darkgrey;" /> - </div> -</template> <template name="main"> - {{setTitle}} - <body> - <div id="dialog" title="Confirm Logout"> +{{setTitle}} +<body> + <div id="dialog" title="Confirm Logout"> <p>Are you sure you want to log out?</p> - </div> - {{> joinAudioDialog}} - <div id="notification"> + </div> + <div id="notification"> <div id="browser-icon-container"></div> <p id="notification-text"></p> - </div> - - <div id="main" class="mainContainer row-fluid"> - {{#if isDisconnected}} - {{>status}} - {{else}} - <div>{{> header}}</div> - {{> whiteboard id="whiteboard" title=getWhiteboardTitle name="whiteboard"}} - {{> chatbar id="chat" title="Chat" name="chatbar"}} - {{> usersList id="users" name="usersList"}} - <audio id="remote-media" autoplay="autoplay"></audio> - {{> footer}} - {{/if}} - <div id='shield'></div> - </div> - {{#if isPortraitMobile}} - {{> slidingMenu}} + </div> + <div id="main" class="mainContainer row-fluid"> + {{#if isDisconnected}} + {{>status}} + {{else}} + <div>{{> header}}</div> + {{> whiteboard id="whiteboard" title=getWhiteboardTitle name="whiteboard"}} + {{> chatbar id="chat" title="Chat" name="chatbar"}} + {{> usersList id="users" name="usersList"}} + <audio id="remote-media" autoplay="autoplay"></audio> + {{> footer}} {{/if}} - </body> + <div id='shield'></div> + </div> + {{#if isMobile}} + {{> slidingMenu}} + {{/if}} +</body> </template> <template name="recordingStatus"> - <!-- Recording status of the meeting --> - {{#with getCurrentMeeting}} - {{#if intendedForRecording}} - {{#if currentlyBeingRecorded}} - <button class="recordingStatus recordingStatusTrue" rel="tooltip" data-placement="bottom" title="This Meeting is Being Recorded"><span class="glyphicon glyphicon-record"></span> Recording</button> - {{else}} - <button class="recordingStatus recordingStatusFalse" rel="tooltip" data-placement="bottom" title="This Meeting is Not Currently Being Recorded"><span class="glyphicon glyphicon-record"></span></button> - {{/if}} - {{/if}} - {{/with}} + <!-- Recording status of the meeting --> + {{#with getCurrentMeeting}} + {{#if intendedForRecording}} + {{#if currentlyBeingRecorded}} + <button class="recordingStatus recordingStatusTrue" rel="tooltip" data-placement="bottom" title="This Meeting is Being Recorded"><span class="glyphicon glyphicon-record"></span> Recording</button> + {{else}} + <button class="recordingStatus recordingStatusFalse" rel="tooltip" data-placement="bottom" title="This Meeting is Not Currently Being Recorded"><span class="glyphicon glyphicon-record"></span></button> + {{/if}} + {{/if}} + {{/with}} </template> <template name='slidingMenu'> <div class="sliding-menu" id="sliding-menu"> <div class="slideSection"> - {{#if getInSession "display_usersList"}} - {{> makeButton btn_class="navbarIconToggleActive usersListIcon slideButton" i_class="glyphicon glyphicon-user" rel="tooltip" data_placement="right" title="Hide List of Users"}} - {{else}} - {{> makeButton btn_class="usersListIcon slideButton" i_class="glyphicon glyphicon-user" rel="tooltip" data_placement="right" title="Show List of Users"}} - {{/if}} - {{#if getInSession "display_whiteboard"}} - {{> makeButton btn_class="navbarIconToggleActive whiteboardIcon slideButton" i_class="ion-easel" rel="tooltip" data_placement="right" title="Hide Whiteboard"}} + {{> makeButton btn_class="navbarIconToggleActive whiteboardIcon slideButton" i_class="glyphicon glyphicon-pencil" rel="tooltip" data_placement="right" title="Hide Whiteboard"}} {{else}} - {{> makeButton btn_class="whiteboardIcon slideButton" i_class="ion-easel" rel="tooltip" data_placement="right" title="Show Whiteboard"}} + {{> makeButton btn_class="whiteboardIcon slideButton" i_class="glyphicon glyphicon-pencil" rel="tooltip" data_placement="right" title="Show Whiteboard"}} {{/if}} {{#if getInSession "display_chatbar"}} @@ -189,10 +157,16 @@ {{> makeButton btn_class="chatBarIcon slideButton" i_class="glyphicon glyphicon-comment" rel="tooltip" data_placement="right" title="Show Message Pane"}} {{/if}} - {{#if amIInAudio}} - {{> makeButton btn_class="navbarIconToggleActive audioFeedIcon slideButton audioButton leaveAudioButton" i_class="ion-volume-mute" sharingAudio=true rel="tooltip" data_placement="bottom" title="Leave Audio Call"}} + {{#if getInSession "display_usersList"}} + {{> makeButton btn_class="navbarIconToggleActive usersListIcon slideButton" i_class="glyphicon glyphicon-user" rel="tooltip" data_placement="right" title="Hide List of Users"}} + {{else}} + {{> makeButton btn_class="usersListIcon slideButton" i_class="glyphicon glyphicon-user" rel="tooltip" data_placement="right" title="Show List of Users"}} + {{/if}} + + {{#if isCurrentUserSharingAudio}} + {{> makeButton btn_class="navbarIconToggleActive audioFeedIcon slideButton" i_class="ion-volume-mute" sharingAudio=true rel="tooltip" data_placement="right" title="Leave Audio Call"}} {{else}} - {{> makeButton btn_class="audioFeedIcon slideButton audioButton joinAudioButton" i_class="glyphicon glyphicon-headphones" sharingAudio=false rel="tooltip" data_placement="bottom" title="Join Audio Call"}} + {{> makeButton btn_class="audioFeedIcon slideButton" i_class="glyphicon glyphicon-headphones" sharingAudio=false rel="tooltip" data_placement="right" title="Join Audio Call"}} {{/if}} {{#if isCurrentUserRaisingHand}} diff --git a/bigbluebutton-html5/app/client/stylesheets/chat.less b/bigbluebutton-html5/app/client/stylesheets/chat.less index 1407d1b95c8f693872186cb99d6e135d03f20794..1ee07825ecd5ca74076ed64d77072ac86eed4315 100755 --- a/bigbluebutton-html5/app/client/stylesheets/chat.less +++ b/bigbluebutton-html5/app/client/stylesheets/chat.less @@ -1,9 +1,9 @@ @import "variables"; .active { - border: .2vh solid !important; + border: .2vh solid extract(@azure, 2) !important; @media @mobile-portrait-with-keyboard, @mobile-portrait { - border-left: .1vh solid !important; + border-left: .1vh solid extract(@azure, 2) !important; } } @@ -76,18 +76,6 @@ .chatNameSelectorPrivate { .chatNameSelector; width:90%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - @media @landscape { - height: 20px; - } - @media @desktop-portrait { - height: 25px; - } - @media @mobile-portrait, @mobile-portrait-with-keyboard { - font-size: 4vw; - } } .chatNameSelectorPublic { @@ -96,6 +84,12 @@ width:100%; } +.chatOptionsText { + @media @mobile-portrait, @mobile-portrait-with-keyboard { + font-size: 5vw; + } +} + .close:hover { background-color: red; } @@ -119,12 +113,6 @@ padding: 5px; } - -.disabledChat { - background-color: grey; - width: 100% !important; -} - .dropdown { float: left; @media @desktop-portrait, @mobile-portrait, @mobile-portrait-with-keyboard { @@ -140,15 +128,13 @@ } } -#fontSizeTable { - /* Static font sizes are used everywhere in this control. This is the maximum amount of space required */ - height: 64px; - #displayButtons { - text-align:center; - width:84px; +#fontSizeControl { + @media @mobile-portrait, @mobile-portrait-with-keyboard { + font-size:7vw; } - #displayLabel { - width: 154px; + .fontSizeLabel { + height:30px; + text-align:center; } } @@ -156,6 +142,13 @@ background: extract(@yellow, 2) !important; } +#increaseFontSize, #decreaseFontSize { + @media @mobile-portrait, @mobile-portrait-with-keyboard { + height: 100px; + width: 100px; + } +} + #MoreChatsDrop { float: right; } @@ -292,7 +285,7 @@ &:hover { background-color: #ddd; - border: 1px solid; + border: 1px solid extract(@azure, 2); border-bottom: none; } } diff --git a/bigbluebutton-html5/app/client/stylesheets/style.less b/bigbluebutton-html5/app/client/stylesheets/style.less index 7920a4ac22c7d7b7198ded9a6864c5b6a5549410..9a86b20aa9b5cf01b6e16796d98ac8dca2997667 100755 --- a/bigbluebutton-html5/app/client/stylesheets/style.less +++ b/bigbluebutton-html5/app/client/stylesheets/style.less @@ -41,10 +41,6 @@ body { overflow-x: hidden; } -.heading { - margin-left:5px -} - .mainContainer { height: 95%; } @@ -54,7 +50,6 @@ body { color: black; padding-top: 13px; text-align: center; - background-color: #EEEEEE; @media @landscape { position:fixed; bottom:0; @@ -70,13 +65,13 @@ body { font-size: 2vw; } - /*the footer should be visible only at the very bottom of a portrait page*/ + // the footer should be visible only at the very bottom of a portrait page @media @desktop-portrait, @mobile-portrait { order: 4; -webkit-order: 4; } @media @mobile-portrait-with-keyboard { - display: none; /*hide when typing*/ + display: none; //hide when typing } } @@ -88,6 +83,8 @@ body { } .btn { .linear-gradient(rgb(72,76,85), rgb(65,68,77)); + border-left: 1px solid extract(@darkGrey, 2); + border-right: 1px solid extract(@darkGrey, 4); &.navbarIconToggleActive { background: extract(@darkGrey, 3); border-bottom: 4px solid extract(@azure, 1); @@ -217,7 +214,7 @@ body { display: block; float: left; &:hover { - background: extract(@darkGrey, 4); + background: extract(@darkGrey, 3); } } @media @landscape { @@ -226,7 +223,7 @@ body { } .navbarSettingsButtons .btn:hover { - background: extract(@darkGrey, 4); + background: extract(@darkGrey, 3); } .panel-footer { @@ -323,6 +320,77 @@ body { } } +/* Custom alert box */ + +/* Logout menu's properties on mobile devices in landscape orientation */ +.landscape-mobile-logout-dialog { + @media @landscape { + .ui-widget-header { + font-size: 100% !important; + } + width: 55% !important; /* overriding "width: auto;" */ + font-size: 3vh !important; /* overriding "font-size: 11px;" */ + .ui-dialog-content { + height: 30% !important; /* overriding "height: auto;" */ + } + .ui-dialog-buttonset { + width: 100%; + font-size: 4vh; + button { + width: 40%; + margin-left: 5% !important; + margin-right: 5% !important; + } + } + height: 32% !important; + } +} + +/* Logout menu's properties on desktop */ +.desktop-logout-dialog { + @media @desktop-portrait, @landscape { + .ui-dialog-content { + height: 36px !important; /* overriding "height: auto;" */ + } + } +} + +.no-close .ui-dialog-titlebar-close { + display: none; /* no close button */ +} + +.logout-dialog.ui-dialog { + .ui-widget-header { + color: extract(@white, 1); + font-weight: bold; + background: extract(@darkGrey, 3); + @media @desktop-portrait, @landscape { + font-size: 12px; + } + } + .ui-dialog-content { + font-weight: bold; + text-align: center; + @media @desktop-portrait, @landscape { + min-height: 0px !important; /* overriding "min-height: 47px;"*/ + } + } + @media @mobile-portrait-with-keyboard, @mobile-portrait { + width: 100% !important; + } +} + +.logout-dialog.ui-widget-content { + background: extract(@white, 3); + border: 5px solid extract(@darkGrey, 3); + @media @desktop-portrait, @landscape { + font-size: 11px; + } + @media @mobile-portrait-with-keyboard, @mobile-portrait { + font-size: 280%; + } +} + .ui-widget-overlay { z-index: 1031; } @@ -378,7 +446,7 @@ body { } } -.glyphicon, .ion-android-hand, .ion-easel { +.glyphicon, .ion-android-hand { @media @mobile-portrait-with-keyboard, @mobile-portrait { font-size: 35px; } @@ -395,7 +463,7 @@ body { .narrowedNavbarTitle { @media @desktop-portrait { - width: calc(~'100% - 410px') !important; + width: calc(~'100% - 358.4px') !important; } } @@ -466,6 +534,8 @@ body { } .btn { .linear-gradient(rgb(72,76,85), rgb(65,68,77)); + border-left: 1px solid extract(@darkGrey, 2); + border-right: 1px solid extract(@darkGrey, 4); &.navbarIconToggleActive { background: extract(@darkGrey, 3); border-bottom: 4px solid extract(@azure, 1); @@ -511,11 +581,12 @@ body { background: grey; &.webrtc-support-notification { - width: 500px !important; + width: 300px !important; height: 50px !important; } &.joined-audio-notification { - width: 500px !important; + width: 200px !important; + height: 32px !important; #browser-icon-container { display: none; } @@ -528,6 +599,7 @@ body { &.ui-widget-content p { color: black; font-size: 14px; + height: 35px; margin: 0; padding: 1px; } @@ -557,103 +629,3 @@ body { .invisible { display: none; } - -/* Custom alert box */ -/* Logout menu's properties on mobile devices in landscape orientation */ -.landscape-mobile-logout-dialog { - @media @landscape { - .ui-widget-header { - font-size: 100% !important; - } - width: 55% !important; /* overriding "width: auto;" */ - font-size: 3vh !important; /* overriding "font-size: 11px;" */ - .ui-dialog-content { - height: 30% !important; /* overriding "height: auto;" */ - } - .ui-dialog-buttonset { - width: 100%; - font-size: 4vh; - button { - width: 40%; - margin-left: 5% !important; - margin-right: 5% !important; - } - } - height: 32% !important; - } -} - -/* Logout menu's properties on desktop */ -.desktop-logout-dialog { - @media @desktop-portrait, @landscape { - .ui-dialog-content { - height: 36px !important; /* overriding "height: auto;" */ - } - } -} - -.no-close .ui-dialog-titlebar-close { - display: none; /* no close button */ -} - -.logout-dialog.ui-dialog { - .ui-widget-header { - color: extract(@white, 1); - font-weight: bold; - background: extract(@darkGrey, 3); - @media @desktop-portrait, @landscape { - font-size: 12px; - } - } - .ui-dialog-content { - font-weight: bold; - text-align: center; - @media @desktop-portrait, @landscape { - min-height: 0px !important; /* overriding "min-height: 47px;"*/ - } - } - @media @mobile-portrait-with-keyboard, @mobile-portrait { - width: 100% !important; - } -} - -.logout-dialog.ui-widget-content { - background: extract(@white, 3); - border: 5px solid extract(@darkGrey, 3); - @media @desktop-portrait, @landscape { - font-size: 11px; - } - @media @mobile-portrait-with-keyboard, @mobile-portrait { - font-size: 280%; - } -} - -.joinAudioDialog { - min-width: 500px; - width: 25% !important; -} -#joinAudioDialog { - background-color: rgb(239, 239, 239); - display: none; -} -.joinAudioDialogButton { - background: -webkit-linear-gradient(rgb(255,255,255), rgb(225,226,229)); /* For Safari 5.1 to 6.0 */ - background: -o-linear-gradient(rgb(255,255,255), rgb(225,226,229)); /* For Opera 11.1 to 12.0 */ - background: -moz-linear-gradient(rgb(255,255,255), rgb(225,226,229)); /* For Firefox 3.6 to 15 */ - background: linear-gradient(rgb(255,255,255), rgb(225,226,229)); /* Standard syntax (must be last) */ - border-radius: 5px; - color: rgb(94,95,99); - font-weight: bold; - padding: 10px; -} -.joinAudioDialogHr { - border: 1px solid darkgrey; - margin: 10px; - margin-bottom:5px; -} -.joinAudioDialogIcon { - font-size: 100px; -} -.ui-dialog-titlebar { - text-align: center; -} diff --git a/bigbluebutton-html5/app/client/stylesheets/users.less b/bigbluebutton-html5/app/client/stylesheets/users.less index 9095fb7eefad5532190063d31c0cf9459288b80d..874b7f5634efc20f4084ea1dd81c326453015c7f 100755 --- a/bigbluebutton-html5/app/client/stylesheets/users.less +++ b/bigbluebutton-html5/app/client/stylesheets/users.less @@ -2,9 +2,6 @@ #content { margin-top: 10px; - &:first-child { - margin-top: 0px; - } overflow: hidden; width: 100%; @media @mobile-portrait, @mobile-portrait-with-keyboard { diff --git a/bigbluebutton-html5/app/client/views/chat/chat_bar.coffee b/bigbluebutton-html5/app/client/views/chat/chat_bar.coffee index eb766d65107e99f6f8ced792eab62fafe0829d41..625df4acf83a76f704c2b010ec2751121ab185e7 100755 --- a/bigbluebutton-html5/app/client/views/chat/chat_bar.coffee +++ b/bigbluebutton-html5/app/client/views/chat/chat_bar.coffee @@ -65,24 +65,6 @@ Handlebars.registerHelper "grabChatTabs", -> setInSession 'chatTabs', initTabs getInSession('chatTabs')[0..3] -# true if the lock settings limit public chat and the current user is locked -Handlebars.registerHelper "publicChatDisabled", -> - userIsLocked = Meteor.Users.findOne({userId:getInSession 'userId'})?.user.locked - publicChatIsDisabled = Meteor.Meetings.findOne({})?.roomLockSettings.disablePubChat - presenter = Meteor.Users.findOne({userId:getInSession 'userId'})?.user.presenter - return userIsLocked and publicChatIsDisabled and !presenter - -# true if the lock settings limit private chat and the current user is locked -Handlebars.registerHelper "privateChatDisabled", -> - userIsLocked = Meteor.Users.findOne({userId:getInSession 'userId'})?.user.locked - privateChatIsDisabled = Meteor.Meetings.findOne({})?.roomLockSettings.disablePrivChat - presenter = Meteor.Users.findOne({userId:getInSession 'userId'})?.user.presenter - return userIsLocked and privateChatIsDisabled and !presenter - -# return whether the user's chat pane is open in Private chat (vs Public chat or Options) -Handlebars.registerHelper "inPrivateChat", -> - return !((getInSession 'inChatWith') in ['PUBLIC_CHAT', 'OPTIONS']) - @sendMessage = -> message = linkify $('#newMessageInput').val() # get the message from the input box unless (message?.length > 0 and (/\S/.test(message))) # check the message has content and it is not whitespace diff --git a/bigbluebutton-html5/app/client/views/chat/chat_bar.html b/bigbluebutton-html5/app/client/views/chat/chat_bar.html index 691352fb23c7dde7e299bd6841cc587fa57fb0aa..c99e4d04272a7bd0ff4227c5a59fa1245dba2bc5 100755 --- a/bigbluebutton-html5/app/client/views/chat/chat_bar.html +++ b/bigbluebutton-html5/app/client/views/chat/chat_bar.html @@ -1,90 +1,76 @@ <template name="chatbar"> - <div id="{{id}}" {{visibility name}} class="component"> - <h3 class="title gradientBar"> - <span class="glyphicon glyphicon-comment heading"></span> - {{> extraConversations}} - </h3> - {{>tabButtons}} <!-- Display public/options tabs, and private chat tabs --> - {{#if getInSession "display_chatPane"}} - <div id="chatbody"> - <ul class="chat" {{messageFontSize}}> - {{#each getCombinedMessagesForChat}} - {{#if message}} - <li>{{> message}}</li> + <div id="{{id}}" {{visibility name}} class="component"> + <h3 class="title gradientBar"> + <span class="glyphicon glyphicon-comment"></span> + {{title}} + {{> extraConversations}} + </h3> + {{>tabButtons}} <!-- Display public/options tabs, and private chat tabs --> + {{#if getInSession "display_chatPane"}} + <div id="chatbody"> + <ul class="chat" {{messageFontSize}}> + {{#each getCombinedMessagesForChat}} + {{#if message}} + <li>{{> message}}</li> + {{/if}} + {{/each}} + {{#unless userExists}}<li>The user has left</li>{{/unless}} + </ul> + </div> + {{#if userExists}} + <div class="panel-footer">{{> chatInput}}</div> {{/if}} - {{/each}} - {{#unless userExists}}<li>The user has left</li>{{/unless}} - </ul> - </div> - {{#if userExists}} - <div class="panel-footer">{{> chatInput}}</div> - {{/if}} - {{else}} - {{> optionsBar}} - {{/if}} - </div> + {{else}} + {{> optionsBar}} + {{/if}} + </div> </template> <template name="chatInput"> - <div id="chatInput" class="chat-input-wrapper"> - {{#if inPrivateChat}} - {{#if privateChatDisabled}} - <textarea id="newMessageInput" class="disabledChat" placeholder="Private chat is temporarily locked (disabled)" rel="tooltip" data-placement="top" title="Private chat is temporarily locked" disabled></textarea> - {{else}} - <textarea id="newMessageInput" placeholder="Write a message..." rel="tooltip" data-placement="top" title="Write a new message"></textarea> - <button type="submit" id="sendMessageButton" class="btn" rel="tooltip" data-placement="top"> - Send - </button> - {{/if}} - {{else}} - {{#if publicChatDisabled}} - <textarea id="newMessageInput" class="disabledChat" placeholder="Public chat is temporarily locked (disabled)" rel="tooltip" data-placement="top" title="Public chat is temporarily locked" disabled></textarea> - {{else}} - <textarea id="newMessageInput" placeholder="Write a message..." rel="tooltip" data-placement="top" title="Write a new message"></textarea> - <button type="submit" id="sendMessageButton" class="btn" rel="tooltip" data-placement="top"> - Send - </button> - {{/if}} - {{/if}} - </div> + <div id="chatInput" class="chat-input-wrapper"> + <textarea id="newMessageInput" placeholder="Write a message..." rel="tooltip" data-placement="top" title="Write a new message"></textarea> + <button type="submit" id="sendMessageButton" class="btn" rel="tooltip" data-placement="top" title="Click to send your message"> + Send + </button> + </div> </template> <template name="chatOptions"> - <p>Chat Options:</p> - {{> optionsFontSize}} + <p>Chat Options:</p> + {{> optionsFontSize}} </template> <template name="extraConversations"> - {{#if tooManyConversations}} - <div id="MoreChatsDrop" class="btn-group"> - <button type="button" id="MoreChatsbutton" class="btn btn-default dropdown-toggle" data-toggle="dropdown">More Chats<span class="caret"></span></button> - <ul class="dropdown-menu extraConversationScrollableMenu" role="menu"> - {{#each getExtraConversations}} - <li class="extraConversation" id="{{safeName name}}"><a href="#">{{safeName name}}</a></li> - {{/each}} - </ul> - </div> - {{/if}} + {{#if tooManyConversations}} + <div id="MoreChatsDrop" class="btn-group"> + <button type="button" id="MoreChatsbutton" class="btn btn-default dropdown-toggle" data-toggle="dropdown">More Chats<span class="caret"></span></button> + <ul class="dropdown-menu extraConversationScrollableMenu" role="menu"> + {{#each getExtraConversations}} + <li class="extraConversation" id="{{safeName name}}"><a href="#">{{safeName name}}</a></li> + {{/each}} + </ul> + </div> + {{/if}} </template> <!-- Displays and styles an individual message in the chat --> <template name="message"> - <span style="float:left;"> - {{#if message.from_username}} - <span class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{message.from_username}}"> - {{message.from_username}} - </span> - {{/if}} - </span> - <span style="float:right;"> - {{#if message.from_time}} - <span {{messageFontSize}}>{{toClockTime message.from_time}}</span> - <span {{messageFontSize}} class="glyphicon glyphicon-time"></span> - {{/if}} - </span> - <br/> - <div style="color:{{colourToHex message.from_color}}">{{{sanitizeAndFormat message.message}}}</div> - {{autoscroll}} + <span style="float:left;"> + {{#if message.from_username}} + <span class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{message.from_username}}"> + {{message.from_username}} + </span> + {{/if}} + </span> + <span style="float:right;"> + {{#if message.from_time}} + <span {{messageFontSize}}>{{toClockTime message.from_time}}</span> + <span {{messageFontSize}} class="glyphicon glyphicon-time"></span> + {{/if}} + </span> + <br/> + <div style="color:{{colourToHex message.from_color}}">{{{sanitizeAndFormat message.message}}}</div> + {{autoscroll}} </template> <!-- Displays the list of options available --> @@ -110,16 +96,12 @@ </template> <template name="optionsFontSize"> - <span class="chatOptionsText" >Chat Message Font Size: </span><br/> - <table id="fontSizeTable"> - <tr> - <td id="displayButtons"> - <button id="decreaseFontSize" class="glyphicon glyphicon-minus"></button> - <button id="increaseFontSize" class="glyphicon glyphicon-plus"></button> - </td > - <td id="displayLabel"><label class="fontSizeLabel" {{messageFontSize}} >Size({{getInSession "messageFontSize"}})</label></td> - </tr> - </table> + <div id="fontSizeControl"> + <span class="chatOptionsText" >Chat Message Font Size: </span><br> + <button id="decreaseFontSize" class="glyphicon glyphicon-minus" ></button> + <label class="fontSizeLabel" {{messageFontSize}} >Size({{getInSession "messageFontSize"}})</label> + <button id="increaseFontSize" class="glyphicon glyphicon-plus"></button> + </div> </template> <!-- Display buttons on the chat tab, public, options, and all the private chat tabs --> diff --git a/bigbluebutton-html5/app/client/views/users/user_item.coffee b/bigbluebutton-html5/app/client/views/users/user_item.coffee index d9e7779ea6e0e89b205b040416049134a5db753c..24f33884fef64af9754012f0a27b5f2e730931c6 100755 --- a/bigbluebutton-html5/app/client/views/users/user_item.coffee +++ b/bigbluebutton-html5/app/client/views/users/user_item.coffee @@ -9,16 +9,3 @@ Template.displayUserIcons.events # the userId of the person who is lowering the hand console.log "lower hand- client click handler" Meteor.call('userLowerHand', getInSession("meetingId"), @userId, getInSession("userId"), getInSession("authToken")) - -Template.displayUserIcons.helpers - userLockedIconApplicable: (userId) -> - # the lock settings affect the user (and requiire a lock icon) if - # the user is set to be locked and there is a relevant lock in place - locked = BBB.getUser(userId)?.user.locked - settings = Meteor.Meetings.findOne()?.roomLockSettings - lockInAction = settings.disablePrivChat or - settings.disableCam or - settings.disableMic or - settings.lockedLayout or - settings.disablePubChat - return locked and lockInAction diff --git a/bigbluebutton-html5/app/client/views/users/user_item.html b/bigbluebutton-html5/app/client/views/users/user_item.html index 18df7410fd029019250401ac9733c4cd225d1e63..14efb5fcd916e85ad2967a79c3ee10284caef325 100755 --- a/bigbluebutton-html5/app/client/views/users/user_item.html +++ b/bigbluebutton-html5/app/client/views/users/user_item.html @@ -1,39 +1,36 @@ + + <template name="displayUserIcons"> {{#if isUserSharingVideo userId}} <span class="userListSettingIcon glyphicon glyphicon-facetime-video" rel="tooltip" data-placement="bottom" title="{{user.name}} is sharing their webcam"></span> {{/if}} - {{#if isUserInAudio userId}} - <!-- if the user is listen only, only display the one icon --> - {{#if isUserListenOnlyAudio userId}} - <span class="userListSettingIcon glyphicon glyphicon-headphones" title="Listening only"></span> - {{else}} - {{#if isCurrentUser userId}} - {{#if isUserMuted userId}} - <span class="muteIcon userListSettingIcon glyphicon glyphicon-volume-off" rel="tooltip" data-placement="bottom" title="Unmute yourself"></span> + {{#if isUserSharingAudio userId}} + {{#if isCurrentUser userId}} + {{#if isUserMuted userId}} + <span class="muteIcon userListSettingIcon glyphicon glyphicon-volume-off" rel="tooltip" data-placement="bottom" title="Unmute yourself"></span> + {{else}} + {{#if isCurrentUserTalking}} + <span class="muteIcon userListSettingIcon glyphicon glyphicon-volume-up" rel="tooltip" data-placement="bottom" title="is talking"></span> {{else}} - {{#if isCurrentUserTalking}} - <span class="muteIcon userListSettingIcon glyphicon glyphicon-volume-up" rel="tooltip" data-placement="bottom" title="is talking"></span> - {{else}} - <span class="muteIcon userListSettingIcon glyphicon glyphicon-volume-down" rel="tooltip" data-placement="bottom" title="is not talking"></span> - {{/if}} + <span class="muteIcon userListSettingIcon glyphicon glyphicon-volume-down" rel="tooltip" data-placement="bottom" title="is not talking"></span> {{/if}} + {{/if}} + {{else}} + {{#if isUserMuted userId}} + <span class="userListSettingIcon glyphicon glyphicon-volume-off" rel="tooltip" data-placement="bottom" title="{{user.name}} is muted"></span> {{else}} - {{#if isUserMuted userId}} - <span class="userListSettingIcon glyphicon glyphicon-volume-off" rel="tooltip" data-placement="bottom" title="{{user.name}} is muted"></span> + {{#if isUserTalking userId}} + <span class="userListSettingIcon glyphicon glyphicon-volume-up" rel="tooltip" data-placement="bottom" title="{{user.name}} is talking"></span> {{else}} - {{#if isUserTalking userId}} - <span class="userListSettingIcon glyphicon glyphicon-volume-up" rel="tooltip" data-placement="bottom" title="{{user.name}} is talking"></span> - {{else}} - <span class="userListSettingIcon glyphicon glyphicon-volume-down" rel="tooltip" data-placement="bottom" title="{{user.name}} is not talking"></span> - {{/if}} + <span class="userListSettingIcon glyphicon glyphicon-volume-down" rel="tooltip" data-placement="bottom" title="{{user.name}} is not talking"></span> {{/if}} {{/if}} {{/if}} {{/if}} - {{#if userLockedIconApplicable userId}} - <span class="userListSettingIcon glyphicon ion-locked" rel="tooltip" data-placement="bottom" title="The viewer is locked"></span> + {{#if isUserListenOnly userId}} + <span class="userListSettingIcon glyphicon glyphicon-headphones" title="Listening only"></span> {{/if}} {{#if user.presenter}} diff --git a/bigbluebutton-html5/app/client/views/users/user_list.coffee b/bigbluebutton-html5/app/client/views/users/user_list.coffee index c6484a6b63abe76010164e3a43bcaa86b6111c1a..96d0fbcb2c217db88bb21c2d005db624d661d49a 100755 --- a/bigbluebutton-html5/app/client/views/users/user_list.coffee +++ b/bigbluebutton-html5/app/client/views/users/user_list.coffee @@ -1,6 +1,3 @@ Template.usersList.helpers - getInfoNumberOfUsers: -> - numberUsers = Meteor.Users.find().count() - if numberUsers > 8 - return "Users: #{numberUsers}" - # do not display the label if there are just a few users + getMeetingSize: -> # Retreieve the number of users in the chat, or "error" string + return Meteor.Users.find().count() diff --git a/bigbluebutton-html5/app/client/views/users/users_list.html b/bigbluebutton-html5/app/client/views/users/users_list.html index b245379dcb5835005c52e4d7a99f976ed5108fc1..4c9ec13d958026fe1269a87fe9da648bb1acca1d 100755 --- a/bigbluebutton-html5/app/client/views/users/users_list.html +++ b/bigbluebutton-html5/app/client/views/users/users_list.html @@ -1,6 +1,6 @@ <template name="usersList"> <div id="{{id}}" {{visibility name}} class="component"> - <h3 class="title gradientBar"><span class="glyphicon glyphicon-user heading"></span> {{getInfoNumberOfUsers}} </h3> + <h3 class="title gradientBar"><span class="glyphicon glyphicon-user"></span> Participants: {{getMeetingSize}} User(s)</h3> <div id="user-contents"> <div class="userlist ScrollableWindowY"> diff --git a/bigbluebutton-html5/app/client/views/whiteboard/whiteboard.html b/bigbluebutton-html5/app/client/views/whiteboard/whiteboard.html index 7288c5f7214b444c4575ba912d11bb240edb57dd..56f6213dd77cfd5cb6c219dfa5d8769c5e98d94e 100755 --- a/bigbluebutton-html5/app/client/views/whiteboard/whiteboard.html +++ b/bigbluebutton-html5/app/client/views/whiteboard/whiteboard.html @@ -4,7 +4,7 @@ {{#if isMobileChromeOrFirefox}} {{> makeButton btn_class="fullscreenWhiteboardButton" i_class="glyphicon glyphicon-fullscreen"}} {{/if}} - <span class="ion-easel heading"></span> + <span class="glyphicon glyphicon-pencil"></span> {{title}} </h3> {{#each getCurrentSlide}} diff --git a/bigbluebutton-html5/app/config.coffee b/bigbluebutton-html5/app/config.coffee index cc606108e86c763e2354e955ca2bafe4a27bf3c3..84bfbf10281d87f57a2ab2859157e36cc8346293 100755 --- a/bigbluebutton-html5/app/config.coffee +++ b/bigbluebutton-html5/app/config.coffee @@ -14,8 +14,6 @@ config.defaultWelcomeMessageFooter = "This server is running a build of <a href= config.maxUsernameLength = 30 config.maxChatLength = 140 -config.lockOnJoin = true - ## Application configurations config.app = {} @@ -23,11 +21,6 @@ config.app = {} config.app.mobileFont = 24 config.app.desktopFont = 12 -# Will offer the user to join the audio when entering the meeting -config.app.autoJoinAudio = false -# The amount of time the client will wait before making another call to successfully hangup the WebRTC conference call -config.app.WebRTCHangupRetryInterval = 2000 - # Configs for redis config.redis = {} config.redis.host = "127.0.0.1" diff --git a/bigbluebutton-html5/app/lib/router.coffee b/bigbluebutton-html5/app/lib/router.coffee old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.ttf b/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.ttf old mode 100755 new mode 100644 index c4e4632486d863337c1c73478ddb3c20726c55a0..f8fcf261a6b2501cb5ede90e5f3d371452c64156 Binary files a/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.ttf and b/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.ttf differ diff --git a/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.woff b/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.woff old mode 100755 new mode 100644 index 5f3a14e0a5ca6d20cc4fac708979e807b0d51bc3..9f808a3cdddbe92050fc02ac0e9251c17d40687d Binary files a/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.woff and b/bigbluebutton-html5/app/public/packages/ionicons/fonts/ionicons.woff differ diff --git a/bigbluebutton-html5/app/server/collection_methods/meetings.coffee b/bigbluebutton-html5/app/server/collection_methods/meetings.coffee index 00d2acc142efe5e23352737a0ff19d82871e0d02..348d08bd74bd4ae5076e04b3ee024737b3c97a8f 100755 --- a/bigbluebutton-html5/app/server/collection_methods/meetings.coffee +++ b/bigbluebutton-html5/app/server/collection_methods/meetings.coffee @@ -2,27 +2,17 @@ # Private methods on server # -------------------------------------------------------------------------------------------- @addMeetingToCollection = (meetingId, name, intendedForRecording, voiceConf, duration) -> - #check if the meeting is already in the collection - unless Meteor.Meetings.findOne({meetingId: meetingId})? - entry = - meetingId: meetingId - meetingName: name - intendedForRecording: intendedForRecording - currentlyBeingRecorded: false # defaut value - voiceConf: voiceConf - duration: duration - roomLockSettings: - # by default the lock settings will be disabled on meeting create - disablePrivChat: false - disableCam: false - disableMic: false - lockOnJoin: Meteor.config.lockOnJoin - lockedLayout: false - disablePubChat: false - - id = Meteor.Meetings.insert(entry) - Meteor.log.info "added meeting _id=[#{id}]:meetingId=[#{meetingId}]:name=[#{name}]:duration=[#{duration}]:voiceConf=[#{voiceConf}] - roomLockSettings:[#{JSON.stringify entry.roomLockSettings}]." + #check if the meeting is already in the collection + unless Meteor.Meetings.findOne({meetingId: meetingId})? + currentlyBeingRecorded = false # defaut value + id = Meteor.Meetings.insert( + meetingId: meetingId, + meetingName: name, + intendedForRecording: intendedForRecording, + currentlyBeingRecorded: currentlyBeingRecorded, + voiceConf: voiceConf, + duration: duration) + Meteor.log.info "added meeting _id=[#{id}]:meetingId=[#{meetingId}]:name=[#{name}]:duration=[#{duration}]:voiceConf=[#{voiceConf}]." @clearMeetingsCollection = (meetingId) -> @@ -32,7 +22,6 @@ Meteor.Meetings.remove({}, Meteor.log.info "cleared Meetings Collection (all meetings)!") -#clean up upon a meeting's end @removeMeetingFromCollection = (meetingId) -> if Meteor.Meetings.findOne({meetingId: meetingId})? Meteor.log.info "end of meeting #{meetingId}. Clear the meeting data from all collections" diff --git a/bigbluebutton-html5/app/server/collection_methods/users.coffee b/bigbluebutton-html5/app/server/collection_methods/users.coffee index 8b431dddb34c926139e7465b4d3eda1426519999..3b3980a09d240a4a53101090b7bbd37df30970a5 100755 --- a/bigbluebutton-html5/app/server/collection_methods/users.coffee +++ b/bigbluebutton-html5/app/server/collection_methods/users.coffee @@ -6,49 +6,6 @@ # immediately, since they do not require permission for things such as muting themsevles. # -------------------------------------------------------------------------------------------- Meteor.methods - # meetingId: the meetingId of the meeting the user is in - # toSetUserId: the userId of the user joining - # requesterUserId: the userId of the requester - # requesterToken: the authToken of the requester - listenOnlyRequestToggle: (meetingId, userId, authToken, isJoining) -> - voiceConf = Meteor.Meetings.findOne({meetingId:meetingId})?.voiceConf - username = Meteor.Users.findOne({meetingId:meetingId, userId:userId})?.user.name - if isJoining - if isAllowedTo('joinListenOnly', meetingId, userId, authToken) - message = - payload: - userid: userId - meeting_id: meetingId - voice_conf: voiceConf - name: username - header: - timestamp: new Date().getTime() - name: "user_connected_to_global_audio" - version: "0.0.1" - - Meteor.log.info "publishing a user listenOnly toggleRequest #{isJoining} request for #{userId}" - - publish Meteor.config.redis.channels.toBBBApps.meeting, message - - else - if isAllowedTo('leaveListenOnly', meetingId, userId, authToken) - message = - payload: - userid: userId - meeting_id: meetingId - voice_conf: voiceConf - name: username - header: - timestamp: new Date().getTime() - name: "user_disconnected_from_global_audio" - version: "0.0.1" - - Meteor.log.info "publishing a user listenOnly toggleRequest #{isJoining} request for #{userId}" - - publish Meteor.config.redis.channels.toBBBApps.meeting, message - - return - # meetingId: the meetingId of the meeting the user[s] is in # toMuteUserId: the userId of the user to be [un]muted # requesterUserId: the userId of the requester @@ -160,6 +117,7 @@ Meteor.methods Meteor.log.info "marking user [#{userId}] as offline in meeting[#{meetingId}]" Meteor.Users.update({'meetingId': meetingId, 'userId': userId}, {$set:{'user.connection_status':'offline'}}) + # Corresponds to a valid action on the HTML clientside # After authorization, publish a user_leaving_request in redis # params: meetingid, userid as defined in BBB-App @@ -192,8 +150,8 @@ Meteor.methods Meteor.Users.update({meetingId: meetingId ,userId: voiceUserObject.web_userid}, {$set: {'user.voiceUser.locked':voiceUserObject.locked}}) # locked if voiceUserObject.muted? Meteor.Users.update({meetingId: meetingId ,userId: voiceUserObject.web_userid}, {$set: {'user.voiceUser.muted':voiceUserObject.muted}}) # muted - if voiceUserObject.listen_only? - Meteor.Users.update({meetingId: meetingId ,userId: voiceUserObject.web_userid}, {$set: {'user.listenOnly':voiceUserObject.listen_only}}) # listenOnly + if voiceUserObject.listenOnly? + Meteor.Users.update({meetingId: meetingId ,userId: voiceUserObject.web_userid}, {$set: {'user.listenOnly':voiceUserObject.listenOnly}}) # muted else Meteor.log.error "ERROR! did not find such voiceUser!" @@ -204,7 +162,7 @@ Meteor.methods # the collection already contains an entry for this user because # we added a dummy user on register_user_message (to save authToken) if u? - Meteor.log.info "UPDATING USER #{user.userid}, authToken=#{u.authToken}, locked=#{user.locked}" + Meteor.log.info "UPDATING USER #{user.userid}, authToken=#{u.authToken}" Meteor.Users.update({userId:user.userid, meetingId: meetingId}, {$set:{ user: userid: user.userid @@ -308,36 +266,6 @@ Meteor.methods Meteor.log.info "added user dummy html5 user with: userid=[#{userId}], id=[#{id}] Users.size is now #{Meteor.Users.find({meetingId: meetingId}).count()}" - -# when new lock settings including disableMic are set, -# all viewers that are in the audio bridge with a mic should be muted and locked -@handleLockingMic = (meetingId, newSettings) -> - # send mute requests for the viewer users joined with mic - for u in Meteor.Users.find({ - meetingId:meetingId - 'user.role':'VIEWER' - 'user.listenOnly':false - 'user.locked':true - 'user.voiceUser.joined':true - 'user.voiceUser.muted':false})?.fetch() - Meteor.log.error u.user.name # - Meteor.call('muteUser', meetingId, u.userId, u.userId, u.authToken, true) #true for muted - -# change the locked status of a user (lock settings) -@setUserLockedStatus = (meetingId, userId, isLocked) -> - if Meteor.Users.findOne({userId:userId, meetingId: meetingId})? - Meteor.Users.update({userId:userId, meetingId: meetingId}, {$set:{'user.locked': isLocked}}) - - # if the user is sharing audio, he should be muted upon locking involving disableMic - u = Meteor.Users.findOne({meetingId:meetingId, userId:userId}) - if u.user.role is 'VIEWER' and !u.user.listenOnly and u.user.voiceUser.joined and !u.user.voiceUser.muted and isLocked - Meteor.call('muteUser', meetingId, u.userId, u.userId, u.authToken, true) #true for muted - - Meteor.log.info "setting user locked status for userid:[#{userId}] from [#{meetingId}] locked=#{isLocked}" - else - Meteor.log.error "(unsuccessful-no such user) setting user locked status for userid:[#{userId}] from [#{meetingId}] locked=#{isLocked}" - - # called on server start and on meeting end @clearUsersCollection = (meetingId) -> if meetingId? diff --git a/bigbluebutton-html5/app/server/redispubsub.coffee b/bigbluebutton-html5/app/server/redispubsub.coffee index 3d81454bb15b1a3e5eaf9334d003c4cf7b3a3cf4..21ae697e0005a82df71d7424b0d47743d032d586 100755 --- a/bigbluebutton-html5/app/server/redispubsub.coffee +++ b/bigbluebutton-html5/app/server/redispubsub.coffee @@ -1,4 +1,8 @@ Meteor.methods + # + # I dont know if this is okay to be server side. We need to call it from the router, but I don't know if any harm can be caused + # by the client calling this + # # Construct and send a message to bbb-web to validate the user validateAuthToken: (meetingId, userId, authToken) -> @@ -56,45 +60,29 @@ class Meteor.RedisPubSub correlationId = message.payload?.reply_to or message.header?.reply_to meetingId = message.payload?.meeting_id - # just because it's common. we handle it anyway - notLoggedEventTypes = [ + ignoredEventTypes = [ "keep_alive_reply" "page_resized_message" "presentation_page_resized_message" - "presentation_cursor_updated_message" - "get_presentation_info_reply" - # "get_users_reply" - "get_chat_history_reply" - "get_all_meetings_reply" - "presentation_shared_message" - "presentation_conversion_done_message" - "presentation_conversion_progress_message" - "presentation_page_generated_message" - "presentation_page_changed_message" + "presentation_cursor_updated_message" # just because it's common. we handle it anyway ] if message?.header? and message?.payload? - unless message.header.name in notLoggedEventTypes + unless message.header.name in ignoredEventTypes Meteor.log.info "eventType= #{message.header.name} ", message: jsonMsg # handle voice events if message.header.name in ['user_left_voice_message', 'user_joined_voice_message', 'user_voice_talking_message', 'user_voice_muted_message'] - if message.payload.user? - updateVoiceUser meetingId, - 'web_userid': message.payload.user.voiceUser.web_userid - 'listen_only': message.payload.listen_only - 'talking': message.payload.user.voiceUser.talking - 'joined': message.payload.user.voiceUser.joined - 'locked': message.payload.user.voiceUser.locked - 'muted': message.payload.user.voiceUser.muted + voiceUser = message.payload.user?.voiceUser + updateVoiceUser meetingId, voiceUser return # listen only if message.header.name is 'user_listening_only' - updateVoiceUser meetingId, {'web_userid': message.payload.userid, 'listen_only': message.payload.listen_only} + updateVoiceUser meetingId, {'web_userid': message.payload.userid, 'listenOnly': message.payload.listen_only} # most likely we don't need to ensure that the user's voiceUser's {talking, joined, muted, locked} are false by default #TODO? - return + return if message.header.name is "get_all_meetings_reply" Meteor.log.info "Let's store some data for the running meetings so that when an HTML5 client joins everything is ready!" @@ -287,37 +275,6 @@ class Meteor.RedisPubSub Meteor.Meetings.update({meetingId: meetingId, intendedForRecording: intendedForRecording}, {$set: {currentlyBeingRecorded: currentlyBeingRecorded}}) return - # -------------------------------------------------- - # lock settings ------------------------------------ - if message.header.name is "eject_voice_user_message" - console.log "\n111111111" - return - - if message.header.name is "new_permission_settings" - oldSettings = Meteor.Meetings.findOne({meetingId:meetingId})?.roomLockSettings - newSettings = message.payload - - # if the disableMic setting was turned on - if !oldSettings?.disableMic and newSettings.disableMic - handleLockingMic(meetingId, newSettings) - - # substitute with the new lock settings - Meteor.Meetings.update({meetingId: meetingId}, {$set: { - 'roomLockSettings.disablePrivChat': message.payload.disablePrivChat - 'roomLockSettings.disableCam': message.payload.disableCam - 'roomLockSettings.disableMic': message.payload.disableMic - 'roomLockSettings.lockOnJoin': message.payload.lockOnJoin - 'roomLockSettings.lockedLayout': message.payload.lockedLayout - 'roomLockSettings.disablePubChat': message.payload.disablePubChat - }}) - return - - if message.header.name is "user_locked_message" or message.header.name is "user_unlocked_message" - userId = message.payload.userid - isLocked = message.payload.locked - setUserLockedStatus(meetingId, userId, isLocked) - return - if message.header.name in ["meeting_ended_message", "meeting_destroyed_event", "end_and_kick_all_message", "disconnect_all_users_message"] if Meteor.Meetings.findOne({meetingId: meetingId})? @@ -335,9 +292,9 @@ class Meteor.RedisPubSub # message should be an object @publish = (channel, message) -> - # Meteor.log.info "Publishing", - # channel: channel - # message: message + Meteor.log.info "Publishing", + channel: channel + message: message if Meteor.redisPubSub? Meteor.redisPubSub.pubClient.publish channel, JSON.stringify(message), (err, res) -> diff --git a/bigbluebutton-html5/app/server/user_permissions.coffee b/bigbluebutton-html5/app/server/user_permissions.coffee old mode 100755 new mode 100644 index ce3962ad81fc7bff5e7f9453a1beab3b86b02d0d..43a422767c1a49fd4c4121aa1225fed71e379575 --- a/bigbluebutton-html5/app/server/user_permissions.coffee +++ b/bigbluebutton-html5/app/server/user_permissions.coffee @@ -1,16 +1,8 @@ presenter = null -# holds the values for whether the moderator user is allowed to perform an action (true) -# or false if not allowed. Some actions have dynamic values depending on the current lock settings +#for the time being moderators have the same permissions that viewers do moderator = - # audio listen only - joinListenOnly: true - leaveListenOnly: true - - # join audio with mic cannot be controlled on the server side as it is - # a client side only functionality - # raising/lowering hand raiseOwnHand : true lowerOwnHand : true @@ -26,29 +18,18 @@ moderator = subscribeChat: true #chat - chatPublic: true - chatPrivate: true - - -# holds the values for whether the viewer user is allowed to perform an action (true) -# or false if not allowed. Some actions have dynamic values depending on the current lock settings -viewer = (meetingId, userId) -> + chatPublic: true #should make this dynamically modifiable later on + chatPrivate: true #should make this dynamically modifiable later on - # listen only - joinListenOnly: true - leaveListenOnly: true - - # join audio with mic cannot be controlled on the server side as it is - # a client side only functionality +viewer = # raising/lowering hand raiseOwnHand : true lowerOwnHand : true # muting muteSelf : true - unmuteSelf : !(Meteor.Meetings.findOne({meetingId:meetingId})?.roomLockSettings.disableMic) or - !(Meteor.Users.findOne({meetingId:meetingId, userId:userId})?.user.locked) + unmuteSelf : true logoutSelf : true @@ -57,18 +38,11 @@ viewer = (meetingId, userId) -> subscribeChat: true #chat - chatPublic: !(Meteor.Meetings.findOne({meetingId:meetingId})?.roomLockSettings.disablePubChat) or - !(Meteor.Users.findOne({meetingId:meetingId, userId:userId})?.user.locked) or - Meteor.Users.findOne({meetingId:meetingId, userId:userId})?.user.presenter - chatPrivate: !(Meteor.Meetings.findOne({meetingId:meetingId})?.roomLockSettings.disablePrivChat) or - !(Meteor.Users.findOne({meetingId:meetingId, userId:userId})?.user.locked) or - Meteor.Users.findOne({meetingId:meetingId, userId:userId})?.user.presenter - - + chatPublic: true #should make this dynamically modifiable later on + chatPrivate: true #should make this dynamically modifiable later on -# carries out the decision making for actions affecting users. For the list of -# actions and the default value - see 'viewer' and 'moderator' in the beginning of the file @isAllowedTo = (action, meetingId, userId, authToken) -> + # Disclaimer:the current version of the HTML5 client represents only VIEWER users validated = Meteor.Users.findOne({meetingId:meetingId, userId: userId})?.validated Meteor.log.info "in isAllowedTo: action-#{action}, userId=#{userId}, authToken=#{authToken} validated:#{validated}" @@ -78,7 +52,7 @@ viewer = (meetingId, userId) -> if user? and authToken is user.authToken # check if the user is who he claims to be if user.validated and user.clientType is "HTML5" if user.user?.role is 'VIEWER' or user.user?.role is 'MODERATOR' or user.user?.role is undefined - return viewer(meetingId, userId)[action] or false + return viewer[action] or false else Meteor.log.warn "UNSUCCESSFULL ATTEMPT FROM userid=#{userId} to perform:#{action}" return false diff --git a/bigbluebutton-web/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy b/bigbluebutton-web/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy index 791e6f6c79d9be3771236d9a72bc78fc45230921..021c97785e77f5fd14aaf929c59e07521c0f06b6 100644 --- a/bigbluebutton-web/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy +++ b/bigbluebutton-web/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy @@ -1,183 +1,183 @@ -/** -* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -* -* Copyright (c) 2014 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.web.services - -import java.util.concurrent.*; -import java.lang.InterruptedException -import org.bigbluebutton.presentation.DocumentConversionService -import org.bigbluebutton.presentation.UploadedPresentation - -class PresentationService { - - static transactional = false - DocumentConversionService documentConversionService - def presentationDir - def testConferenceMock - def testRoomMock - def testPresentationName - def testUploadedPresentation - def defaultUploadedPresentation - def presentationBaseUrl - - def deletePresentation = {conf, room, filename -> - def directory = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename) - deleteDirectory(directory) - } - - def deleteDirectory = {directory -> - log.debug "delete = ${directory}" - /** - * Go through each directory and check if it's not empty. - * We need to delete files inside a directory before a - * directory can be deleted. - **/ - File[] files = directory.listFiles(); - for (int i = 0; i < files.length; i++) { - if (files[i].isDirectory()) { - deleteDirectory(files[i]) - } else { - files[i].delete() - } - } - // Now that the directory is empty. Delete it. - directory.delete() - } - - def listPresentations = {conf, room -> - def presentationsList = [] - def directory = roomDirectory(conf, room) - log.debug "directory ${directory.absolutePath}" - if( directory.exists() ){ - directory.eachFile(){ file-> - System.out.println(file.name) - if( file.isDirectory() ) - presentationsList.add( file.name ) - } - } - return presentationsList - } - - def getPresentationDir = { - return presentationDir - } - - def processUploadedPresentation = {uploadedPres -> - // Run conversion on another thread. - Timer t = new Timer(uploadedPres.getName(), false) - - t.runAfter(1000) { - try { - documentConversionService.processDocument(uploadedPres) - } finally { - t.cancel() - } - } - } - - def showSlide(String conf, String room, String presentationName, String id) { - new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "slide-${id}.swf") - } - - def showPngImage(String conf, String room, String presentationName, String id) { - new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "pngs" + File.separatorChar + "slide${id}.png") - } - - def showPresentation = {conf, room, filename -> - new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename + File.separatorChar + "slides.swf") - } - - def showThumbnail = {conf, room, presentationName, thumb -> - println "Show thumbnails request for $presentationName $thumb" - def thumbFile = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + - "thumbnails" + File.separatorChar + "thumb-${thumb}.png" - log.debug "showing $thumbFile" - - new File(thumbFile) - } - - def showTextfile = {conf, room, presentationName, textfile -> - println "Show textfiles request for $presentationName $textfile" - def txt = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + - "textfiles" + File.separatorChar + "slide-${textfile}.txt" - log.debug "showing $txt" - - new File(txt) - } - - def numberOfThumbnails = {conf, room, name -> - def thumbDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "thumbnails") - thumbDir.listFiles().length - } - - def numberOfPngs = {conf, room, name -> - def PngsDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "pngs") - PngsDir.listFiles().length - } - - def numberOfTextfiles = {conf, room, name -> - log.debug roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles" - def textfilesDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles") - textfilesDir.listFiles().length - } - - def roomDirectory = {conf, room -> - return new File(presentationDir + File.separatorChar + conf + File.separatorChar + room) - } - - def testConversionProcess() { - File presDir = new File(roomDirectory(testConferenceMock, testRoomMock).absolutePath + File.separatorChar + testPresentationName) - - if (presDir.exists()) { - File pres = new File(presDir.getAbsolutePath() + File.separatorChar + testUploadedPresentation) - if (pres.exists()) { - UploadedPresentation uploadedPres = new UploadedPresentation(testConferenceMock, testRoomMock, testPresentationName); - uploadedPres.setUploadedFile(pres); - // Run conversion on another thread. - new Timer().runAfter(1000) - { - documentConversionService.processDocument(uploadedPres) - } - } else { - log.error "${pres.absolutePath} does NOT exist" - } - } else { - log.error "${presDir.absolutePath} does NOT exist." - } - - } - - def getFile = {conf, room, presentationName -> - println "download request for $presentationName" - def fileDirectory = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + -"download") - //list the files of the download directory ; it must have only 1 file to download - def list = fileDirectory.listFiles() - //new File(pdfFile) - list[0] - } -} - -/*** Helper classes **/ -import java.io.FilenameFilter; -import java.io.File; -class PngFilter implements FilenameFilter { - public boolean accept(File dir, String name) { - return (name.endsWith(".png")); - } -} +/** +* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ +* +* Copyright (c) 2014 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.web.services + +import java.util.concurrent.*; +import java.lang.InterruptedException +import org.bigbluebutton.presentation.DocumentConversionService +import org.bigbluebutton.presentation.UploadedPresentation + +class PresentationService { + + static transactional = false + DocumentConversionService documentConversionService + def presentationDir + def testConferenceMock + def testRoomMock + def testPresentationName + def testUploadedPresentation + def defaultUploadedPresentation + def presentationBaseUrl + + def deletePresentation = {conf, room, filename -> + def directory = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename) + deleteDirectory(directory) + } + + def deleteDirectory = {directory -> + log.debug "delete = ${directory}" + /** + * Go through each directory and check if it's not empty. + * We need to delete files inside a directory before a + * directory can be deleted. + **/ + File[] files = directory.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + deleteDirectory(files[i]) + } else { + files[i].delete() + } + } + // Now that the directory is empty. Delete it. + directory.delete() + } + + def listPresentations = {conf, room -> + def presentationsList = [] + def directory = roomDirectory(conf, room) + log.debug "directory ${directory.absolutePath}" + if( directory.exists() ){ + directory.eachFile(){ file-> + System.out.println(file.name) + if( file.isDirectory() ) + presentationsList.add( file.name ) + } + } + return presentationsList + } + + def getPresentationDir = { + return presentationDir + } + + def processUploadedPresentation = {uploadedPres -> + // Run conversion on another thread. + Timer t = new Timer(uploadedPres.getName(), false) + + t.runAfter(1000) { + try { + documentConversionService.processDocument(uploadedPres) + } finally { + t.cancel() + } + } + } + + def showSlide(String conf, String room, String presentationName, String id) { + new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "slide-${id}.swf") + } + + def showPngImage(String conf, String room, String presentationName, String id) { + new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "pngs" + File.separatorChar + "slide${id}.png") + } + + def showPresentation = {conf, room, filename -> + new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename + File.separatorChar + "slides.swf") + } + + def showThumbnail = {conf, room, presentationName, thumb -> + println "Show thumbnails request for $presentationName $thumb" + def thumbFile = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + + "thumbnails" + File.separatorChar + "thumb-${thumb}.png" + log.debug "showing $thumbFile" + + new File(thumbFile) + } + + def showTextfile = {conf, room, presentationName, textfile -> + println "Show textfiles request for $presentationName $textfile" + def txt = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + + "textfiles" + File.separatorChar + "slide-${textfile}.txt" + log.debug "showing $txt" + + new File(txt) + } + + def numberOfThumbnails = {conf, room, name -> + def thumbDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "thumbnails") + thumbDir.listFiles().length + } + + def numberOfPngs = {conf, room, name -> + def PngsDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "pngs") + PngsDir.listFiles().length + } + + def numberOfTextfiles = {conf, room, name -> + log.debug roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles" + def textfilesDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles") + textfilesDir.listFiles().length + } + + def roomDirectory = {conf, room -> + return new File(presentationDir + File.separatorChar + conf + File.separatorChar + room) + } + + def testConversionProcess() { + File presDir = new File(roomDirectory(testConferenceMock, testRoomMock).absolutePath + File.separatorChar + testPresentationName) + + if (presDir.exists()) { + File pres = new File(presDir.getAbsolutePath() + File.separatorChar + testUploadedPresentation) + if (pres.exists()) { + UploadedPresentation uploadedPres = new UploadedPresentation(testConferenceMock, testRoomMock, testPresentationName); + uploadedPres.setUploadedFile(pres); + // Run conversion on another thread. + new Timer().runAfter(1000) + { + documentConversionService.processDocument(uploadedPres) + } + } else { + log.error "${pres.absolutePath} does NOT exist" + } + } else { + log.error "${presDir.absolutePath} does NOT exist." + } + + } + + def getFile = {conf, room, presentationName -> + println "download request for $presentationName" + def fileDirectory = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + +"download") + //list the files of the download directory ; it must have only 1 file to download + def list = fileDirectory.listFiles() + //new File(pdfFile) + list[0] + } +} + +/*** Helper classes **/ +import java.io.FilenameFilter; +import java.io.File; +class PngFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + return (name.endsWith(".png")); + } +} diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java b/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java index df6037d137dd437c88cc97fda70385830f58bd38..596d20747b66b7ddbcfbcbaa2c34cab1174a1e22 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java @@ -181,7 +181,7 @@ public class MeetingMessageHandler implements MessageHandler { listener.handle(new UserRoleChanged(meetingId, userid, role)); } } - } + } } } } diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MessagingConstants.java b/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MessagingConstants.java index 33cc69c1dd6a459cdd5fefc71e995c3034b32946..1cc45708289ce8f64a096a0fed6d617f4e9d46db 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MessagingConstants.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/api/messaging/MessagingConstants.java @@ -53,6 +53,7 @@ public class MessagingConstants { public static final String USER_SHARE_WEBCAM_EVENT = "user_shared_webcam_message"; public static final String USER_UNSHARE_WEBCAM_EVENT = "user_unshared_webcam_message"; public static final String USER_ROLE_CHANGE_EVENT = "user_role_changed_message"; + public static final String SEND_POLLS_EVENT = "SendPollsEvent"; public static final String KEEP_ALIVE_REPLY = "keep_alive_reply";