Merge remote-tracking branch 'upstream/master' into EmojiReworked
@ -29,7 +29,7 @@ import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectUserCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUsetToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUserToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
|
||||
import org.freeswitch.esl.client.inbound.Client;
|
||||
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
|
||||
@ -128,7 +128,7 @@ public class ConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void tranfer(TransferUsetToMeetingCommand tutmc) {
|
||||
public void tranfer(TransferUserToMeetingCommand tutmc) {
|
||||
Client c = manager.getESLClient();
|
||||
if (c.canSend()) {
|
||||
c.sendAsyncApiCommand(tutmc.getCommand(), tutmc.getCommandArgs());
|
||||
|
@ -31,7 +31,7 @@ import org.bigbluebutton.freeswitch.voice.freeswitch.actions.FreeswitchCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUsetToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUserToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
|
||||
|
||||
public class FreeswitchApplication {
|
||||
@ -48,9 +48,12 @@ public class FreeswitchApplication {
|
||||
private final String USER = "0"; /* not used for now */
|
||||
|
||||
private volatile boolean sendMessages = false;
|
||||
|
||||
private final String audioProfile;
|
||||
|
||||
public FreeswitchApplication(ConnectionManager manager) {
|
||||
public FreeswitchApplication(ConnectionManager manager, String profile) {
|
||||
this.manager = manager;
|
||||
this.audioProfile = profile;
|
||||
}
|
||||
|
||||
private void queueMessage(FreeswitchCommand command) {
|
||||
@ -62,12 +65,13 @@ public class FreeswitchApplication {
|
||||
}
|
||||
}
|
||||
|
||||
public void transferUserToMeeting(String voiceConfId,
|
||||
String targetVoiceConfId, String voiceUserId) {
|
||||
TransferUsetToMeetingCommand tutmc = new TransferUsetToMeetingCommand(
|
||||
voiceConfId, targetVoiceConfId, voiceUserId, USER);
|
||||
queueMessage(tutmc);
|
||||
}
|
||||
public void transferUserToMeeting(String voiceConfId,
|
||||
String targetVoiceConfId, String voiceUserId) {
|
||||
TransferUserToMeetingCommand tutmc = new TransferUserToMeetingCommand(
|
||||
voiceConfId, targetVoiceConfId, voiceUserId, this.audioProfile,
|
||||
USER);
|
||||
queueMessage(tutmc);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
sendMessages = true;
|
||||
@ -153,8 +157,8 @@ public class FreeswitchApplication {
|
||||
EjectAllUsersCommand cmd = (EjectAllUsersCommand) command;
|
||||
System.out.println("Sending EjectAllUsersCommand for conference = [" + cmd.getRoom() + "]");
|
||||
manager.ejectAll(cmd);
|
||||
} else if (command instanceof TransferUsetToMeetingCommand) {
|
||||
TransferUsetToMeetingCommand cmd = (TransferUsetToMeetingCommand) command;
|
||||
} else if (command instanceof TransferUserToMeetingCommand) {
|
||||
TransferUserToMeetingCommand cmd = (TransferUserToMeetingCommand) command;
|
||||
System.out.println("Sending TransferUsetToMeetingCommand for conference = ["
|
||||
+ cmd.getRoom() + "]");
|
||||
manager.tranfer(cmd);
|
||||
|
@ -19,21 +19,23 @@
|
||||
|
||||
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
|
||||
|
||||
public class TransferUsetToMeetingCommand extends FreeswitchCommand {
|
||||
public class TransferUserToMeetingCommand extends FreeswitchCommand {
|
||||
|
||||
private final String targetRoom;
|
||||
private final String participant;
|
||||
private final String targetRoom;
|
||||
private final String participant;
|
||||
private final String audioProfile;
|
||||
|
||||
public TransferUsetToMeetingCommand(String room, String targetRoom,
|
||||
String participant, String requesterId) {
|
||||
super(room, requesterId);
|
||||
this.targetRoom = targetRoom;
|
||||
this.participant = participant;
|
||||
}
|
||||
public TransferUserToMeetingCommand(String room, String targetRoom,
|
||||
String participant, String profile, String requesterId) {
|
||||
super(room, requesterId);
|
||||
this.targetRoom = targetRoom;
|
||||
this.participant = participant;
|
||||
this.audioProfile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandArgs() {
|
||||
return room + SPACE + "transfer" + SPACE + targetRoom + SPACE
|
||||
+ participant;
|
||||
}
|
||||
@Override
|
||||
public String getCommandArgs() {
|
||||
return room + SPACE + "transfer" + SPACE + targetRoom + "@"
|
||||
+ this.audioProfile + SPACE + participant;
|
||||
}
|
||||
}
|
@ -24,6 +24,9 @@ freeswitch {
|
||||
port=8021
|
||||
password="ClueCon"
|
||||
}
|
||||
conf {
|
||||
profile="cdquality"
|
||||
}
|
||||
}
|
||||
|
||||
redis {
|
||||
|
@ -31,7 +31,7 @@ object Boot extends App with SystemConfiguration {
|
||||
|
||||
connManager.start()
|
||||
|
||||
val fsApplication = new FreeswitchApplication(connManager)
|
||||
val fsApplication = new FreeswitchApplication(connManager, fsProfile)
|
||||
fsApplication.start()
|
||||
|
||||
val redisMsgReceiver = new RedisMessageReceiver(fsApplication)
|
||||
|
@ -10,6 +10,7 @@ trait SystemConfiguration {
|
||||
lazy val eslHost = Try(config.getString("freeswitch.esl.host")).getOrElse("127.0.0.1")
|
||||
lazy val eslPort = Try(config.getInt("freeswitch.esl.port")).getOrElse(8021)
|
||||
lazy val eslPassword = Try(config.getString("freeswitch.esl.password")).getOrElse("ClueCon")
|
||||
lazy val fsProfile = Try(config.getString("freeswitch.conf.profile")).getOrElse("cdquality")
|
||||
|
||||
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
|
||||
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)
|
||||
|
@ -4,7 +4,7 @@
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_redirect default;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Allow 30M uploaded presentation document.
|
||||
client_max_body_size 30m;
|
||||
|
@ -16,6 +16,7 @@ import org.bigbluebutton.screenshare.client.ExitCode;
|
||||
import org.bigbluebutton.screenshare.client.ScreenShareInfo;
|
||||
import org.bigbluebutton.screenshare.client.net.NetworkConnectionListener;
|
||||
import org.bytedeco.javacpp.Loader;
|
||||
import org.bytedeco.javacpp.avcodec;
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
@ -144,25 +145,27 @@ public class FfmpegScreenshare {
|
||||
frame = grabber.grabImage();
|
||||
if (frame != null) {
|
||||
try {
|
||||
long timestamp = now - startTime;
|
||||
// Override timestamp from system screen grabber. Otherwise, we will have skewed recorded file.
|
||||
// FfmpegFrameRecorder needs to propagate this timestamp into the avpacket sent to the server.
|
||||
// ralam - Sept. 14, 2016
|
||||
frame.timestamp = timestamp;
|
||||
//System.out.println("frame timestamp=[" + frame.timestamp + "] ");
|
||||
mainRecorder.record(frame);
|
||||
} catch (Exception e) {
|
||||
//System.out.println("CaptureScreen Exception 1");
|
||||
if (!ignoreDisconnect) {
|
||||
listener.networkConnectionException(ExitCode.INTERNAL_ERROR, null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
listener.networkConnectionException(ExitCode.INTERNAL_ERROR, null);
|
||||
}
|
||||
|
||||
|
||||
long sleepFramerate = (long) (1000 / frameRate);
|
||||
long timestamp = now - startTime;
|
||||
mainRecorder.setTimestamp(timestamp * 1000);
|
||||
|
||||
// System.out.println("i=[" + i + "] timestamp=[" + timestamp + "]");
|
||||
//System.out.println("timestamp=[" + timestamp + "]");
|
||||
mainRecorder.setFrameNumber(frameNumber);
|
||||
|
||||
// System.out.println("[ENCODER] encoded image " + frameNumber + " in " + (System.currentTimeMillis() - now));
|
||||
|
@ -688,7 +688,7 @@ public class FFmpegFrameRecorder extends FrameRecorder {
|
||||
if (oc != null) {
|
||||
try {
|
||||
/* flush all the buffers */
|
||||
while (video_st != null && ifmt_ctx == null && recordImage(0, 0, 0, 0, 0, AV_PIX_FMT_NONE, (Buffer[])null));
|
||||
while (video_st != null && ifmt_ctx == null && recordImage(0, 0, 0, 0, 0, AV_PIX_FMT_NONE, 0, (Buffer[])null));
|
||||
while (audio_st != null && ifmt_ctx == null && recordSamples(0, 0, (Buffer[])null));
|
||||
|
||||
if (interleaved && video_st != null && audio_st != null) {
|
||||
@ -710,11 +710,11 @@ public class FFmpegFrameRecorder extends FrameRecorder {
|
||||
}
|
||||
public void record(Frame frame, int pixelFormat) throws Exception {
|
||||
if (frame == null || (frame.image == null && frame.samples == null)) {
|
||||
recordImage(0, 0, 0, 0, 0, pixelFormat, (Buffer[])null);
|
||||
recordImage(0, 0, 0, 0, 0, pixelFormat, frame.timestamp, (Buffer[])null);
|
||||
} else {
|
||||
if (frame.image != null) {
|
||||
frame.keyFrame = recordImage(frame.imageWidth, frame.imageHeight, frame.imageDepth,
|
||||
frame.imageChannels, frame.imageStride, pixelFormat, frame.image);
|
||||
frame.imageChannels, frame.imageStride, pixelFormat, frame.timestamp, frame.image);
|
||||
}
|
||||
if (frame.samples != null) {
|
||||
frame.keyFrame = recordSamples(frame.sampleRate, frame.audioChannels, frame.samples);
|
||||
@ -722,7 +722,8 @@ public class FFmpegFrameRecorder extends FrameRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean recordImage(int width, int height, int depth, int channels, int stride, int pixelFormat, Buffer ... image) throws Exception {
|
||||
public boolean recordImage(int width, int height, int depth, int channels, int stride,
|
||||
int pixelFormat, long frameTimestamp, Buffer ... image) throws Exception {
|
||||
if (video_st == null) {
|
||||
throw new Exception("No video output stream (Is imageWidth > 0 && imageHeight > 0 and has start() been called?)");
|
||||
}
|
||||
@ -808,10 +809,15 @@ public class FFmpegFrameRecorder extends FrameRecorder {
|
||||
/* if zero size, it means the image was buffered */
|
||||
if (got_video_packet[0] != 0) {
|
||||
if (video_pkt.pts() != AV_NOPTS_VALUE) {
|
||||
video_pkt.pts(av_rescale_q(video_pkt.pts(), video_c.time_base(), video_st.time_base()));
|
||||
// Override timestamp from system screen grabber. Otherwise, we will have skewed recorded file.
|
||||
// FfmpegFrameRecorder needs to propagate this timestamp into the avpacket sent to the server.
|
||||
// ralam - Sept. 14, 2016
|
||||
video_pkt.pts(frameTimestamp);
|
||||
//video_pkt.pts(av_rescale_q(video_pkt.pts(), video_c.time_base(), video_st.time_base()));
|
||||
}
|
||||
if (video_pkt.dts() != AV_NOPTS_VALUE) {
|
||||
video_pkt.dts(av_rescale_q(video_pkt.dts(), video_c.time_base(), video_st.time_base()));
|
||||
video_pkt.dts(frameTimestamp);
|
||||
//video_pkt.dts(av_rescale_q(video_pkt.dts(), video_c.time_base(), video_st.time_base()));
|
||||
}
|
||||
video_pkt.stream_index(video_st.index());
|
||||
} else {
|
||||
|
@ -26,7 +26,7 @@ import org.bigbluebutton.red5.pubsub.MessagePublisher;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
|
||||
public class CaptionService {
|
||||
private static Logger log = Red5LoggerFactory.getLogger( CaptionService.class, "bigbluebutton" );
|
||||
|
||||
@ -59,15 +59,29 @@ public class CaptionService {
|
||||
}
|
||||
|
||||
public void editCaptionHistory(Map<String, Object> msg) {
|
||||
int startIndex = (Integer) msg.get("startIndex");
|
||||
int endIndex = (Integer) msg.get("endIndex");
|
||||
String locale = msg.get("locale").toString();
|
||||
String localeCode = msg.get("localeCode").toString();
|
||||
String text = msg.get("text").toString();
|
||||
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String userID = getBbbSession().getInternalUserID();
|
||||
|
||||
|
||||
Integer startIndex;
|
||||
if (msg.get("startIndex") instanceof Double) {
|
||||
Double tempStartIndex = (Double) msg.get("startIndex");
|
||||
startIndex = tempStartIndex.intValue();
|
||||
} else {
|
||||
startIndex = (Integer) msg.get("startIndex");
|
||||
}
|
||||
|
||||
Integer endIndex;
|
||||
if (msg.get("endIndex") instanceof Double) {
|
||||
Double tempEndIndex = (Double) msg.get("endIndex");
|
||||
endIndex = tempEndIndex.intValue();
|
||||
} else {
|
||||
endIndex = (Integer) msg.get("endIndex");
|
||||
}
|
||||
|
||||
red5InGW.editCaptionHistory(meetingID, userID, startIndex, endIndex, locale, localeCode, text);
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,23 @@ public class PollingService {
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String userId = getBbbSession().getInternalUserID();
|
||||
String pollId = (String) message.get("pollId");
|
||||
Integer questionId = (Integer) message.get("answerId");
|
||||
Integer answerId = (Integer) message.get("answerId");
|
||||
|
||||
|
||||
Integer questionId;
|
||||
if (message.get("answerId") instanceof Double) {
|
||||
Double tempQuestionId = (Double) message.get("answerId");
|
||||
questionId = tempQuestionId.intValue();
|
||||
} else {
|
||||
questionId = (Integer) message.get("answerId");
|
||||
}
|
||||
|
||||
Integer answerId;
|
||||
if (message.get("answerId") instanceof Double) {
|
||||
Double tempAnswerId = (Double) message.get("answerId");
|
||||
answerId = tempAnswerId.intValue();
|
||||
} else {
|
||||
answerId = (Integer) message.get("answerId");
|
||||
}
|
||||
|
||||
red5GW.votePoll(meetingID, userId, pollId, questionId, answerId);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 391 B |
@ -322,12 +322,12 @@ bbb.screensharePublish.helpText.MacChrome1 = 1. Locate 'screenshare.jnlp'
|
||||
bbb.screensharePublish.helpText.MacChrome2 = 2. Select 'Show In Finder'
|
||||
bbb.screensharePublish.helpText.MacChrome3 = 3. Right-click and select 'Open'
|
||||
bbb.screensharePublish.helpText.MacChrome4 = 4. Select 'Open' (if prompted)
|
||||
bbb.screensharePublish.helpText.LinuxFirefox1 = Placeholder linuxFirefox1
|
||||
bbb.screensharePublish.helpText.LinuxFirefox2 = Placeholder linuxFirefox2
|
||||
bbb.screensharePublish.helpText.LinuxFirefox3 = Placeholder linuxFirefox3
|
||||
bbb.screensharePublish.helpText.LinuxChrome1 = Placeholder linuxChrome1
|
||||
bbb.screensharePublish.helpText.LinuxChrome2 = Placeholder linuxChrome2
|
||||
bbb.screensharePublish.helpText.LinuxChrome3 = Placeholder linuxChrome3
|
||||
bbb.screensharePublish.helpText.LinuxFirefox1 = 1. Click 'OK' to run
|
||||
bbb.screensharePublish.helpText.LinuxFirefox2 = 2. Accept the certificate
|
||||
bbb.screensharePublish.helpText.LinuxFirefox3 =
|
||||
bbb.screensharePublish.helpText.LinuxChrome1 = 1. Locate 'screenshare.jnlp'
|
||||
bbb.screensharePublish.helpText.LinuxChrome2 = 2. Click to open
|
||||
bbb.screensharePublish.helpText.LinuxChrome3 = 3. Accept the certificate
|
||||
bbb.screensharePublish.shareTypeLabel.text = Share:
|
||||
bbb.screensharePublish.shareType.fullScreen = Full screen
|
||||
bbb.screensharePublish.shareType.region = Region
|
||||
|
@ -47,7 +47,7 @@
|
||||
uri="rtmp://HOST/screenshare"
|
||||
showButton="true"
|
||||
baseTabIndex="201"
|
||||
useWebRTCIfAvailable="false"
|
||||
tryWebRTCFirst="false"
|
||||
chromeExtensionKey=""
|
||||
help="http://HOST/client/help/screenshare-help.html"
|
||||
/>
|
||||
|
@ -97,7 +97,7 @@
|
||||
iframe.onload = function() {
|
||||
iframe.isLoaded = true;
|
||||
};
|
||||
iframe.src = 'https://www.webrtc-experiment.com/getSourceId/';
|
||||
// iframe.src = 'https://www.webrtc-experiment.com/getSourceId/';
|
||||
iframe.style.display = 'none';
|
||||
(document.body || document.documentElement).appendChild(iframe);
|
||||
})();
|
||||
})();
|
||||
|
@ -36,16 +36,12 @@ package org.bigbluebutton.core.managers
|
||||
}
|
||||
|
||||
|
||||
public function setUri(uri:String):void {
|
||||
connDelegate.setUri(uri);
|
||||
}
|
||||
|
||||
public function get connection():NetConnection {
|
||||
return connDelegate.connection;
|
||||
}
|
||||
|
||||
public function connect(params:ConferenceParameters):void {
|
||||
connDelegate.connect(params);
|
||||
public function connect():void {
|
||||
connDelegate.connect();
|
||||
}
|
||||
|
||||
public function disconnect(onUserAction:Boolean):void {
|
||||
|
@ -18,18 +18,15 @@
|
||||
*/
|
||||
package org.bigbluebutton.core.managers
|
||||
{
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
import flash.display.DisplayObject;
|
||||
import flash.events.TimerEvent;
|
||||
import flash.utils.Dictionary;
|
||||
import flash.utils.Timer;
|
||||
|
||||
import flash.utils.Timer;
|
||||
import mx.collections.ArrayCollection;
|
||||
import mx.core.FlexGlobals;
|
||||
import mx.core.IFlexDisplayObject;
|
||||
import mx.managers.PopUpManager;
|
||||
|
||||
import mx.managers.PopUpManager;
|
||||
import org.as3commons.logging.api.ILogger;
|
||||
import org.as3commons.logging.api.getClassLogger;
|
||||
import org.bigbluebutton.core.UsersUtil;
|
||||
|
@ -27,6 +27,10 @@ package org.bigbluebutton.core.managers {
|
||||
conferenceParameters = c;
|
||||
}
|
||||
|
||||
public function getConfParams():ConferenceParameters {
|
||||
return conferenceParameters;
|
||||
}
|
||||
|
||||
public function getLogoutUrl():String {
|
||||
if (conferenceParameters == null)
|
||||
return null;
|
||||
|
@ -30,7 +30,7 @@ package org.bigbluebutton.main.events
|
||||
public static const PORT_TEST_UPDATE:String = "PORT_TEST_UPDATE";
|
||||
public static const TUNNELING_FAILED:String = "RTMTP_ALSO_FAILED";
|
||||
|
||||
public var protocol:String;
|
||||
public var tunnel:Boolean;
|
||||
public var hostname:String;
|
||||
public var port:String;
|
||||
public var app:String;
|
||||
|
@ -24,13 +24,7 @@ package org.bigbluebutton.main.events
|
||||
{
|
||||
public static const START_USER_SERVICES:String = "START_USER_SERVICES";
|
||||
public static const USER_SERVICES_STARTED:String = "USER_SERVICES_STARTED";
|
||||
|
||||
public var applicationURI:String;
|
||||
public var hostURI:String;
|
||||
public var isTunnelling:Boolean = false;
|
||||
|
||||
public var user:Object;
|
||||
|
||||
|
||||
public function UserServicesEvent(type:String)
|
||||
{
|
||||
super(type, true, false);
|
||||
|
@ -46,11 +46,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
</EventHandlers>
|
||||
|
||||
<EventHandlers type="{PortTestEvent.PORT_TEST_SUCCESS}" >
|
||||
<MethodInvoker generator="{ModulesProxy}" method="portTestSuccess" arguments="{event.protocol}" />
|
||||
<MethodInvoker generator="{ModulesProxy}" method="portTestSuccess" arguments="{event.tunnel}" />
|
||||
</EventHandlers>
|
||||
|
||||
<EventHandlers type="{PortTestEvent.PORT_TEST_FAILED}" >
|
||||
<MethodInvoker generator="{ModulesProxy}" method="testRTMPT" arguments="{event.protocol}" />
|
||||
<MethodInvoker generator="{ModulesProxy}" method="testRTMPT" arguments="{event.tunnel}" />
|
||||
</EventHandlers>
|
||||
|
||||
<EventHandlers type="{SuccessfulLoginEvent.USER_LOGGED_IN}" >
|
||||
|
@ -1,3 +1,3 @@
|
||||
/**
* 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
{
import flash.events.NetStatusEvent;
import flash.events.TimerEvent;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
import flash.utils.Timer;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
[Bindable]
/**
* Test RTMP port.
*
* @author Thijs Triemstra ( info@collab.nl )
*/
public class PortTest
{
private static const LOGGER:ILogger = getClassLogger(PortTest);
/**
* Protocol name.
*/
private var protocol : String;
/**
* Protocol name (uppercase).
*/
public var protocolName : String;
/**
* RTMP hostname.
*/
private var hostname : String;
/**
* RTMP port.
*/
public var port : String;
/**
* RTMP port.
*/
public var portName : String = "Default";
/**
* RTMP application.
*/
private var application : String;
/**
* Base RTMP URI.
*/
private var baseURI : String;
/**
* RTMP connection.
*/
public var nc : NetConnection;
/**
* Connection status.
*/
public var status : String;
private var _connectionListener:Function;
/**
* Timer to control timeout of connection test
*/
private var testTimeout:Number = 0;
/**
* Timer to control timeout of connection test
*/
private var connectionTimer:Timer;
/**
* Set default encoding to AMF0 so FMS also understands.
*/
NetConnection.defaultObjectEncoding = ObjectEncoding.AMF0;
/**
* Create new port test and connect to the RTMP server.
*
* @param protocol
* @param hostname
* @param port
* @param application
* @testTimeout timeout of test in milliseconds
*/
public function PortTest( protocol : String = "",
hostname : String = "",
port : String = "",
application : String = "",
testTimeout : Number = 10000
)
{
this.protocol = protocol;
this.protocolName = protocol.toUpperCase();
this.hostname = hostname;
this.application = application;
this.testTimeout = testTimeout;
if ( port.length > 0 )
protocol : String = "",
testTimeout : Number = 10000
protocol : String = "",
)
protocol : String = "",
{
}
protocol : String = "",
this.protocolName = protocol.toUpperCase();
{
hostname : String = "",
protocol : String = "",
this.protocol = protocol;
hostname : String = "",
protocol : String = "",
this.baseURI = this.protocol + "://" + this.hostname + this.port + "/" + this.application;
//
}
/**
* Start connection.
*/
public function connect() : void
{
this.nc = new NetConnection();
this.nc.client = this;
|
||||
/**
* 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
{
import flash.events.NetStatusEvent;
import flash.events.TimerEvent;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
import flash.utils.Timer;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
[Bindable]
/**
* Test RTMP port.
*
* @author Thijs Triemstra ( info@collab.nl )
*/
public class PortTest
{
private static const LOGGER:ILogger = getClassLogger(PortTest);
/**
* Connect using rtmp or rtmpt.
*/
private var tunnel: Boolean;
/**
* RTMP hostname.
*/
private var hostname : String;
/**
* RTMP port.
*/
public var port : String;
/**
* RTMP port.
*/
public var portName : String = "Default";
/**
* RTMP application.
*/
private var application : String;
/**
* Base RTMP URI.
*/
private var baseURI : String;
/**
* RTMP connection.
*/
public var nc : NetConnection;
/**
* Connection status.
*/
public var status : String;
private var _connectionListener:Function;
/**
* Timer to control timeout of connection test
*/
private var testTimeout:Number = 0;
/**
* Timer to control timeout of connection test
*/
private var connectionTimer:Timer;
/**
* Set default encoding to AMF0 so FMS also understands.
*/
NetConnection.defaultObjectEncoding = ObjectEncoding.AMF0;
/**
* Create new port test and connect to the RTMP server.
*
* @param protocol
* @param hostname
* @param port
* @param application
* @testTimeout timeout of test in milliseconds
*/
public function PortTest( tunnel : Boolean,
hostname : String = "",
port : String = "",
application : String = "",
testTimeout : Number = 10000) {
this.tunnel = tunnel;
this.hostname = hostname;
this.application = application;
this.testTimeout = testTimeout;
if ( port.length > 0 ) {
protocol : String = "",
)
protocol : String = "",
{
hostname : String = "",
{
this.port = port;
}
hostname : String = "",
protocol : String = "",
if (tunnel) {
hostname : String = "",
this.protocolName = protocol.toUpperCase();
port : String = "",
port : String = "",
protocol : String = "",
port : String = "",
hostname : String = "",
//
}
/**
* Start connection.
*/
public function connect() : void
{
this.nc = new NetConnection();
this.nc.client = this;
|
||||
this.nc.proxyType = "best";
|
||||
this.nc.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
// connect to server
try
{
// Create connection with the server.
this.nc.connect( this.baseURI, "portTestMeetingId", "portTestDummyUserId" );
status = "Connecting...";
connectionTimer = new Timer(testTimeout, 1);
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
connectionTimer.start();
}
catch( e : ArgumentError )
{
// Invalid parameters.
status = "ERROR: " + e.message;
}
}
/**
* Method called when connection timed out
*/
public function connectionTimeout (e:TimerEvent) : void
{
status = "FAILED";
_connectionListener(status, protocol, hostname, port, application);
close();
}
/**
* Close connection.
*/
public function close() : void
{
//Stop timeout timer when connected/rejected
connectionTimer.stop();
// Remove listener.
this.nc.removeEventListener( NetStatusEvent.NET_STATUS, netStatus );
// Close the NetConnection.
this.nc.close();
}
/**
* Catch NetStatusEvents.
*
* @param event
*/
protected function netStatus( event : NetStatusEvent ) : void
{
var info : Object = event.info;
var statusCode : String = info.code;
//
if ( statusCode == "NetConnection.Connect.Success" )
{
status = "SUCCESS";
_connectionListener(status, protocol, hostname, port, application);
}
connectionTimer = new Timer(testTimeout, 1);
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
statusCode == "NetConnection.Connect.Closed" )
// Create connection with the server.
}
_connectionListener(status, protocol, hostname, port, application);
}
catch( e : ArgumentError )
connectionTimer = new Timer(testTimeout, 1);
}
public function onBWCheck(... rest):Number {
return 0;
}
public function onBWDone(... rest):void {
var p_bw:Number;
if (rest.length > 0) p_bw = rest[0];
// your application should do something here
// when the bandwidth check is complete
LOGGER.debug("bandwidth = {0} Kbps.", [p_bw]);
}
public function addConnectionSuccessListener(listener:Function):void {
_connectionListener = listener;
}
}
}
|
||||
this.nc.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
// connect to server
try
{
LOGGER.debug("Testing connection to " + this.baseURI);
// Create connection with the server.
this.nc.connect( this.baseURI, "portTestMeetingId", "portTestDummyUserId" );
status = "Connecting...";
connectionTimer = new Timer(testTimeout, 1);
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
connectionTimer.start();
}
catch( e : ArgumentError )
{
// Invalid parameters.
status = "ERROR: " + e.message;
}
}
/**
* Method called when connection timed out
*/
public function connectionTimeout (e:TimerEvent) : void
{
LOGGER.debug("Timedout trying to connect to " + this.baseURI);
status = "FAILED";
_connectionListener(status, tunnel, hostname, port, application);
close();
}
/**
* Close connection.
*/
public function close() : void
{
//Stop timeout timer when connected/rejected
connectionTimer.stop();
// Remove listener.
this.nc.removeEventListener( NetStatusEvent.NET_STATUS, netStatus );
// Close the NetConnection.
this.nc.close();
}
/**
* Catch NetStatusEvents.
*
* @param event
*/
protected function netStatus( event : NetStatusEvent ) : void
{
var info : Object = event.info;
var statusCode : String = info.code;
//
// Close NetConnection.
close();
connectionTimer = new Timer(testTimeout, 1);
}
status = "Connecting...";
connectionTimer = new Timer(testTimeout, 1);
catch( e : ArgumentError )
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
// Create connection with the server.
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
statusCode == "NetConnection.Connect.Closed" ) {
LOGGER.debug("Failed to connect to " + this.baseURI);
}
connectionTimer.addEventListener(TimerEvent.TIMER, connectionTimeout);
}
}
public function onBWCheck(... rest):Number {
return 0;
}
public function onBWDone(... rest):void {
var p_bw:Number;
if (rest.length > 0) p_bw = rest[0];
// your application should do something here
// when the bandwidth check is complete
LOGGER.debug("bandwidth = {0} Kbps.", [p_bw]);
}
public function addConnectionSuccessListener(listener:Function):void {
_connectionListener = listener;
}
}
}
|
||||
|
25
bigbluebutton-client/src/org/bigbluebutton/main/model/PortTestProxy.as
Normal file → Executable file
@ -28,31 +28,29 @@ package org.bigbluebutton.main.model {
|
||||
private static const LOGGER:ILogger = getClassLogger(PortTestProxy);
|
||||
|
||||
private var nc:NetConnection;
|
||||
private var protocol:String;
|
||||
private var tunnel:Boolean;
|
||||
private var port:String;
|
||||
private var hostname:String;
|
||||
private var application:String;
|
||||
private var uri:String;
|
||||
private var modulesDispatcher:ModulesDispatcher;
|
||||
|
||||
public function PortTestProxy(modulesDispatcher: ModulesDispatcher) {
|
||||
this.modulesDispatcher = modulesDispatcher;
|
||||
}
|
||||
|
||||
public function connect(protocol:String = "", hostname:String = "", port:String = "", application:String = "", testTimeout:Number = 10000):void {
|
||||
var portTest:PortTest = new PortTest(protocol,hostname,port,application, testTimeout);
|
||||
public function connect(tunnel:Boolean, hostname:String = "", port:String = "", application:String = "", testTimeout:Number = 10000):void {
|
||||
this.tunnel = tunnel;
|
||||
var portTest:PortTest = new PortTest(tunnel, hostname, port, application, testTimeout);
|
||||
portTest.addConnectionSuccessListener(connectionListener);
|
||||
var red5Url:String = protocol + "://" + hostname + "/" + application;
|
||||
|
||||
|
||||
portTest.connect();
|
||||
}
|
||||
|
||||
private function connectionListener(status:String, protocol:String, hostname:String, port:String, application:String):void {
|
||||
uri = protocol + "://" + hostname + "/" + application;
|
||||
private function connectionListener(status:String, tunnel:Boolean, hostname:String, port:String, application:String):void {
|
||||
if (status == "SUCCESS") {
|
||||
modulesDispatcher.sendPortTestSuccessEvent(port, hostname, protocol, application);
|
||||
modulesDispatcher.sendPortTestSuccessEvent(port, hostname, tunnel, application);
|
||||
} else {
|
||||
modulesDispatcher.sendPortTestFailedEvent(port, hostname, protocol, application);
|
||||
modulesDispatcher.sendPortTestFailedEvent(port, hostname, tunnel, application);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,14 +64,13 @@ package org.bigbluebutton.main.model {
|
||||
var statusCode : String = info.code;
|
||||
|
||||
if (statusCode == "NetConnection.Connect.Success") {
|
||||
modulesDispatcher.sendPortTestSuccessEvent(port, hostname, protocol, application);
|
||||
modulesDispatcher.sendPortTestSuccessEvent(port, hostname, tunnel, application);
|
||||
} else if (statusCode == "NetConnection.Connect.Rejected" ||
|
||||
statusCode == "NetConnection.Connect.Failed" ||
|
||||
statusCode == "NetConnection.Connect.Closed" ) {
|
||||
LOGGER.error("::netStatusEventHandler - Failed to connect to {0}", [uri]);
|
||||
modulesDispatcher.sendPortTestFailedEvent(port, hostname, protocol, application);
|
||||
modulesDispatcher.sendPortTestFailedEvent(port, hostname, tunnel, application);
|
||||
} else {
|
||||
LOGGER.error("Failed to connect to {0} due to {1}", [uri, statusCode]);
|
||||
modulesDispatcher.sendPortTestFailedEvent(port, hostname, tunnel, application);
|
||||
}
|
||||
// Close NetConnection.
|
||||
close();
|
||||
|
@ -18,15 +18,19 @@
|
||||
*/
|
||||
package org.bigbluebutton.main.model.modules
|
||||
{
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import flash.system.ApplicationDomain;
|
||||
import flash.utils.Dictionary;
|
||||
import mx.collections.ArrayCollection;
|
||||
import flash.utils.Dictionary;
|
||||
|
||||
import mx.collections.ArrayCollection;
|
||||
|
||||
import org.as3commons.logging.api.ILogger;
|
||||
import org.as3commons.logging.api.getClassLogger;
|
||||
import org.bigbluebutton.common.IBigBlueButtonModule;
|
||||
import org.bigbluebutton.common.Role;
|
||||
import org.bigbluebutton.core.BBB;
|
||||
import org.bigbluebutton.core.model.MeetingModel;
|
||||
import org.bigbluebutton.main.events.AppVersionEvent;
|
||||
import org.bigbluebutton.main.model.ConferenceParameters;
|
||||
|
||||
@ -44,8 +48,6 @@ package org.bigbluebutton.main.model.modules
|
||||
private var _applicationDomain:ApplicationDomain;
|
||||
private var conferenceParameters:ConferenceParameters;
|
||||
|
||||
private var _protocol:String;
|
||||
|
||||
private var modulesDispatcher:ModulesDispatcher;
|
||||
|
||||
public function ModuleManager(modulesDispatcher: ModulesDispatcher)
|
||||
@ -72,8 +74,8 @@ package org.bigbluebutton.main.model.modules
|
||||
BBB.loadConfig();
|
||||
}
|
||||
|
||||
public function useProtocol(protocol:String):void {
|
||||
_protocol = protocol;
|
||||
public function useProtocol(tunnel:Boolean):void {
|
||||
BBB.initConnectionManager().isTunnelling = tunnel;
|
||||
}
|
||||
|
||||
public function get portTestHost():String {
|
||||
@ -96,7 +98,11 @@ package org.bigbluebutton.main.model.modules
|
||||
var m:ModuleDescriptor = getModule(name);
|
||||
if (m != null) {
|
||||
var bbb:IBigBlueButtonModule = m.module as IBigBlueButtonModule;
|
||||
m.loadConfigAttributes(conferenceParameters, _protocol);
|
||||
var protocol:String = "rtmp";
|
||||
if (BBB.initConnectionManager().isTunnelling) {
|
||||
protocol = "rtmpt";
|
||||
}
|
||||
m.loadConfigAttributes(conferenceParameters, protocol);
|
||||
bbb.start(m.attributes);
|
||||
}
|
||||
}
|
||||
@ -155,8 +161,7 @@ package org.bigbluebutton.main.model.modules
|
||||
}
|
||||
|
||||
public function startUserServices():void {
|
||||
var appURL:String = BBB.getConfigManager().config.application.uri.replace(/rtmp:/gi, _protocol + ":");
|
||||
modulesDispatcher.sendStartUserServicesEvent(appURL, BBB.getConfigManager().config.application.host, _protocol.toUpperCase() == "RTMPT");
|
||||
modulesDispatcher.sendStartUserServicesEvent();
|
||||
}
|
||||
|
||||
public function loadAllModules(parameters:ConferenceParameters):void{
|
||||
|
@ -18,10 +18,8 @@
|
||||
*/
|
||||
package org.bigbluebutton.main.model.modules
|
||||
{
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import flash.events.TimerEvent;
|
||||
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
import flash.events.TimerEvent;
|
||||
import org.as3commons.logging.api.ILogger;
|
||||
import org.as3commons.logging.api.getClassLogger;
|
||||
import org.as3commons.logging.util.jsonXify;
|
||||
@ -71,11 +69,8 @@ package org.bigbluebutton.main.model.modules
|
||||
dispatcher.dispatchEvent(loginEvent);
|
||||
}
|
||||
|
||||
public function sendStartUserServicesEvent(application:String, host:String, isTunnelling:Boolean):void{
|
||||
public function sendStartUserServicesEvent():void{
|
||||
var e:UserServicesEvent = new UserServicesEvent(UserServicesEvent.START_USER_SERVICES);
|
||||
e.applicationURI = application;
|
||||
e.hostURI = host;
|
||||
e.isTunnelling = isTunnelling;
|
||||
dispatcher.dispatchEvent(e);
|
||||
}
|
||||
|
||||
@ -130,11 +125,11 @@ package org.bigbluebutton.main.model.modules
|
||||
dispatcher.dispatchEvent(new PortTestEvent(PortTestEvent.TUNNELING_FAILED));
|
||||
}
|
||||
|
||||
public function sendPortTestSuccessEvent(port:String, host:String, protocol:String, app:String):void{
|
||||
public function sendPortTestSuccessEvent(port:String, host:String, tunnel:Boolean, app:String):void{
|
||||
var logData:Object = new Object();
|
||||
logData.port = port;
|
||||
logData.server = host;
|
||||
logData.protocol = protocol;
|
||||
logData.tunnel = tunnel;
|
||||
logData.app = app;
|
||||
logData.userId = meetingInfo.userId;
|
||||
logData.username = meetingInfo.username;
|
||||
@ -145,17 +140,17 @@ package org.bigbluebutton.main.model.modules
|
||||
var portEvent:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_SUCCESS);
|
||||
portEvent.port = port;
|
||||
portEvent.hostname = host;
|
||||
portEvent.protocol = protocol;
|
||||
portEvent.tunnel = tunnel;
|
||||
portEvent.app = app;
|
||||
dispatcher.dispatchEvent(portEvent);
|
||||
|
||||
}
|
||||
|
||||
public function sendPortTestFailedEvent(port:String, host:String, protocol:String, app:String):void{
|
||||
public function sendPortTestFailedEvent(port:String, host:String, tunnel:Boolean, app:String):void{
|
||||
var portFailEvent:PortTestEvent = new PortTestEvent(PortTestEvent.PORT_TEST_FAILED);
|
||||
portFailEvent.port = port;
|
||||
portFailEvent.hostname = host;
|
||||
portFailEvent.protocol = protocol;
|
||||
portFailEvent.tunnel = tunnel;
|
||||
portFailEvent.app = app;
|
||||
dispatcher.dispatchEvent(portFailEvent);
|
||||
|
||||
|
@ -42,8 +42,9 @@ package org.bigbluebutton.main.model.modules
|
||||
return _user.username;
|
||||
}
|
||||
|
||||
public function portTestSuccess(protocol:String):void {
|
||||
modulesManager.useProtocol(protocol);
|
||||
public function portTestSuccess(tunnel:Boolean):void {
|
||||
LOGGER.debug("Successfully tested connection to server. isTunnelling=" + tunnel);
|
||||
modulesManager.useProtocol(tunnel);
|
||||
modulesManager.startUserServices();
|
||||
}
|
||||
|
||||
@ -72,12 +73,16 @@ package org.bigbluebutton.main.model.modules
|
||||
}
|
||||
|
||||
public function testRTMP():void{
|
||||
portTestProxy.connect("RTMP", getPortTestHost(), "1935", getPortTestApplication(), getPortTestTimeout());
|
||||
portTestProxy.connect(false /*"RTMP"*/, getPortTestHost(), "1935", getPortTestApplication(), getPortTestTimeout());
|
||||
}
|
||||
|
||||
public function testRTMPT(protocol:String):void{
|
||||
if (protocol == "RTMP") portTestProxy.connect("RTMPT", getPortTestHost(), "", getPortTestApplication(), getPortTestTimeout());
|
||||
else modulesDispatcher.sendTunnelingFailedEvent(getPortTestHost(), getPortTestApplication());
|
||||
public function testRTMPT(tunnel:Boolean):void{
|
||||
if (! tunnel) {
|
||||
// Try to test using rtmpt as rtmp failed.
|
||||
portTestProxy.connect(true /*"RTMPT"*/, getPortTestHost(), "", getPortTestApplication(), getPortTestTimeout());
|
||||
} else {
|
||||
modulesDispatcher.sendTunnelingFailedEvent(getPortTestHost(), getPortTestApplication());
|
||||
}
|
||||
}
|
||||
|
||||
public function loadAllModules(params:ConferenceParameters):void{
|
||||
|
@ -18,8 +18,7 @@
|
||||
*/
|
||||
package org.bigbluebutton.main.model.users
|
||||
{
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
import flash.events.AsyncErrorEvent;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.NetStatusEvent;
|
||||
@ -30,6 +29,7 @@ package org.bigbluebutton.main.model.users
|
||||
import flash.utils.Timer;
|
||||
import org.as3commons.logging.api.ILogger;
|
||||
import org.as3commons.logging.api.getClassLogger;
|
||||
import org.bigbluebutton.core.BBB;
|
||||
import org.bigbluebutton.core.UsersUtil;
|
||||
import org.bigbluebutton.core.managers.ReconnectionManager;
|
||||
import org.bigbluebutton.core.services.BandwidthMonitor;
|
||||
@ -39,7 +39,7 @@ package org.bigbluebutton.main.model.users
|
||||
import org.bigbluebutton.main.model.ConferenceParameters;
|
||||
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
|
||||
import org.bigbluebutton.main.model.users.events.UsersConnectionEvent;
|
||||
|
||||
|
||||
public class NetConnectionDelegate
|
||||
{
|
||||
private static const LOGGER:ILogger = getClassLogger(NetConnectionDelegate);
|
||||
@ -50,17 +50,8 @@ package org.bigbluebutton.main.model.users
|
||||
|
||||
private var _userid:Number = -1;
|
||||
private var _role:String = "unknown";
|
||||
private var _applicationURI:String;
|
||||
private var _conferenceParameters:ConferenceParameters;
|
||||
|
||||
// These two are just placeholders. We'll get this from the server later and
|
||||
// then pass to other modules.
|
||||
private var _authToken:String = "AUTHORIZED";
|
||||
private var _room:String;
|
||||
private var tried_tunneling:Boolean = false;
|
||||
private var logoutOnUserCommand:Boolean = false;
|
||||
private var backoff:Number = 2000;
|
||||
|
||||
private var dispatcher:Dispatcher;
|
||||
private var _messageListeners:Array = new Array();
|
||||
|
||||
@ -70,24 +61,10 @@ package org.bigbluebutton.main.model.users
|
||||
|
||||
private var _validateTokenTimer:Timer = null;
|
||||
|
||||
public function NetConnectionDelegate():void
|
||||
{
|
||||
public function NetConnectionDelegate():void {
|
||||
dispatcher = new Dispatcher();
|
||||
|
||||
_netConnection = new NetConnection();
|
||||
_netConnection.proxyType = "best";
|
||||
_netConnection.client = this;
|
||||
_netConnection.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
|
||||
_netConnection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, netASyncError );
|
||||
_netConnection.addEventListener( SecurityErrorEvent.SECURITY_ERROR, netSecurityError );
|
||||
_netConnection.addEventListener( IOErrorEvent.IO_ERROR, netIOError );
|
||||
}
|
||||
|
||||
public function setUri(uri:String):void {
|
||||
_applicationURI = uri;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function get connection():NetConnection {
|
||||
return _netConnection;
|
||||
}
|
||||
@ -136,9 +113,11 @@ package org.bigbluebutton.main.model.users
|
||||
}
|
||||
|
||||
private function validateToken():void {
|
||||
var confParams:ConferenceParameters = BBB.initUserConfigManager().getConfParams();
|
||||
|
||||
var message:Object = new Object();
|
||||
message["userId"] = _conferenceParameters.internalUserID;
|
||||
message["authToken"] = _conferenceParameters.authToken;
|
||||
message["userId"] = confParams.internalUserID;
|
||||
message["authToken"] = confParams.authToken;
|
||||
|
||||
_validateTokenTimer = new Timer(7000, 1);
|
||||
_validateTokenTimer.addEventListener(TimerEvent.TIMER, validataTokenTimerHandler);
|
||||
@ -265,23 +244,39 @@ package org.bigbluebutton.main.model.users
|
||||
* 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;
|
||||
public function connect():void {
|
||||
var confParams:ConferenceParameters = BBB.initUserConfigManager().getConfParams();
|
||||
|
||||
tried_tunneling = tunnel;
|
||||
_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 );
|
||||
|
||||
try {
|
||||
var uri:String = _applicationURI + "/" + _conferenceParameters.room;
|
||||
var appURL:String = BBB.getConfigManager().config.application.uri;
|
||||
var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/;
|
||||
var result:Array = pattern.exec(appURL);
|
||||
|
||||
var protocol:String = "rtmp";
|
||||
var uri:String = appURL + "/" + confParams.room;
|
||||
|
||||
if (BBB.initConnectionManager().isTunnelling) {
|
||||
uri = "rtmpt://" + result.server + "/" + result.app + "/" + confParams.room;
|
||||
} else {
|
||||
uri = "rtmp://" + result.server + ":1935/" + result.app + "/" + confParams.room;
|
||||
}
|
||||
|
||||
|
||||
if (tunnel) {
|
||||
uri = uri.replace(/rtmp:/gi, "rtmpt:");
|
||||
}
|
||||
LOGGER.debug("BBB Apps URI=" + uri);
|
||||
|
||||
_netConnection.connect(uri, _conferenceParameters.username, _conferenceParameters.role,
|
||||
_conferenceParameters.room, _conferenceParameters.voicebridge,
|
||||
_conferenceParameters.record, _conferenceParameters.externUserID,
|
||||
_conferenceParameters.internalUserID, _conferenceParameters.muteOnStart, _conferenceParameters.lockSettings);
|
||||
_netConnection.connect(uri, confParams.username, confParams.role,
|
||||
confParams.room, confParams.voicebridge,
|
||||
confParams.record, confParams.externUserID,
|
||||
confParams.internalUserID, confParams.muteOnStart, confParams.lockSettings);
|
||||
|
||||
} catch(e:ArgumentError) {
|
||||
// Invalid parameters.
|
||||
switch (e.errorID) {
|
||||
@ -309,17 +304,6 @@ package org.bigbluebutton.main.model.users
|
||||
handleResult( event );
|
||||
}
|
||||
|
||||
private var _bwMon:BandwidthMonitor = new BandwidthMonitor();
|
||||
|
||||
private function startMonitoringBandwidth():void {
|
||||
LOGGER.debug("Start monitoring bandwidth.");
|
||||
var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/;
|
||||
var result:Array = pattern.exec(_applicationURI);
|
||||
_bwMon.serverURL = result.server;
|
||||
_bwMon.serverApplication = "video";
|
||||
_bwMon.start();
|
||||
}
|
||||
|
||||
public function handleResult(event:Object):void {
|
||||
var info : Object = event.info;
|
||||
var statusCode : String = info.code;
|
||||
@ -331,29 +315,18 @@ package org.bigbluebutton.main.model.users
|
||||
case "NetConnection.Connect.Success":
|
||||
numNetworkChangeCount = 0;
|
||||
JSLog.debug("Successfully connected to BBB App.", logData);
|
||||
|
||||
validateToken();
|
||||
|
||||
break;
|
||||
|
||||
case "NetConnection.Connect.Failed":
|
||||
if (tried_tunneling) {
|
||||
LOGGER.error(":Connection to viewers application failed...even when tunneling");
|
||||
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED);
|
||||
} else {
|
||||
disconnect(false);
|
||||
LOGGER.error(":Connection to viewers application failed...try tunneling");
|
||||
var rtmptRetryTimer:Timer = new Timer(1000, 1);
|
||||
rtmptRetryTimer.addEventListener("timer", rtmptRetryTimerHandler);
|
||||
rtmptRetryTimer.start();
|
||||
}
|
||||
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_FAILED);
|
||||
break;
|
||||
|
||||
case "NetConnection.Connect.Closed":
|
||||
logData.message = "NetConnection.Connect.Closed on bbb-apps";
|
||||
LOGGER.info(JSON.stringify(logData));
|
||||
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_CLOSED);
|
||||
|
||||
break;
|
||||
|
||||
case "NetConnection.Connect.InvalidApp":
|
||||
@ -367,7 +340,8 @@ package org.bigbluebutton.main.model.users
|
||||
break;
|
||||
|
||||
case "NetConnection.Connect.Rejected":
|
||||
LOGGER.debug(":Connection to the server rejected. Uri: {0}. Check if the red5 specified in the uri exists and is running", [_applicationURI]);
|
||||
var appURL:String = BBB.getConfigManager().config.application.uri
|
||||
LOGGER.debug(":Connection to the server rejected. Uri: {0}. Check if the red5 specified in the uri exists and is running", [appURL]);
|
||||
sendConnectionFailedEvent(ConnectionFailedEvent.CONNECTION_REJECTED);
|
||||
break;
|
||||
|
||||
@ -386,11 +360,6 @@ package org.bigbluebutton.main.model.users
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function rtmptRetryTimerHandler(event:TimerEvent):void {
|
||||
LOGGER.debug("rtmptRetryTimerHandler: {0}", [event]);
|
||||
connect(_conferenceParameters, true);
|
||||
}
|
||||
|
||||
protected function netSecurityError(event: SecurityErrorEvent):void {
|
||||
LOGGER.error("Security error - {0}", [event.text]);
|
||||
@ -435,9 +404,10 @@ package org.bigbluebutton.main.model.users
|
||||
var disconnectedEvent:BBBEvent = new BBBEvent(BBBEvent.RECONNECT_DISCONNECTED_EVENT);
|
||||
disconnectedEvent.payload.type = ReconnectionManager.BIGBLUEBUTTON_CONNECTION;
|
||||
disconnectedEvent.payload.callback = connect;
|
||||
disconnectedEvent.payload.callbackParameters = new Array(_conferenceParameters, tried_tunneling);
|
||||
disconnectedEvent.payload.callbackParameters = new Array();
|
||||
dispatcher.dispatchEvent(disconnectedEvent);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (UsersUtil.isUserEjected()) {
|
||||
logData.user = UsersUtil.getUserData();
|
||||
|
@ -18,11 +18,9 @@
|
||||
*/
|
||||
package org.bigbluebutton.main.model.users
|
||||
{
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
import flash.external.ExternalInterface;
|
||||
import flash.net.NetConnection;
|
||||
|
||||
import flash.net.NetConnection;
|
||||
import mx.collections.ArrayCollection;
|
||||
|
||||
import org.as3commons.logging.api.ILogger;
|
||||
@ -67,14 +65,10 @@ package org.bigbluebutton.main.model.users
|
||||
dispatcher = new Dispatcher();
|
||||
}
|
||||
|
||||
public function startService(e:UserServicesEvent):void {
|
||||
applicationURI = e.applicationURI;
|
||||
hostURI = e.hostURI;
|
||||
BBB.initConnectionManager().isTunnelling = e.isTunnelling;
|
||||
|
||||
public function startService(e:UserServicesEvent):void {
|
||||
joinService = new JoinService();
|
||||
joinService.addJoinResultListener(joinListener);
|
||||
joinService.load(e.hostURI);
|
||||
joinService.load(BBB.getConfigManager().config.application.host);
|
||||
}
|
||||
|
||||
private function joinListener(success:Boolean, result:Object):void {
|
||||
@ -110,7 +104,7 @@ package org.bigbluebutton.main.model.users
|
||||
_conferenceParameters.username = result.username;
|
||||
_conferenceParameters.role = result.role;
|
||||
_conferenceParameters.room = result.room;
|
||||
_conferenceParameters.authToken = result.authToken;
|
||||
_conferenceParameters.authToken = result.authToken;
|
||||
_conferenceParameters.webvoiceconf = result.webvoiceconf;
|
||||
_conferenceParameters.voicebridge = result.voicebridge;
|
||||
_conferenceParameters.welcome = result.welcome;
|
||||
@ -148,8 +142,7 @@ package org.bigbluebutton.main.model.users
|
||||
|
||||
private function connect():void{
|
||||
_connectionManager = BBB.initConnectionManager();
|
||||
_connectionManager.setUri(applicationURI);
|
||||
_connectionManager.connect(_conferenceParameters);
|
||||
_connectionManager.connect();
|
||||
}
|
||||
|
||||
public function logoutUser():void {
|
||||
|
@ -150,7 +150,7 @@ package org.bigbluebutton.modules.screenshare.managers {
|
||||
var option:ScreenshareOptions = new ScreenshareOptions();
|
||||
option.parseOptions();
|
||||
|
||||
if (option.useWebRTCIfAvailable && !BrowserCheck.isWebRTCSupported()) {
|
||||
if (option.tryWebRTCFirst && !BrowserCheck.isWebRTCSupported()) {
|
||||
usingJava = true;
|
||||
publishWindowManager.startSharing(module.getCaptureServerUri(), module.getRoom());
|
||||
sharing = true;
|
||||
|
@ -169,7 +169,7 @@ package org.bigbluebutton.modules.screenshare.managers
|
||||
var options:ScreenshareOptions = new ScreenshareOptions();
|
||||
options.parseOptions();
|
||||
|
||||
if (options.useWebRTCIfAvailable && BrowserCheck.isWebRTCSupported()) {
|
||||
if (options.tryWebRTCFirst && BrowserCheck.isWebRTCSupported()) {
|
||||
JSLog.warn("WebRTCDeskshareManager::handleStartSharingEvent WebRTC Supported", {});
|
||||
if (BrowserCheck.isFirefox()) {
|
||||
onSuccess("Firefox, lets try");
|
||||
|
@ -24,7 +24,7 @@ package org.bigbluebutton.modules.screenshare.model
|
||||
{
|
||||
[Bindable] public var showButton:Boolean = true;
|
||||
[Bindable] public var baseTabIndex:int;
|
||||
[Bindable] public var useWebRTCIfAvailable:Boolean = false;
|
||||
[Bindable] public var tryWebRTCFirst:Boolean = false;
|
||||
[Bindable] public var chromeExtensionKey:String = null;
|
||||
[Bindable] public var helpUrl:String;
|
||||
|
||||
@ -40,8 +40,8 @@ package org.bigbluebutton.modules.screenshare.model
|
||||
if (vxml.@showButton != undefined){
|
||||
showButton = (vxml.@showButton.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
if (vxml.@useWebRTCIfAvailable != undefined) {
|
||||
useWebRTCIfAvailable = (vxml.@useWebRTCIfAvailable.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
if (vxml.@tryWebRTCFirst != undefined) {
|
||||
tryWebRTCFirst = (vxml.@tryWebRTCFirst.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
if (vxml.@chromeExtensionKey != undefined) {
|
||||
chromeExtensionKey = vxml.@chromeExtensionKey.toString();
|
||||
|
@ -490,6 +490,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
info += "Chrome";
|
||||
}
|
||||
} else if (os == "Linux") {
|
||||
shareTypeBox.visible = true;
|
||||
info = "Linux";
|
||||
if (browser == "Firefox") {
|
||||
info += "Firefox";
|
||||
|
@ -174,10 +174,6 @@ package org.bigbluebutton.modules.users.services
|
||||
}
|
||||
|
||||
private function handleDeskShareRTMPBroadcastNotification(msg:Object):void {
|
||||
LOGGER.debug("*** handleDeskShareRTMPBroadcastNotification **** \n", [msg]);
|
||||
LOGGER.debug("*** handleDeskShareRTMPBroadcastNotification **** url=", msg.rtmpUrl);
|
||||
LOGGER.debug("*** handleDeskShareRTMPBroadcastNotification **** broadcasting=", msg.broadcasting);
|
||||
|
||||
var event:WebRTCViewStreamEvent;
|
||||
if (msg.broadcasting) {
|
||||
event = new WebRTCViewStreamEvent(WebRTCViewStreamEvent.START);
|
||||
|
@ -46,13 +46,16 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
private var roomsProvider:Array;
|
||||
|
||||
private var dispatcher:Dispatcher;
|
||||
|
||||
|
||||
[Bindable]
|
||||
private var mode:String;
|
||||
|
||||
private static var assignement : Dictionary;
|
||||
|
||||
private function onCloseClicked():void {
|
||||
PopUpManager.removePopUp(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a BreakoutRoomEvent to start creating breakout rooms
|
||||
*/
|
||||
@ -70,7 +73,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
room.name =
|
||||
UserManager.getInstance().getConference().meetingName + " (" +
|
||||
ResourceUtil.getInstance().getString('bbb.users.breakout.room')
|
||||
+ " - " + (i + 1).toString() + ")";;
|
||||
+ " - " + (i + 1).toString() + ")";
|
||||
for (var j:int = 0; j < users.length; j++) {
|
||||
room.users.push(users[j].userID);
|
||||
}
|
||||
@ -80,6 +83,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
event.durationInMinutes = durationStepper.value;
|
||||
event.record = recordCheckbox.selected;
|
||||
dispatcher.dispatchEvent(event);
|
||||
storeAssignement();
|
||||
PopUpManager.removePopUp(this);
|
||||
} else {
|
||||
Alert.show(ResourceUtil.getInstance().getString('bbb.users.breakout.insufficientUsers'));
|
||||
@ -149,6 +153,33 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
startButton.enabled = true;
|
||||
}
|
||||
|
||||
protected function loadAssignement():void {
|
||||
var rooms:int = assignement['rooms'];
|
||||
roomsCombo.selectedIndex = assignement['rooms'] - 2;
|
||||
recordCheckbox.selected = assignement['record'];
|
||||
durationStepper.value = assignement['duration'];
|
||||
var user:BBBUser;
|
||||
var currentUsers:ArrayCollection = UserManager.getInstance().getConference().users;
|
||||
// Create breakout rooms boxes
|
||||
for (var r:int = 1; r <= rooms; r++) {
|
||||
var list:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
list.users = new ArrayCollection();
|
||||
list.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r).toString();
|
||||
}
|
||||
// Load user assignment from the previous action
|
||||
var unassignedUsers:ArrayCollection = new ArrayCollection();
|
||||
for (var u:int = 0; u < currentUsers.length; u++) {
|
||||
user = currentUsers.getItemAt(u) as BBBUser;
|
||||
if (assignement.hasOwnProperty(user.userID)) {
|
||||
BreakoutList(roomsContainer.getChildAt(assignement[user.userID])).users.addItem(BBBUser.copy(user));
|
||||
} else {
|
||||
unassignedUsers.addItem(BBBUser.copy(user));
|
||||
}
|
||||
}
|
||||
// Create not assigned users list
|
||||
createUnassignedList(unassignedUsers);
|
||||
}
|
||||
|
||||
protected function assignUsersForInvitation():void {
|
||||
var originalUsers:ArrayCollection = UserManager.getInstance().getConference().users;
|
||||
var users:ArrayCollection = new ArrayCollection();
|
||||
@ -177,12 +208,31 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
unassignedUsers.addItem(user);
|
||||
}
|
||||
}
|
||||
var unassignedList:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
unassignedList.users = unassignedUsers;
|
||||
unassignedList.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.notAssigned');
|
||||
unassignedList.notAssignedUsers = true;
|
||||
createUnassignedList(unassignedUsers);
|
||||
startButton.enabled = true;
|
||||
}
|
||||
|
||||
private function createUnassignedList(dataProvider:ArrayCollection):void {
|
||||
var unassignedList:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
unassignedList.users = dataProvider;
|
||||
unassignedList.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.notAssigned');
|
||||
unassignedList.notAssignedUsers = true;
|
||||
}
|
||||
|
||||
private function storeAssignement():void {
|
||||
assignement = new Dictionary(true);
|
||||
assignement['rooms'] = roomsCombo.selectedIndex + 2;
|
||||
assignement['record'] = recordCheckbox.selected;
|
||||
assignement['duration'] = durationStepper.value;
|
||||
for (var r:int = 0; r < assignement['rooms']; r++) {
|
||||
var users:ArrayCollection = BreakoutList(roomsContainer.getChildAt(r)).users;
|
||||
for (var u:int = 0; u < users.length; u++) {
|
||||
// We store pairs { userID : roomNumber } to be easier to check later
|
||||
assignement[BBBUser(users[u]).userID] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function initCreateBreakoutRooms(mode:String):void {
|
||||
dispatcher = new Dispatcher();
|
||||
roomsProvider = new Array();
|
||||
@ -194,7 +244,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
if (this.mode == "create") {
|
||||
startButton.label = ResourceUtil.getInstance().getString('bbb.users.breakout.start');
|
||||
textArea.text = ResourceUtil.getInstance().getString('bbb.users.breakout.breakoutRooms');
|
||||
assignUsersForCreation();
|
||||
if (assignement == null) {
|
||||
assignUsersForCreation();
|
||||
} else {
|
||||
loadAssignement();
|
||||
}
|
||||
visible = true;
|
||||
} else {
|
||||
startButton.label = ResourceUtil.getInstance().getString('bbb.users.breakout.invite');
|
||||
|
@ -77,22 +77,23 @@ package org.bigbluebutton.util.logging
|
||||
|
||||
public function log(name:String, shortName:String, level:int, timeStamp:Number, message:*, parameters:Array, person:String):void
|
||||
{
|
||||
var userId:String = UsersUtil.getMyUserID();
|
||||
var meetingId:String = UsersUtil.getInternalMeetingID();
|
||||
name = userId + " " + meetingId + " " + name;
|
||||
var formattedMessage:String=_formatter.format(name, shortName, level, timeStamp, message, parameters, person);
|
||||
|
||||
// check if contains info from config field
|
||||
var reg1:RegExp = new RegExp(_logPattern, "g");
|
||||
if(reg1.test(formattedMessage)) { // only log messages of the specified pattern
|
||||
if (UsersUtil.getInternalMeetingID() != null && UsersUtil.getMyUserID() != "UNKNOWN USER") {
|
||||
var arr:Array = new Array ();
|
||||
arr.push({"name":name, "shortName":shortName, "level":level, "timeStamp":timeStamp, "message":formattedMessage, "parameters":parameters, "person":person});
|
||||
if (meetingId != null && userId != "UNKNOWN USER") {
|
||||
|
||||
// We will always recycle the URLRequest instance and use it to send logging HTTP requests
|
||||
if (!_request)
|
||||
{
|
||||
_request=new URLRequest(_serverUri + "/" + CLIENT + "/" + UsersUtil.getInternalMeetingID() + "/" + UsersUtil.getMyUserID());
|
||||
_request=new URLRequest(_serverUri + "/" + CLIENT + "/" + meetingId + "/" + userId);
|
||||
_request.method=URLRequestMethod.POST;
|
||||
}
|
||||
var JsonObj:String = JSON.stringify(arr);
|
||||
var JsonObj:String = JSON.stringify(formattedMessage);
|
||||
_request.contentType = "application/json";
|
||||
|
||||
_request.data=JsonObj;
|
||||
|
@ -280,14 +280,13 @@ uncomment () {
|
||||
stop_bigbluebutton () {
|
||||
echo "Stopping BigBlueButton"
|
||||
if command -v systemctl >/dev/null; then
|
||||
systemctl stop red5 tomcat7 nginx freeswitch bbb-apps-akka bbb-fsesl-akka bbb-record-core.service bbb-record-core.timer
|
||||
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
|
||||
HTML5=bbb-html5
|
||||
fi
|
||||
systemctl stop red5 tomcat7 nginx freeswitch bbb-apps-akka bbb-fsesl-akka bbb-record-core.service bbb-record-core.timer $HTML5
|
||||
else
|
||||
/etc/init.d/monit stop
|
||||
|
||||
if [ -f /etc/init/bbb-html5.conf ]; then
|
||||
stop bbb-html5
|
||||
fi
|
||||
|
||||
/etc/init.d/$RED5 stop
|
||||
/etc/init.d/${SERVLET_CONTAINER} stop
|
||||
/etc/init.d/nginx stop
|
||||
@ -323,7 +322,10 @@ stop_bigbluebutton () {
|
||||
start_bigbluebutton () {
|
||||
echo "Starting BigBlueButton"
|
||||
if command -v systemctl >/dev/null; then
|
||||
systemctl start red5 tomcat7 nginx freeswitch bbb-apps-akka bbb-fsesl-akka bbb-record-core.timer
|
||||
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
|
||||
HTML5=bbb-html5
|
||||
fi
|
||||
systemctl start red5 tomcat7 nginx freeswitch bbb-apps-akka bbb-fsesl-akka bbb-record-core.timer $HTML5
|
||||
else
|
||||
$FREESWITCH_INIT_D start
|
||||
|
||||
|
@ -602,8 +602,9 @@ fi
|
||||
done
|
||||
echo
|
||||
echo "--"
|
||||
ps fU $SERVLET_CONTAINER -o "%c%a" | grep -v COMMAND | grep -v logging.properties
|
||||
|
||||
systemctl --all --no-pager list-timers bbb-record-core.timer
|
||||
echo "--"
|
||||
systemctl --no-pager status bbb-record-core.service
|
||||
echo "--"
|
||||
|
||||
if tail -n 20 /var/log/bigbluebutton/bbb-web.log | grep -q "is recorded. Process it."; then
|
||||
|
@ -9,6 +9,7 @@ import NotificationsBarContainer from '../notifications-bar/container';
|
||||
|
||||
import Button from '../button/component';
|
||||
import styles from './styles';
|
||||
import cx from 'classnames';
|
||||
|
||||
const propTypes = {
|
||||
navbar: PropTypes.element,
|
||||
@ -21,6 +22,14 @@ const propTypes = {
|
||||
};
|
||||
|
||||
export default class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
compactUserList: false, //TODO: Change this on userlist resize (?)
|
||||
};
|
||||
}
|
||||
|
||||
renderNavBar() {
|
||||
const { navbar } = this.props;
|
||||
|
||||
@ -50,11 +59,18 @@ export default class App extends Component {
|
||||
}
|
||||
|
||||
renderUserList() {
|
||||
const { userList } = this.props;
|
||||
let { userList } = this.props;
|
||||
const { compactUserList } = this.state;
|
||||
|
||||
let userListStyle = {};
|
||||
userListStyle[styles.compact] = compactUserList;
|
||||
if (userList) {
|
||||
userList = React.cloneElement(userList, {
|
||||
compact: compactUserList,
|
||||
});
|
||||
|
||||
return (
|
||||
<nav className={styles.userList}>
|
||||
<nav className={cx(styles.userList, userListStyle)}>
|
||||
{userList}
|
||||
</nav>
|
||||
);
|
||||
|
@ -101,6 +101,10 @@ const showModal = (val) => {
|
||||
}
|
||||
};
|
||||
|
||||
const clearModal = () => {
|
||||
showModal(null);
|
||||
};
|
||||
|
||||
export {
|
||||
subscribeForData,
|
||||
setCredentials,
|
||||
@ -110,4 +114,5 @@ export {
|
||||
redirectToLogoutUrl,
|
||||
getModal,
|
||||
showModal,
|
||||
clearModal,
|
||||
};
|
||||
|
@ -97,11 +97,7 @@ $actionsbar-height: 50px; // TODO: Change to ActionsBar real height
|
||||
.userList {
|
||||
@extend %full-page;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
|
||||
@include mq($small-only) {
|
||||
padding-top: $navbar-height;
|
||||
}
|
||||
overflow: visible;
|
||||
|
||||
@include mq($small-only) {
|
||||
padding-top: $navbar-height;
|
||||
@ -117,11 +113,15 @@ $actionsbar-height: 50px; // TODO: Change to ActionsBar real height
|
||||
}
|
||||
}
|
||||
|
||||
.compact {
|
||||
flex-basis: 4.6rem;
|
||||
}
|
||||
|
||||
.chat {
|
||||
@extend %full-page;
|
||||
z-index: 3;
|
||||
|
||||
@include mq($small-only) {
|
||||
z-index: 3;
|
||||
padding-top: $navbar-height;
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,8 @@
|
||||
|
||||
.closeChat {
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
margin-bottom: $line-height-computed;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { findDOMNode } from 'react-dom';
|
||||
import styles from './styles';
|
||||
import DropdownTrigger from './trigger/component';
|
||||
import DropdownContent from './content/component';
|
||||
import cx from 'classnames';
|
||||
|
||||
const FOCUSABLE_CHILDREN = `[tabindex]:not([tabindex="-1"]), a, input, button`;
|
||||
|
||||
@ -48,19 +49,37 @@ export default class Dropdown extends Component {
|
||||
this.state = { isOpen: false, };
|
||||
this.handleShow = this.handleShow.bind(this);
|
||||
this.handleHide = this.handleHide.bind(this);
|
||||
this.handleStateCallback = this.handleStateCallback.bind(this);
|
||||
this.handleToggle = this.handleToggle.bind(this);
|
||||
this.handleWindowClick = this.handleWindowClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.isOpen !== this.props.isOpen
|
||||
&& this.state.isOpen !== this.props.isOpen) {
|
||||
this.setState({ isOpen: this.props.isOpen }, this.handleStateCallback);
|
||||
}
|
||||
}
|
||||
|
||||
handleStateCallback() {
|
||||
const { onShow, onHide } = this.props;
|
||||
|
||||
if (this.state.isOpen && onShow) {
|
||||
onShow();
|
||||
} else if (onHide) {
|
||||
onHide();
|
||||
}
|
||||
}
|
||||
|
||||
handleShow() {
|
||||
this.setState({ isOpen: true });
|
||||
this.setState({ isOpen: true }, this.handleStateCallback);
|
||||
|
||||
const contentElement = findDOMNode(this.refs.content);
|
||||
contentElement.querySelector(FOCUSABLE_CHILDREN).focus();
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
this.setState({ isOpen: false });
|
||||
this.setState({ isOpen: false }, this.handleStateCallback);
|
||||
const triggerElement = findDOMNode(this.refs.trigger);
|
||||
triggerElement.focus();
|
||||
}
|
||||
@ -93,7 +112,7 @@ export default class Dropdown extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { children, className, style } = this.props;
|
||||
|
||||
let trigger = children.find(x => x.type === DropdownTrigger);
|
||||
let content = children.find(x => x.type === DropdownContent);
|
||||
@ -114,7 +133,7 @@ export default class Dropdown extends Component {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.dropdown}>
|
||||
<div style={style} className={cx(styles.dropdown, className)}>
|
||||
{trigger}
|
||||
{content}
|
||||
</div>
|
||||
|
@ -23,12 +23,8 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
export default class DropdownContent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { placement, className, children } = this.props;
|
||||
const { placement, className, children, style } = this.props;
|
||||
const { dropdownToggle, dropdownShow, dropdownHide } = this.props;
|
||||
|
||||
let placementName = placement.split(' ').join('-');
|
||||
@ -41,6 +37,7 @@ export default class DropdownContent extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
aria-expanded={this.props['aria-expanded']}
|
||||
className={cx(styles.content, styles[placementName], className)}>
|
||||
{boundChildren}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component, PropTypes, Children, cloneElement } from 'react';
|
||||
import styles from './styles';
|
||||
import cx from 'classnames';
|
||||
|
||||
import KEY_CODES from '/imports/utils/keyCodes';
|
||||
|
||||
@ -84,6 +85,7 @@ export default class DropdownList extends Component {
|
||||
const { dropdownHide } = this.props;
|
||||
|
||||
this.setState({ activeItemIndex: null });
|
||||
|
||||
dropdownHide();
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
@ -92,7 +94,9 @@ export default class DropdownList extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const boundChildren = Children.map(this.props.children,
|
||||
const { children, style, className } = this.props;
|
||||
|
||||
const boundChildren = Children.map(children,
|
||||
(item, i) => {
|
||||
if (item.type === ListSeparator) {
|
||||
return item;
|
||||
@ -122,7 +126,7 @@ export default class DropdownList extends Component {
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className={styles.list} role="menu">
|
||||
<ul style={style} className={cx(styles.list, className)} role="menu">
|
||||
{boundChildren}
|
||||
</ul>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import styles from '../styles';
|
||||
import _ from 'underscore';
|
||||
import cx from 'classnames';
|
||||
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
|
||||
@ -22,14 +23,15 @@ export default class DropdownListItem extends Component {
|
||||
const { icon, label } = this.props;
|
||||
|
||||
return [
|
||||
(<Icon iconName={icon} key="icon" className={styles.itemIcon}/>),
|
||||
(icon ? <Icon iconName={icon} key="icon" className={styles.itemIcon}/> : null),
|
||||
(<span className={styles.itemLabel} key="label">{label}</span>),
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { label, description, children,
|
||||
injectRef, tabIndex, onClick, onKeyDown, } = this.props;
|
||||
injectRef, tabIndex, onClick, onKeyDown,
|
||||
className, style, } = this.props;
|
||||
|
||||
return (
|
||||
<li
|
||||
@ -39,7 +41,8 @@ export default class DropdownListItem extends Component {
|
||||
tabIndex={tabIndex}
|
||||
aria-labelledby={this.labelID}
|
||||
aria-describedby={this.descID}
|
||||
className={styles.item}
|
||||
className={cx(styles.item, className)}
|
||||
style={style}
|
||||
role="menuitem">
|
||||
{
|
||||
children ? children
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import styles from '../styles';
|
||||
import cx from 'classnames';
|
||||
|
||||
export default class DropdownListSeparator extends Component {
|
||||
render() {
|
||||
return <li className={styles.separator} role="separator" />;
|
||||
const { style, className } = this.props;
|
||||
return <li style={style} className={cx(styles.separator, className)} role="separator" />;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
$dropdown-bg: $color-white;
|
||||
$dropdown-color: $color-text;
|
||||
$caret-shadow-color: $color-gray;
|
||||
|
||||
$dropdown-caret-width: 12px;
|
||||
$dropdown-caret-height: 8px;
|
||||
@ -20,7 +21,7 @@ $dropdown-caret-height: 8px;
|
||||
// min-width: 150px;
|
||||
z-index: 1000;
|
||||
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
@ -49,13 +50,17 @@ $dropdown-caret-height: 8px;
|
||||
transform: translateX(-50%);
|
||||
margin-bottom: $dropdown-caret-height * 1.25;
|
||||
|
||||
&:after {
|
||||
&:before, &:after {
|
||||
border-left: $dropdown-caret-width solid transparent;
|
||||
border-right: $dropdown-caret-width solid transparent;
|
||||
border-top: $dropdown-caret-height solid $dropdown-bg;
|
||||
bottom: 0;
|
||||
margin-bottom: -($dropdown-caret-height);
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-top: $dropdown-caret-height solid $caret-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
%up-caret {
|
||||
@ -64,13 +69,17 @@ $dropdown-caret-height: 8px;
|
||||
transform: translateX(-50%);
|
||||
margin-top: $dropdown-caret-height * 1.25;
|
||||
|
||||
&:after {
|
||||
&:before, &:after {
|
||||
border-left: $dropdown-caret-width solid transparent;
|
||||
border-right: $dropdown-caret-width solid transparent;
|
||||
border-bottom: $dropdown-caret-height solid $dropdown-bg;
|
||||
margin-top: -($dropdown-caret-height);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-bottom: $dropdown-caret-height solid $caret-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
%right-caret {
|
||||
@ -78,7 +87,7 @@ $dropdown-caret-height: 8px;
|
||||
transform: translateX(-100%) translateY(-50%);
|
||||
left: -($dropdown-caret-height * 1.25);
|
||||
|
||||
&:after {
|
||||
&:before, &:after{
|
||||
border-top: $dropdown-caret-width solid transparent;
|
||||
border-bottom: $dropdown-caret-width solid transparent;
|
||||
border-left: $dropdown-caret-height solid $dropdown-bg;
|
||||
@ -86,6 +95,10 @@ $dropdown-caret-height: 8px;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-left: $dropdown-caret-height solid $caret-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
%left-caret {
|
||||
@ -93,7 +106,7 @@ $dropdown-caret-height: 8px;
|
||||
transform: translateX(100%) translateY(-50%);
|
||||
right: -($dropdown-caret-height * 1.25);
|
||||
|
||||
&:after {
|
||||
&:before, &:after {
|
||||
border-top: $dropdown-caret-width solid transparent;
|
||||
border-bottom: $dropdown-caret-width solid transparent;
|
||||
border-right: $dropdown-caret-height solid $dropdown-bg;
|
||||
@ -101,10 +114,14 @@ $dropdown-caret-height: 8px;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-right: $dropdown-caret-height solid $caret-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
%horz-center-caret {
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
margin-left: -($dropdown-caret-width);
|
||||
}
|
||||
}
|
||||
@ -113,7 +130,7 @@ $dropdown-caret-height: 8px;
|
||||
transform: translateX(-100%);
|
||||
left: 100%;
|
||||
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
right: $dropdown-caret-width / 2;
|
||||
}
|
||||
}
|
||||
@ -123,13 +140,13 @@ $dropdown-caret-height: 8px;
|
||||
right: 100%;
|
||||
left: auto;
|
||||
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
left: $dropdown-caret-width / 2;
|
||||
}
|
||||
}
|
||||
|
||||
%vert-center-caret {
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
margin-top: -($dropdown-caret-width);
|
||||
}
|
||||
}
|
||||
@ -137,7 +154,7 @@ $dropdown-caret-height: 8px;
|
||||
%vert-top-caret {
|
||||
top: 0;
|
||||
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
top: 0;
|
||||
margin-top: $dropdown-caret-width / 2;
|
||||
}
|
||||
@ -147,7 +164,7 @@ $dropdown-caret-height: 8px;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
|
||||
&:after {
|
||||
&:after, &:before {
|
||||
top: auto;
|
||||
bottom: $dropdown-caret-width / 2;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import cx from 'classnames';
|
||||
|
||||
import KEY_CODES from '/imports/utils/keyCodes';
|
||||
|
||||
@ -20,7 +21,7 @@ export default class DropdownTrigger extends Component {
|
||||
}
|
||||
|
||||
handleKeyDown(event) {
|
||||
const { dropdownShow, dropdownHide } = this.props;
|
||||
const { dropdownShow, dropdownHide, } = this.props;
|
||||
|
||||
if ([KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.which)) {
|
||||
event.preventDefault();
|
||||
@ -40,13 +41,16 @@ export default class DropdownTrigger extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { children, style, className, } = this.props;
|
||||
const TriggerComponent = React.Children.only(children);
|
||||
|
||||
const TriggerComponentBounded = React.cloneElement(children, {
|
||||
onClick: this.handleClick,
|
||||
onKeyDown: this.handleKeyDown,
|
||||
'aria-haspopup': true,
|
||||
tabIndex: '0',
|
||||
style: style,
|
||||
className: cx(children.props.className, className),
|
||||
});
|
||||
|
||||
return TriggerComponentBounded;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import ModalBase from './base/component';
|
||||
import Button from '../button/component';
|
||||
import styles from './styles.scss';
|
||||
@ -45,6 +46,7 @@ export default class Modal extends Component {
|
||||
|
||||
handleDismiss() {
|
||||
this.setState({ isOpen: false });
|
||||
clearModal();
|
||||
}
|
||||
|
||||
handleConfirm() {
|
||||
|
@ -25,6 +25,7 @@ class ChatListItem extends Component {
|
||||
const {
|
||||
chat,
|
||||
openChat,
|
||||
compact,
|
||||
} = this.props;
|
||||
|
||||
const linkPath = [PRIVATE_CHAT_PATH, chat.id].join('');
|
||||
@ -33,11 +34,11 @@ class ChatListItem extends Component {
|
||||
linkClasses[styles.active] = chat.id === openChat;
|
||||
|
||||
return (
|
||||
<li className={cx(styles.chatListItem, linkClasses)} {...this.props}>
|
||||
<li className={cx(styles.chatListItem, linkClasses)}>
|
||||
<Link to={linkPath} className={styles.chatListItemLink}>
|
||||
{chat.icon ? this.renderChatIcon() : this.renderChatAvatar()}
|
||||
<div className={styles.chatName}>
|
||||
<h3 className={styles.chatNameMain}>{chat.name}</h3>
|
||||
{!compact ? <h3 className={styles.chatNameMain}>{chat.name}</h3> : null }
|
||||
</div>
|
||||
{(chat.unreadCounter > 0) ?
|
||||
<div className={styles.unreadMessages}>
|
||||
|
@ -28,6 +28,9 @@ const listTransition = {
|
||||
class UserList extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
compact: this.props.compact,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -42,13 +45,16 @@ class UserList extends Component {
|
||||
renderHeader() {
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<h2 className={styles.headerTitle}>
|
||||
<FormattedMessage
|
||||
id="app.userlist.participantsTitle"
|
||||
description="Title for the Header"
|
||||
defaultMessage="Participants"
|
||||
/>
|
||||
</h2>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h2 className={styles.headerTitle}>
|
||||
<FormattedMessage
|
||||
id="app.userlist.participantsTitle"
|
||||
description="Title for the Header"
|
||||
defaultMessage="Participants"
|
||||
/>
|
||||
</h2> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -70,13 +76,16 @@ class UserList extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.messages}>
|
||||
<h3 className={styles.smallTitle}>
|
||||
<FormattedMessage
|
||||
id="app.userlist.messagesTitle"
|
||||
description="Title for the messages list"
|
||||
defaultMessage="Messages"
|
||||
/>
|
||||
</h3>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h3 className={styles.smallTitle}>
|
||||
<FormattedMessage
|
||||
id="app.userlist.messagesTitle"
|
||||
description="Title for the messages list"
|
||||
defaultMessage="Messages"
|
||||
/>
|
||||
</h3> : <hr className={styles.separator}></hr>
|
||||
}
|
||||
<div className={styles.scrollableList}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
@ -90,6 +99,7 @@ class UserList extends Component {
|
||||
className={cx(styles.chatsList, styles.scrollableList)}>
|
||||
{openChats.map(chat => (
|
||||
<ChatListItem
|
||||
compact={this.state.compact}
|
||||
key={chat.id}
|
||||
openChat={openChat}
|
||||
chat={chat} />
|
||||
@ -105,18 +115,22 @@ class UserList extends Component {
|
||||
users,
|
||||
currentUser,
|
||||
userActions,
|
||||
compact,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.participants}>
|
||||
<h3 className={styles.smallTitle}>
|
||||
<FormattedMessage
|
||||
id="app.userlist.participantsTitle"
|
||||
description="Title for the Participants list"
|
||||
defaultMessage="Participants"
|
||||
/>
|
||||
({this.props.users.length})
|
||||
</h3>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h3 className={styles.smallTitle}>
|
||||
<FormattedMessage
|
||||
id="app.userlist.participantsTitle"
|
||||
description="Title for the Participants list"
|
||||
defaultMessage="Participants"
|
||||
/>
|
||||
({users.length})
|
||||
</h3> : <hr className={styles.separator}></hr>
|
||||
}
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
transitionAppear={true}
|
||||
@ -127,8 +141,10 @@ class UserList extends Component {
|
||||
transitionLeaveTimeout={0}
|
||||
component="ul"
|
||||
className={cx(styles.participantsList, styles.scrollableList)}>
|
||||
{users.map(user => (
|
||||
{
|
||||
users.map(user => (
|
||||
<UserListItem
|
||||
compact={this.state.compact}
|
||||
key={user.id}
|
||||
user={user}
|
||||
currentUser={currentUser}
|
||||
|
@ -6,10 +6,25 @@ import UserList from './component.jsx';
|
||||
|
||||
class UserListContainer extends Component {
|
||||
render() {
|
||||
const {
|
||||
compact,
|
||||
users,
|
||||
currentUser,
|
||||
openChats,
|
||||
openChat,
|
||||
userActions,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<UserList
|
||||
{...this.props}>
|
||||
{this.props.children}
|
||||
compact={compact}
|
||||
users={users}
|
||||
currentUser={currentUser}
|
||||
openChats={openChats}
|
||||
openChat={openChat}
|
||||
userActions={userActions}>
|
||||
{children}
|
||||
</UserList>
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ $user-icons-color-hover: $color-gray;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0rem;
|
||||
margin-left: 0.7rem;
|
||||
margin-top: 0.9rem;
|
||||
margin-top: 0.3rem;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
transition: all 0.3s;
|
||||
@ -102,6 +102,7 @@ $user-icons-color-hover: $color-gray;
|
||||
.participantsList,
|
||||
.chatsList {
|
||||
@extend .lists;
|
||||
overflow-x: hidden;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
@ -130,6 +131,13 @@ $user-icons-color-hover: $color-gray;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 1rem auto;
|
||||
width: 2.2rem;
|
||||
border: 0;
|
||||
border-top: 1px solid $color-gray-lighter;
|
||||
}
|
||||
|
||||
.enter, .appear {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
@ -2,11 +2,19 @@ import React, { Component } from 'react';
|
||||
import UserAvatar from '/imports/ui/components/user-avatar/component';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import UserActions from './user-actions/component';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { withRouter } from 'react-router';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import styles from './styles.scss';
|
||||
import cx from 'classnames';
|
||||
import _ from 'underscore';
|
||||
|
||||
import Dropdown from '/imports/ui/components/dropdown/component';
|
||||
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
||||
import DropdownContent from '/imports/ui/components/dropdown/content/component';
|
||||
import DropdownList from '/imports/ui/components/dropdown/list/component';
|
||||
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
||||
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
|
||||
|
||||
const propTypes = {
|
||||
user: React.PropTypes.shape({
|
||||
@ -74,51 +82,148 @@ class UserListItem extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
visibleActions: false,
|
||||
isActionsOpen: false,
|
||||
};
|
||||
|
||||
this.handleToggleActions = this.handleToggleActions.bind(this);
|
||||
this.handleClickOutsideDropdown = this.handleClickOutsideDropdown.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.onActionsShow = this.onActionsShow.bind(this);
|
||||
this.onActionsHide = this.onActionsHide.bind(this);
|
||||
}
|
||||
|
||||
handleClickOutsideDropdown(e) {
|
||||
const node = findDOMNode(this);
|
||||
const shouldUpdateState = e.target !== node &&
|
||||
!node.contains(e.target) &&
|
||||
this.state.visibleActions;
|
||||
if (shouldUpdateState) {
|
||||
this.setState({ visibleActions: false });
|
||||
}
|
||||
handleScroll() {
|
||||
this.setState({
|
||||
isActionsOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleToggleActions() {
|
||||
this.setState({ visibleActions: !this.state.visibleActions });
|
||||
getAvailableActions() {
|
||||
const {
|
||||
currentUser,
|
||||
user,
|
||||
userActions,
|
||||
router,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
openChat,
|
||||
clearStatus,
|
||||
setPresenter,
|
||||
promote,
|
||||
kick,
|
||||
} = userActions;
|
||||
|
||||
return _.compact([
|
||||
(!user.isCurrent ? this.renderUserAction(openChat, router, user) : null),
|
||||
(currentUser.isModerator ? this.renderUserAction(clearStatus, user) : null),
|
||||
(currentUser.isModerator ? this.renderUserAction(setPresenter, user) : null),
|
||||
(currentUser.isModerator ? this.renderUserAction(promote, user) : null),
|
||||
(currentUser.isModerator ? this.renderUserAction(kick, user) : null),
|
||||
]);
|
||||
}
|
||||
|
||||
onActionsShow() {
|
||||
const dropdown = findDOMNode(this.refs.dropdown);
|
||||
this.setState({
|
||||
contentTop: `${dropdown.offsetTop - dropdown.parentElement.parentElement.scrollTop}px`,
|
||||
isActionsOpen: true,
|
||||
active: true,
|
||||
});
|
||||
|
||||
findDOMNode(this).parentElement.addEventListener('scroll', this.handleScroll, false);
|
||||
}
|
||||
|
||||
onActionsHide() {
|
||||
this.setState({
|
||||
active: false,
|
||||
isActionsOpen: false,
|
||||
});
|
||||
|
||||
findDOMNode(this).parentElement.removeEventListener('scroll', this.handleScroll, false);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
user,
|
||||
currentUser,
|
||||
userActions,
|
||||
compact,
|
||||
} = this.props;
|
||||
|
||||
let userItemContentsStyle = {};
|
||||
userItemContentsStyle[styles.userItemContentsCompact] = compact;
|
||||
userItemContentsStyle[styles.active] = this.state.active;
|
||||
|
||||
return (
|
||||
<li onClick={this.handleToggleActions.bind(this, user)}
|
||||
className={styles.userListItem} {...this.props}>
|
||||
<div className={styles.userItemContents}>
|
||||
<UserAvatar user={this.props.user}/>
|
||||
{this.renderUserName()}
|
||||
{this.renderUserIcons()}
|
||||
</div>
|
||||
{this.renderUserActions()}
|
||||
<li
|
||||
className={cx(styles.userListItem, userItemContentsStyle)}>
|
||||
{this.renderUserContents()}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
renderUserContents() {
|
||||
const {
|
||||
user,
|
||||
} = this.props;
|
||||
|
||||
let actions = this.getAvailableActions();
|
||||
let contents = (
|
||||
<div tabIndex={0} className={styles.userItemContents}>
|
||||
<UserAvatar user={user}/>
|
||||
{this.renderUserName()}
|
||||
{this.renderUserIcons()}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!actions.length) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isOpen={this.state.isActionsOpen}
|
||||
ref="dropdown"
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
className={styles.dropdown}>
|
||||
<DropdownTrigger>
|
||||
{contents}
|
||||
</DropdownTrigger>
|
||||
<DropdownContent
|
||||
style={{
|
||||
top: this.state.contentTop,
|
||||
}}
|
||||
className={styles.dropdownContent}
|
||||
placement="right top">
|
||||
|
||||
<DropdownList>
|
||||
{
|
||||
[
|
||||
(<DropdownListItem
|
||||
className={styles.actionsHeader}
|
||||
key={_.uniqueId('action-header')}
|
||||
label={user.name}
|
||||
defaultMessage={user.name}/>),
|
||||
(<DropdownListSeparator key={_.uniqueId('action-separator')} />),
|
||||
].concat(actions)
|
||||
}
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
renderUserName() {
|
||||
const {
|
||||
user,
|
||||
intl,
|
||||
compact,
|
||||
} = this.props;
|
||||
|
||||
if (compact) {
|
||||
return;
|
||||
}
|
||||
|
||||
let userNameSub = [];
|
||||
if (user.isPresenter) {
|
||||
userNameSub.push(intl.formatMessage(messages.presenter));
|
||||
@ -145,8 +250,13 @@ class UserListItem extends Component {
|
||||
renderUserIcons() {
|
||||
const {
|
||||
user,
|
||||
compact,
|
||||
} = this.props;
|
||||
|
||||
if (compact) {
|
||||
return;
|
||||
}
|
||||
|
||||
let audioChatIcon = null;
|
||||
if (user.isVoiceUser || user.isListenOnly) {
|
||||
if (user.isMuted) {
|
||||
@ -168,34 +278,22 @@ class UserListItem extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderUserActions() {
|
||||
renderUserAction(action, ...parameters) {
|
||||
const {
|
||||
user,
|
||||
currentUser,
|
||||
userActions,
|
||||
user,
|
||||
} = this.props;
|
||||
|
||||
let visibleActions = null;
|
||||
if (this.state.visibleActions) {
|
||||
visibleActions = <UserActions
|
||||
user={user}
|
||||
currentUser={currentUser}
|
||||
userActions={userActions}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={userActionsTransition}
|
||||
transitionAppear={true}
|
||||
transitionEnter={true}
|
||||
transitionLeave={true}
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
>
|
||||
{visibleActions}
|
||||
</ReactCSSTransitionGroup>
|
||||
const userAction = (
|
||||
<DropdownListItem key={_.uniqueId('action-item-')}
|
||||
icon={action.icon}
|
||||
label={action.label}
|
||||
defaultMessage={action.label}
|
||||
onClick={action.handler.bind(this, ...parameters)}
|
||||
/>
|
||||
);
|
||||
|
||||
return userAction;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,18 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: $list-item-bg-hover;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.userItemContentsCompact {
|
||||
}
|
||||
|
||||
.userName {
|
||||
@ -35,7 +47,6 @@
|
||||
line-height: 1rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
@ -74,7 +85,7 @@
|
||||
}
|
||||
|
||||
.userItemContents {
|
||||
flex-grow: 1;
|
||||
flex-grow: 0;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
@ -129,3 +140,20 @@
|
||||
transition: all 300ms;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.dropdownContent {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.actionsHeader {
|
||||
color: $color-gray;
|
||||
|
||||
&:hover {
|
||||
color: $color-gray !important;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import styles from './styles.scss';
|
||||
|
||||
const propTypes = {
|
||||
user: React.PropTypes.shape({
|
||||
id: React.PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
|
||||
currentUser: React.PropTypes.shape({
|
||||
isModerator: React.PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
|
||||
userActions: React.PropTypes.shape().isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
};
|
||||
|
||||
class UserActions extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
user,
|
||||
currentUser,
|
||||
router,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
openChat,
|
||||
clearStatus,
|
||||
setPresenter,
|
||||
promote,
|
||||
kick,
|
||||
} = this.props.userActions;
|
||||
|
||||
return (
|
||||
<div key={user.id} className={styles.userItemActions}>
|
||||
<ul className={styles.userActionsList}>
|
||||
|
||||
{!user.isCurrent ? this.renderUserAction(openChat, router, user) : null}
|
||||
{currentUser.isModerator ? this.renderUserAction(clearStatus, user) : null}
|
||||
{currentUser.isModerator ? this.renderUserAction(setPresenter, user) : null}
|
||||
{currentUser.isModerator ? this.renderUserAction(promote, user) : null}
|
||||
{currentUser.isModerator ? this.renderUserAction(kick, user) : null}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUserAction(action, ...parameters) {
|
||||
const currentUser = this.props.currentUser;
|
||||
const user = this.props.user;
|
||||
|
||||
const userAction = (
|
||||
<li onClick={action.handler.bind(this, ...parameters)}
|
||||
className={styles.userActionsItem}>
|
||||
<Icon iconName={action.icon} className={styles.actionIcon}/>
|
||||
<span className={styles.actionText}>
|
||||
{action.label}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
||||
return userAction;
|
||||
}
|
||||
}
|
||||
|
||||
UserActions.propTypes = propTypes;
|
||||
UserActions.defaultProps = defaultProps;
|
||||
|
||||
export default withRouter(UserActions);
|
@ -1,42 +0,0 @@
|
||||
@import '../styles.scss';
|
||||
|
||||
.userItemActions {
|
||||
overflow: hidden;
|
||||
// display: none;
|
||||
}
|
||||
|
||||
.userActionsList {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.userActionsItem {
|
||||
margin-top: 0.25rem;
|
||||
padding: 0.2rem 0;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: darken($user-list-bg, 14%);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.actionIcon {
|
||||
color: $color-gray-light;
|
||||
line-height: 1.1rem;
|
||||
flex-basis: 1.7rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.actionText {
|
||||
color: $color-gray;
|
||||
padding: 0 0.6rem;
|
||||
}
|
@ -14,7 +14,7 @@ app:
|
||||
bbbServerVersion: "1.0"
|
||||
copyrightYear: "2015"
|
||||
html5ClientBuild: "NNNN"
|
||||
defaultWelcomeMessage: Welcome to %%CONFNAME%%!\r\rFor help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.\r\rTo join the audio bridge click the gear icon (upper-right hand corner). Use a headset to avoid causing background noise for others.\r\r\r
|
||||
defaultWelcomeMessage: Welcome to %%CONFNAME%%!<br /><br />For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br /><br />To join the audio bridge click the gear icon (upper-right hand corner). Use a headset to avoid causing background noise for others.<br /><br /><br />
|
||||
lockOnJoin: true
|
||||
defaultWelcomeMessageFooter: This server is running a build of <a href="http://docs.bigbluebutton.org/1.0/10overview.html" target="_blank"><u>BigBlueButton 1.0</u></a>.
|
||||
|
||||
|
@ -408,9 +408,7 @@ public class ParamsProcessorUtil {
|
||||
}
|
||||
|
||||
public String getDefaultConfigXML() {
|
||||
if (defaultConfigXML == null) {
|
||||
defaultConfigXML = getConfig(defaultConfigURL);
|
||||
}
|
||||
defaultConfigXML = getConfig(defaultConfigURL);
|
||||
|
||||
return defaultConfigXML;
|
||||
}
|
||||
|