Merge branch 'v2.0.x-release' into fix-audio-locale-change

This commit is contained in:
Anton Georgiev 2018-01-09 14:52:49 -05:00
commit e9093a6c19
307 changed files with 24096 additions and 7136 deletions

1
.gitignore vendored
View File

@ -32,3 +32,4 @@ clients/flash/**/build
clients/flash/**/.gradle
**/.idea/*
*.iml
*~

View File

@ -4,7 +4,6 @@ import org.bigbluebutton.common2.msgs.UserJoinMeetingAfterReconnectReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.apps.voice.UserJoinedVoiceConfEvtMsgHdlr
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.VoiceUsers
import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers with UserJoinedVoiceConfEvtMsgHdlr {

View File

@ -5,6 +5,7 @@ import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
this: BaseMeetingActor =>
@ -46,6 +47,12 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
val voiceUserState = VoiceUserState(intId, voiceUserId, callingWith, callerIdName, callerIdNum, muted, talking, listenOnly = isListenOnly)
VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
if (MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props.voiceProp.voiceConf,
voiceUserId, true)
outGW.send(event)
}
broadcastEvent(voiceUserState)
if (liveMeeting.props.meetingProp.isBreakout) {
@ -80,4 +87,5 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
val event = StartRecordingVoiceConfSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
}

View File

@ -131,6 +131,13 @@ class MeetingActor(
var lastRttTestSentOn = System.currentTimeMillis()
// Initialize if the meeting is muted on start
if (props.voiceProp.muteOnStart) {
MeetingStatus2x.muteMeeting(liveMeeting.status)
} else {
MeetingStatus2x.unmuteMeeting(liveMeeting.status)
}
/*******************************************************************/
//object FakeTestData extends FakeTestData
//FakeTestData.createFakeUsers(liveMeeting)

View File

@ -16,7 +16,7 @@ case class RecordProp(record: Boolean, autoStartRecording: Boolean, allowStartSt
case class WelcomeProp(welcomeMsgTemplate: String, welcomeMsg: String, modOnlyMessage: String)
case class VoiceProp(telVoice: String, voiceConf: String, dialNumber: String)
case class VoiceProp(telVoice: String, voiceConf: String, dialNumber: String, muteOnStart: Boolean)
case class UsersProp(maxUsers: Int, webcamsOnlyForModerator: Boolean, guestPolicy: String)

View File

@ -266,7 +266,8 @@ public class MeetingService implements MessageListener {
m.isBreakout(), m.getSequence(), m.getMetadata(), m.getGuestPolicy(), m.getWelcomeMessageTemplate(),
m.getWelcomeMessage(), m.getModeratorOnlyMessage(), m.getDialNumber(), m.getMaxUsers(),
m.getMaxInactivityTimeoutMinutes(), m.getWarnMinutesBeforeMax(),
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes());
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(),
m.getMuteOnStart());
}

View File

@ -81,6 +81,7 @@ public class ParamsProcessorUtil {
private boolean autoStartRecording;
private boolean allowStartStopRecording;
private boolean webcamsOnlyForModerator;
private boolean defaultMuteOnStart = false;
private String defaultConfigXML = null;
@ -352,6 +353,9 @@ public class ParamsProcessorUtil {
int meetingDuration = processMeetingDuration(params.get("duration"));
int logoutTimer = processMeetingDuration(params.get("logoutTimer"));
// Hardcode to zero as we don't use this feature in 2.0.x (ralam dec 18, 2017)
logoutTimer = 0;
// set is breakout room property
boolean isBreakout = false;
if (!StringUtils.isEmpty(params.get("isBreakout"))) {
@ -463,7 +467,9 @@ public class ParamsProcessorUtil {
meeting.storeConfig(true, configXML);
if (!StringUtils.isEmpty(params.get("moderatorOnlyMessage"))) {
String moderatorOnlyMessage = params.get("moderatorOnlyMessage");
String moderatorOnlyMessageTemplate = params.get("moderatorOnlyMessage");
String moderatorOnlyMessage = substituteKeywords(moderatorOnlyMessageTemplate,
dialNumber, telVoice, meetingName);
meeting.setModeratorOnlyMessage(moderatorOnlyMessage);
}
@ -478,6 +484,19 @@ public class ParamsProcessorUtil {
meeting.setParentMeetingId(parentMeetingId);
}
if (!StringUtils.isEmpty(params.get("logo"))) {
meeting.setCustomLogoURL(params.get("logo"));
}
if (!StringUtils.isEmpty(params.get("copyright"))) {
meeting.setCustomCopyright(params.get("copyright"));
}
Boolean muteOnStart = defaultMuteOnStart;
if (!StringUtils.isEmpty(params.get("muteOnStart"))) {
muteOnStart = Boolean.parseBoolean(params.get("muteOnStart"));
}
meeting.setMuteOnStart(muteOnStart);
return meeting;
}
@ -892,6 +911,15 @@ public class ParamsProcessorUtil {
meetingExpireIfNoUserJoinedInMinutes = value;
}
public void setMuteOnStart(Boolean mute) {
defaultMuteOnStart = mute;
}
public Boolean getMuteOnStart() {
return defaultMuteOnStart;
}
public ArrayList<String> decodeIds(String encodeid) {
ArrayList<String> ids=new ArrayList<String>();
try {

View File

@ -63,6 +63,9 @@ public class Meeting {
private final ConcurrentMap<String, Config> configs;
private final Boolean isBreakout;
private final List<String> breakoutRooms = new ArrayList<String>();
private String customLogoURL = "";
private String customCopyright = "";
private Boolean muteOnStart = false;
private Integer maxInactivityTimeoutMinutes = 120;
private Integer warnMinutesBeforeMax = 5;
@ -165,8 +168,8 @@ public class Meeting {
return createdTime;
}
public Integer setSequence(Integer s) {
return sequence = s;
public void setSequence(Integer s) {
sequence = s;
}
public Integer getSequence() {
@ -221,8 +224,8 @@ public class Meeting {
return intMeetingId;
}
public String setParentMeetingId(String p) {
return parentMeetingId = p;
public void setParentMeetingId(String p) {
parentMeetingId = p;
}
public String getParentMeetingId() {
@ -292,7 +295,31 @@ public class Meeting {
public boolean hasUserJoined() {
return userHasJoined;
}
public String getCustomLogoURL() {
return customLogoURL;
}
public void setCustomLogoURL(String url) {
customLogoURL = url;
}
public void setCustomCopyright(String copyright) {
customCopyright = copyright;
}
public String getCustomCopyright() {
return customCopyright;
}
public void setMuteOnStart(Boolean mute) {
muteOnStart = mute;
}
public Boolean getMuteOnStart() {
return muteOnStart;
}
public void userJoined(User user) {
userHasJoined = true;
this.users.put(user.getInternalUserId(), user);

View File

@ -19,7 +19,8 @@ public interface IBbbWebApiGWApp {
String dialNumber, Integer maxUsers,
Integer maxInactivityTimeoutMinutes, Integer warnMinutesBeforeMax,
Integer meetingExpireIfNoUserJoinedInMinutes,
Integer meetingExpireWhenLastUserLeftInMinutes);
Integer meetingExpireWhenLastUserLeftInMinutes,
Boolean muteOnStart);
void registerUser(String meetingID, String internalUserId, String fullname, String role,
String externUserID, String authToken, String avatarURL, Boolean guest, Boolean authed);

View File

@ -87,7 +87,8 @@ class BbbWebApiGWApp(val oldMessageReceivedGW: OldMessageReceivedGW,
dialNumber: String, maxUsers: java.lang.Integer, maxInactivityTimeoutMinutes: java.lang.Integer,
warnMinutesBeforeMax: java.lang.Integer,
meetingExpireIfNoUserJoinedInMinutes: java.lang.Integer,
meetingExpireWhenLastUserLeftInMinutes: java.lang.Integer): Unit = {
meetingExpireWhenLastUserLeftInMinutes: java.lang.Integer,
muteOnStart: java.lang.Boolean): Unit = {
val meetingProp = MeetingProp(name = meetingName, extId = extMeetingId, intId = meetingId,
isBreakout = isBreakout.booleanValue())
@ -104,7 +105,7 @@ class BbbWebApiGWApp(val oldMessageReceivedGW: OldMessageReceivedGW,
val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence.intValue(), breakoutRooms = Vector())
val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg,
modOnlyMessage = modOnlyMessage)
val voiceProp = VoiceProp(telVoice = voiceBridge, voiceConf = voiceBridge, dialNumber = dialNumber)
val voiceProp = VoiceProp(telVoice = voiceBridge, voiceConf = voiceBridge, dialNumber = dialNumber, muteOnStart = muteOnStart.booleanValue())
val usersProp = UsersProp(maxUsers = maxUsers.intValue(), webcamsOnlyForModerator = webcamsOnlyForModerator.booleanValue(),
guestPolicy = guestPolicy)
val metadataProp = MetadataProp(mapAsScalaMap(metadata).toMap)

View File

@ -145,6 +145,8 @@ phonecomponents|MuteMeButton {
backgroundColor : #FFFFFF;
paddingTop : 0;
paddingBottom : 6;
verticalAlign : middle;
verticalGap : 0;
}
.breakoutRoomRibbon {
@ -156,7 +158,7 @@ phonecomponents|MuteMeButton {
}
.topBoxStyle {
paddingTop : 6;
paddingTop : 0;
paddingBottom : 0;
paddingLeft : 8;
paddingRight : 8;

View File

@ -215,7 +215,7 @@ bbb.users.usersGrid.mediaItemRenderer.pushToTalk = Unmute {0}
bbb.users.usersGrid.mediaItemRenderer.pushToMute = Mute {0}
bbb.users.usersGrid.mediaItemRenderer.pushToLock = Lock {0}
bbb.users.usersGrid.mediaItemRenderer.pushToUnlock = Unlock {0}
bbb.users.usersGrid.mediaItemRenderer.kickUser = Kick {0}
bbb.users.usersGrid.mediaItemRenderer.kickUser = Remove {0}
bbb.users.usersGrid.mediaItemRenderer.webcam = Webcam shared
bbb.users.usersGrid.mediaItemRenderer.micOff = Microphone off
bbb.users.usersGrid.mediaItemRenderer.micOn = Microphone on
@ -513,7 +513,7 @@ bbb.logout.unknown = Your client has lost connection with the server
bbb.logout.guestkickedout = The moderator didn't allow you to join this meeting
bbb.logout.usercommand = You have logged out of the conference
bbb.logour.breakoutRoomClose = Your browser window will be closed
bbb.logout.ejectedFromMeeting = A moderator has kicked you out of the meeting.
bbb.logout.ejectedFromMeeting = You have been removed from the meeting.
bbb.logout.refresh.message = If this logout was unexpected click the button below to reconnect.
bbb.logout.refresh.label = Reconnect
bbb.settings.title = Settings
@ -711,7 +711,7 @@ bbb.shortcutkey.present.fitPage.function = Fit slides to page
bbb.shortcutkey.users.makePresenter = 89
bbb.shortcutkey.users.makePresenter.function = Make selected person presenter
bbb.shortcutkey.users.kick = 69
bbb.shortcutkey.users.kick.function = Kick selected person from the meeting
bbb.shortcutkey.users.kick.function = Remove selected person from the meeting
bbb.shortcutkey.users.mute = 83
bbb.shortcutkey.users.mute.function = Mute or unmute selected person
bbb.shortcutkey.users.muteall = 65
@ -829,6 +829,7 @@ bbb.users.breakout.timerForRoom.toolTip = Time left for this breakout room
bbb.users.breakout.timer.toolTip = Time left for breakout rooms
bbb.users.breakout.calculatingRemainingTime = Calculating remaining time...
bbb.users.breakout.closing = Closing
bbb.users.breakout.closewarning.text = Breakout rooms are closing in a minute.
bbb.users.breakout.rooms = Rooms
bbb.users.breakout.roomsCombo.accessibilityName = Number of rooms to create
bbb.users.breakout.room = Room

View File

@ -16,8 +16,7 @@
showToolbar="true" showFooter="true" showMeetingName="true" showHelpButton="true"
showLogoutWindow="true" showLayoutTools="true" confirmLogout="true" showNetworkMonitor="false"
showRecordingNotification="true" logoutOnStopRecording="false"/>
<meeting muteOnStart="false" />
<breakoutRooms enabled="true" record="false" />
<breakoutRooms enabled="true" record="false" privateChateEnabled="true"/>
<logging enabled="true" logTarget="trace" level="info" format="{dateUTC} {time} :: {name} :: [{logLevel}] {message}" uri="http://HOST/log" logPattern=".*"/>
<lock disableCam="false" disableMic="false" disablePrivateChat="false"
disablePublicChat="false" lockedLayout="false" lockOnJoin="true" lockOnJoinConfigurable="false"/>
@ -49,6 +48,7 @@
uri="rtmp://HOST/screenshare"
showButton="true"
enablePause="true"
tryKurentoWebRTC="false"
tryWebRTCFirst="false"
chromeExtensionLink=""
chromeExtensionKey=""

View File

@ -142,8 +142,8 @@
<script src="lib/verto-min.js" language="javascript"></script>
<script src="lib/verto_extension.js" language="javascript"></script>
<script src="lib/kurento-utils.min.js" language="javascript"></script>
<script src="lib/kurento-extension.js" language="javascript"></script>
<script src="lib/kurento-utils.js" language="javascript"></script>
<script src="lib/bbb_api_bridge.js?v=VERSION" language="javascript"></script>
<script src="lib/sip.js?v=VERSION" language="javascript"></script>

View File

@ -1,6 +1,7 @@
var isFirefox = typeof window.InstallTrigger !== 'undefined';
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = !!window.chrome && !isOpera;
var isSafari = navigator.userAgent.indexOf("Safari") >= 0 && !isChrome;
var kurentoHandler = null;
Kurento = function (
@ -20,7 +21,7 @@ Kurento = function (
this.screenConstraints = {};
this.mediaCallback = null;
this.voiceBridge = voiceBridge;
this.voiceBridge = voiceBridge + '-SCREENSHARE';
this.internalMeetingId = internalMeetingId;
this.vid_width = window.screen.width;
@ -33,9 +34,8 @@ Kurento = function (
this.caller_id_name = conferenceUsername;
this.caller_id_number = conferenceUsername;
this.pingInterval;
this.kurentoPort = "kurento-screenshare";
this.kurentoPort = "bbb-webrtc-sfu";
this.hostName = window.location.hostname;
this.socketUrl = 'wss://' + this.hostName + '/' + this.kurentoPort;
@ -43,6 +43,7 @@ Kurento = function (
if (chromeExtension != null) {
this.chromeExtension = chromeExtension;
window.chromeExtension = chromeExtension;
}
if (onFail != null) {
@ -57,21 +58,44 @@ Kurento = function (
this.KurentoManager= function () {
this.kurentoVideo = null;
this.kurentoScreenShare = null;
this.kurentoScreenshare = null;
};
KurentoManager.prototype.exitScreenShare = function () {
if (this.kurentoScreenShare != null) {
if(kurentoHandler.pingInterval) {
clearInterval(kurentoHandler.pingInterval);
console.log(" [exitScreenShare] Exiting screensharing");
if(typeof this.kurentoScreenshare !== 'undefined' && this.kurentoScreenshare) {
if(this.kurentoScreenshare.ws !== null) {
this.kurentoScreenshare.ws.onclose = function(){};
this.kurentoScreenshare.ws.close();
}
if(kurentoHandler.ws !== null) {
kurentoHandler.ws.onclose = function(){};
kurentoHandler.ws.close();
this.kurentoScreenshare.disposeScreenShare();
this.kurentoScreenshare = null;
}
if (this.kurentoScreenshare) {
this.kurentoScreenshare = null;
}
if(typeof this.kurentoVideo !== 'undefined' && this.kurentoVideo) {
this.exitVideo();
}
};
KurentoManager.prototype.exitVideo = function () {
console.log(" [exitScreenShare] Exiting screensharing viewing");
if(typeof this.kurentoVideo !== 'undefined' && this.kurentoVideo) {
if(this.kurentoVideo.ws !== null) {
this.kurentoVideo.ws.onclose = function(){};
this.kurentoVideo.ws.close();
}
kurentoHandler.disposeScreenShare();
this.kurentoScreenShare = null;
kurentoHandler = null;
this.kurentoVideo.disposeScreenShare();
this.kurentoVideo = null;
}
if (this.kurentoVideo) {
this.kurentoVideo = null;
}
};
@ -79,24 +103,21 @@ KurentoManager.prototype.shareScreen = function (tag) {
this.exitScreenShare();
var obj = Object.create(Kurento.prototype);
Kurento.apply(obj, arguments);
this.kurentoScreenShare = obj;
kurentoHandler = obj;
this.kurentoScreenShare.setScreenShare(tag);
this.kurentoScreenshare = obj;
this.kurentoScreenshare.setScreenShare(tag);
};
// Still unused, part of the HTML5 implementation
KurentoManager.prototype.joinWatchVideo = function (tag) {
this.exitVideo();
var obj = Object.create(Kurento.prototype);
Kurento.apply(obj, arguments);
this.kurentoVideo = obj;
kurentoHandler = obj;
this.kurentoVideo.setWatchVideo(tag);
};
Kurento.prototype.setScreenShare = function (tag) {
this.mediaCallback = this.makeShare;
this.mediaCallback = this.makeShare.bind(this);
this.create(tag);
};
@ -112,19 +133,18 @@ Kurento.prototype.init = function () {
console.log("this browser supports websockets");
this.ws = new WebSocket(this.socketUrl);
this.ws.onmessage = this.onWSMessage;
this.ws.onclose = function (close) {
this.ws.onmessage = this.onWSMessage.bind(this);
this.ws.onclose = (close) => {
kurentoManager.exitScreenShare();
self.onFail("Websocket connection closed");
};
this.ws.onerror = function (error) {
this.ws.onerror = (error) => {
kurentoManager.exitScreenShare();
self.onFail("Websocket connection error");
};
this.ws.onopen = function() {
self.pingInterval = setInterval(self.ping, 3000);
this.ws.onopen = function () {
self.mediaCallback();
};
}.bind(self);
}
else
console.log("this browser does not support websockets");
@ -135,15 +155,16 @@ Kurento.prototype.onWSMessage = function (message) {
switch (parsedMessage.id) {
case 'presenterResponse':
kurentoHandler.presenterResponse(parsedMessage);
this.presenterResponse(parsedMessage);
break;
case 'viewerResponse':
this.viewerResponse(parsedMessage);
break;
case 'stopSharing':
kurentoManager.exitScreenShare();
break;
case 'iceCandidate':
kurentoHandler.webRtcPeer.addIceCandidate(parsedMessage.candidate);
break;
case 'pong':
this.webRtcPeer.addIceCandidate(parsedMessage.candidate);
break;
default:
console.error('Unrecognized message', parsedMessage);
@ -156,21 +177,33 @@ Kurento.prototype.setRenderTag = function (tag) {
Kurento.prototype.presenterResponse = function (message) {
if (message.response != 'accepted') {
var errorMsg = message.message ? message.message : 'Unknow error';
console.warn('Call not accepted for the following reason: ' + errorMsg);
var errorMsg = message.message ? message.message : 'Unknown error';
console.warn('Call not accepted for the following reason: ' + JSON.stringify(errorMsg, null, 2));
kurentoManager.exitScreenShare();
kurentoHandler.onFail(errorMessage);
this.onFail(errorMessage);
} else {
console.log("Presenter call was accepted with SDP => " + message.sdpAnswer);
this.webRtcPeer.processAnswer(message.sdpAnswer);
}
}
Kurento.prototype.viewerResponse = function (message) {
if (message.response != 'accepted') {
var errorMsg = message.message ? message.message : 'Unknown error';
console.warn('Call not accepted for the following reason: ' + errorMsg);
kurentoManager.exitScreenShare();
this.onFail(errorMessage);
} else {
console.log("Viewer call was accepted with SDP => " + message.sdpAnswer);
this.webRtcPeer.processAnswer(message.sdpAnswer);
}
}
Kurento.prototype.serverResponse = function (message) {
if (message.response != 'accepted') {
var errorMsg = message.message ? message.message : 'Unknow error';
console.warn('Call not accepted for the following reason: ' + errorMsg);
kurentoHandler.dispose();
kurentoManager.exitScreenShare();
} else {
this.webRtcPeer.processAnswer(message.sdpAnswer);
}
@ -178,89 +211,105 @@ Kurento.prototype.serverResponse = function (message) {
Kurento.prototype.makeShare = function() {
var self = this;
console.log("Kurento.prototype.makeShare " + JSON.stringify(this.webRtcPeer, null, 2));
if (!this.webRtcPeer) {
var options = {
onicecandidate : this.onIceCandidate
onicecandidate : self.onIceCandidate.bind(self)
}
console.log("Peer options " + JSON.stringify(options, null, 2));
kurentoHandler.startScreenStreamFrom();
this.startScreenStreamFrom();
}
}
Kurento.prototype.onOfferPresenter = function (error, offerSdp) {
let self = this;
if(error) {
console.log("Kurento.prototype.onOfferPresenter Error " + error);
kurentoHandler.onFail(error);
this.onFail(error);
return;
}
var message = {
id : 'presenter',
type: 'screenshare',
internalMeetingId: kurentoHandler.internalMeetingId,
voiceBridge: kurentoHandler.voiceBridge,
callerName : kurentoHandler.caller_id_name,
role: 'presenter',
internalMeetingId: self.internalMeetingId,
voiceBridge: self.voiceBridge,
callerName : self.caller_id_name,
sdpOffer : offerSdp,
vh: kurentoHandler.vid_height,
vw: kurentoHandler.vid_width
vh: self.vid_height,
vw: self.vid_width
};
console.log("onOfferPresenter sending to screenshare server => " + JSON.stringify(message, null, 2));
kurentoHandler.sendMessage(message);
this.sendMessage(message);
}
Kurento.prototype.startScreenStreamFrom = function () {
var screenInfo = null;
var _this = this;
var self = this;
if (!!window.chrome) {
if (!_this.chromeExtension) {
_this.logError({
if (!self.chromeExtension) {
self.logError({
status: 'failed',
message: 'Missing Chrome Extension key',
});
_this.onFail();
self.onFail();
return;
}
}
// TODO it would be nice to check those constraints
_this.screenConstraints.video = {};
if (typeof screenConstraints !== undefined) {
self.screenConstraints = {};
}
self.screenConstraints.video = {};
console.log(self);
var options = {
//localVideo: this.renderTag,
onicecandidate : _this.onIceCandidate,
mediaConstraints : _this.screenConstraints,
localVideo: document.getElementById(this.renderTag),
onicecandidate : self.onIceCandidate.bind(self),
mediaConstraints : self.screenConstraints,
sendSource : 'desktop'
};
console.log(" Peer options => " + JSON.stringify(options, null, 2));
_this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, function(error) {
self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, function(error) {
if(error) {
console.log("WebRtcPeerSendonly constructor error " + JSON.stringify(error, null, 2));
kurentoHandler.onFail(error);
self.onFail(error);
return kurentoManager.exitScreenShare();
}
_this.webRtcPeer.generateOffer(_this.onOfferPresenter);
self.webRtcPeer.generateOffer(self.onOfferPresenter.bind(self));
console.log("Generated peer offer w/ options " + JSON.stringify(options));
});
}
Kurento.prototype.onIceCandidate = function(candidate) {
Kurento.prototype.onIceCandidate = function (candidate) {
let self = this;
console.log('Local candidate' + JSON.stringify(candidate));
var message = {
id : 'onIceCandidate',
role: 'presenter',
type: 'screenshare',
voiceBridge: kurentoHandler.voiceBridge,
voiceBridge: self.voiceBridge,
candidate : candidate
}
console.log("this object " + JSON.stringify(this, null, 2));
kurentoHandler.sendMessage(message);
this.sendMessage(message);
}
Kurento.prototype.onViewerIceCandidate = function (candidate) {
let self = this;
console.log('Viewer local candidate' + JSON.stringify(candidate));
var message = {
id : 'viewerIceCandidate',
role: 'viewer',
type: 'screenshare',
voiceBridge: self.voiceBridge,
candidate : candidate,
callerName: self.caller_id_name
}
this.sendMessage(message);
}
Kurento.prototype.setWatchVideo = function (tag) {
@ -276,60 +325,50 @@ Kurento.prototype.viewer = function () {
if (!this.webRtcPeer) {
var options = {
remoteVideo: this.renderTag,
onicecandidate : onIceCandidate
remoteVideo: document.getElementById(this.renderTag),
onicecandidate : this.onViewerIceCandidate.bind(this)
}
webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function(error) {
self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function(error) {
if(error) {
return kurentoHandler.onFail(error);
return self.onFail(error);
}
this.generateOffer(onOfferViewer);
this.generateOffer(self.onOfferViewer.bind(self));
});
}
};
Kurento.prototype.onOfferViewer = function (error, offerSdp) {
let self = this;
if(error) {
console.log("Kurento.prototype.onOfferViewer Error " + error);
return kurentoHandler.onFail();
return this.onFail();
}
var message = {
id : 'viewer',
type: 'screenshare',
internalMeetingId: kurentoHandler.internalMeetingId,
voiceBridge: kurentoHandler.voiceBridge,
callerName : kurentoHandler.caller_id_name,
role: 'viewer',
internalMeetingId: self.internalMeetingId,
voiceBridge: self.voiceBridge,
callerName : self.caller_id_name,
sdpOffer : offerSdp
};
console.log("onOfferViewer sending to screenshare server => " + JSON.stringify(message, null, 2));
kurentoHandler.sendMessage(message);
this.sendMessage(message);
};
Kurento.prototype.ping = function() {
var message = {
id : 'ping',
type: 'screenshare',
internalMeetingId: kurentoHandler.internalMeetingId,
voiceBridge: kurentoHandler.voiceBridge,
callerName : kurentoHandler.caller_id_name,
};
kurentoHandler.sendMessage(message);
}
Kurento.prototype.stop = function() {
if (this.webRtcPeer) {
var message = {
id : 'stop',
type : 'screenshare',
voiceBridge: kurentoHandler.voiceBridge
}
kurentoHandler.sendMessage(message);
kurentoHandler.disposeScreenShare();
}
//if (this.webRtcPeer) {
// var message = {
// id : 'stop',
// type : 'screenshare',
// voiceBridge: kurentoHandler.voiceBridge
// }
// kurentoHandler.sendMessage(message);
// kurentoHandler.disposeScreenShare();
//}
}
Kurento.prototype.dispose = function() {
@ -360,19 +399,6 @@ Kurento.prototype.logError = function (obj) {
console.error(obj);
};
Kurento.prototype.getChromeScreenConstraints = function(callback, extensionId) {
chrome.runtime.sendMessage(extensionId, {
getStream: true,
sources: [
"window",
"screen",
"tab"
]},
function(response) {
console.log(response);
callback(response);
});
};
Kurento.normalizeCallback = function (callback) {
if (typeof callback == 'function') {
@ -389,30 +415,42 @@ Kurento.normalizeCallback = function (callback) {
// this function explains how to use above methods/objects
window.getScreenConstraints = function(sendSource, callback) {
var _this = this;
var chromeMediaSourceId = sendSource;
if(isChrome) {
kurentoHandler.getChromeScreenConstraints (function (constraints) {
let chromeMediaSourceId = sendSource;
let screenConstraints = {video: {}};
var sourceId = constraints.streamId;
if(isChrome) {
getChromeScreenConstraints ((constraints) => {
let sourceId = constraints.streamId;
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
kurentoHandler.screenConstraints.video.chromeMediaSource = { exact: [sendSource]};
kurentoHandler.screenConstraints.video.chromeMediaSourceId= sourceId;
console.log("getScreenConstraints for Chrome returns => " +JSON.stringify(kurentoHandler.screenConstraints, null, 2));
screenConstraints.video.chromeMediaSource = { exact: [sendSource]};
screenConstraints.video.chromeMediaSourceId = sourceId;
console.log("getScreenConstraints for Chrome returns => ");
console.log(screenConstraints);
// now invoking native getUserMedia API
callback(null, kurentoHandler.screenConstraints);
callback(null, screenConstraints);
}, kurentoHandler.chromeExtension);
}, chromeExtension);
}
else if (isFirefox) {
kurentoHandler.screenConstraints.video.mediaSource= "screen";
kurentoHandler.screenConstraints.video.width= {max: kurentoHandler.vid_width};
kurentoHandler.screenConstraints.video.height = {max: kurentoHandler.vid_height};
screenConstraints.video.mediaSource= "window";
screenConstraints.video.width= {max: "1280"};
screenConstraints.video.height = {max: "720"};
console.log("getScreenConstraints for Firefox returns => " +JSON.stringify(kurentoHandler.screenConstraints, null, 2));
console.log("getScreenConstraints for Firefox returns => ");
console.log(screenConstraints);
// now invoking native getUserMedia API
callback(null, kurentoHandler.screenConstraints);
callback(null, screenConstraints);
}
else if(isSafari) {
screenConstraints.video.mediaSource= "screen";
screenConstraints.video.width= {max: window.screen.width};
screenConstraints.video.height = {max: window.screen.vid_height};
console.log("getScreenConstraints for Safari returns => ");
console.log(screenConstraints);
// now invoking native getUserMedia API
callback(null, screenConstraints);
}
}
@ -437,3 +475,22 @@ window.kurentoWatchVideo = function () {
window.kurentoInitialize();
window.kurentoManager.joinWatchVideo.apply(window.kurentoManager, arguments);
};
window.kurentoExitVideo = function () {
window.kurentoInitialize();
window.kurentoManager.exitVideo();
}
window.getChromeScreenConstraints = function(callback, extensionId) {
chrome.runtime.sendMessage(extensionId, {
getStream: true,
sources: [
"window",
"screen",
"tab"
]},
function(response) {
console.log(response);
callback(response);
});
};;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});

View File

@ -21,24 +21,35 @@ package org.bigbluebutton.core {
import flash.events.TimerEvent;
import flash.utils.Dictionary;
import flash.utils.Timer;
import mx.controls.Alert;
import mx.controls.Label;
import org.bigbluebutton.util.i18n.ResourceUtil;
import mx.managers.PopUpManager;
import org.bigbluebutton.util.i18n.ResourceUtil;
public final class TimerUtil {
public static var timers:Dictionary = new Dictionary(true);
public static function setCountDownTimer(label:Label, seconds:int):void {
public static function setCountDownTimer(label:Label, seconds:int, showMinuteWarning:Boolean=false):void {
var timer:Timer = getTimer(label.id, seconds);
var minuteWarningShown:Boolean = false;
var minuteAlert:Alert = null;
if (!timer.hasEventListener(TimerEvent.TIMER)) {
timer.addEventListener(TimerEvent.TIMER, function():void {
var remainingSeconds:int = timer.repeatCount - timer.currentCount;
var formattedTime:String = (Math.floor(remainingSeconds / 60)) + ":" + (remainingSeconds % 60 >= 10 ? "" : "0") + (remainingSeconds % 60);
label.text = formattedTime;
if (remainingSeconds < 60 && showMinuteWarning && !minuteWarningShown) {
minuteAlert = Alert.show(ResourceUtil.getInstance().getString('bbb.users.breakout.closewarning.text'));
minuteWarningShown = true;
}
});
timer.addEventListener(TimerEvent.TIMER_COMPLETE, function():void {
label.text = ResourceUtil.getInstance().getString('bbb.users.breakout.closing');
if (minuteAlert != null) {
PopUpManager.removePopUp(minuteAlert);
}
});
} else {
timer.stop();

View File

@ -17,6 +17,7 @@ package org.bigbluebutton.core.model
public var webcamsOnlyForModerator:Boolean = false;
public var metadata:Object = null;
public var muteOnStart:Boolean = false;
public var customLogo:String = "";
public var customCopyright:String = "";
}
}

View File

@ -1,31 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.main.model.options {
import org.bigbluebutton.core.Options;
public class MeetingOptions extends Options {
[Bindable]
public var muteOnStart:Boolean = false;
public function MeetingOptions() {
name = "meeting";
}
}
}

View File

@ -11,13 +11,14 @@ package org.bigbluebutton.main.model.users
public var authToken: String;
public var customdata:Object = new Object();
public var logoutUrl: String;
public var logoutTimer : int;
public var logoutTimer : int;
public var defaultLayout: String;
public var avatarURL: String;
public var dialnumber: String;
public var voiceConf: String;
public var welcome: String;
public var customLogo:String;
public var customCopyright:String;
public var meetingName: String;
public var extMeetingId: String;
public var intMeetingId: String;
@ -28,5 +29,6 @@ package org.bigbluebutton.main.model.users
public var webcamsOnlyForModerator: Boolean;
public var metadata: Object = new Object();
public var modOnlyMessage: String;
public var muteOnStart:Boolean = false;
}
}

View File

@ -118,7 +118,7 @@ package org.bigbluebutton.main.model.users
private function handleComplete(e:Event):void {
var result:Object = JSON.parse(e.target.data);
var logData:Object = UsersUtil.initLogData();
logData.tags = ["initialization"];
@ -158,9 +158,10 @@ package org.bigbluebutton.main.model.users
apiResponse.welcome = result.response.welcome;
apiResponse.logoutUrl = processLogoutUrl(result.response);
apiResponse.logoutTimer = result.response.logoutTimer;
apiResponse.logoutTimer = result.response.logoutTimer;
apiResponse.defaultLayout = result.response.defaultLayout;
apiResponse.avatarURL = result.response.avatarURL
apiResponse.avatarURL = result.response.avatarURL;
apiResponse.customdata = new Object();
if (result.response.customdata) {
@ -178,7 +179,11 @@ package org.bigbluebutton.main.model.users
if (result.response.hasOwnProperty("modOnlyMessage")) {
apiResponse.modOnlyMessage = result.response.modOnlyMessage;
}
apiResponse.customLogo = result.response.customLogoURL;
apiResponse.customCopyright = result.response.customCopyright;
apiResponse.muteOnStart = result.response.muteOnStart as Boolean;
if (_resultListener != null) _resultListener(true, apiResponse);
}

View File

@ -22,6 +22,7 @@ package org.bigbluebutton.main.model.users
import flash.external.ExternalInterface;
import flash.net.NetConnection;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB;
@ -40,7 +41,6 @@ package org.bigbluebutton.main.model.users
import org.bigbluebutton.main.events.SuccessfulLoginEvent;
import org.bigbluebutton.main.events.UserServicesEvent;
import org.bigbluebutton.main.model.options.ApplicationOptions;
import org.bigbluebutton.main.model.options.MeetingOptions;
import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent;
import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent;
import org.bigbluebutton.main.model.users.events.ChangeRoleEvent;
@ -49,8 +49,9 @@ package org.bigbluebutton.main.model.users
import org.bigbluebutton.main.model.users.events.KickUserEvent;
import org.bigbluebutton.main.model.users.events.RoleChangeEvent;
import org.bigbluebutton.main.model.users.events.UsersConnectionEvent;
import org.bigbluebutton.modules.users.events.MeetingMutedEvent;
import org.bigbluebutton.modules.users.services.MessageReceiver;
import org.bigbluebutton.modules.users.services.MessageSender;
import org.bigbluebutton.modules.users.services.MessageSender;
public class UserService {
private static const LOGGER:ILogger = getClassLogger(UserService);
@ -96,8 +97,7 @@ package org.bigbluebutton.main.model.users
private function joinListener(success:Boolean, result: EnterApiResponse):void {
if (success) {
var meetingOptions : MeetingOptions = Options.getOptions(MeetingOptions) as MeetingOptions;
LiveMeeting.inst().me.id = result.intUserId
LiveMeeting.inst().me.name = result.username;
LiveMeeting.inst().me.externalId = result.extUserId;
@ -128,8 +128,10 @@ package org.bigbluebutton.main.model.users
LiveMeeting.inst().meeting.allowStartStopRecording = result.allowStartStopRecording;
LiveMeeting.inst().meeting.webcamsOnlyForModerator = result.webcamsOnlyForModerator;
LiveMeeting.inst().meeting.metadata = result.metadata;
LiveMeeting.inst().meeting.muteOnStart = meetingOptions.muteOnStart;
LiveMeeting.inst().meeting.muteOnStart = result.muteOnStart;
LiveMeeting.inst().meetingStatus.isMeetingMuted = result.muteOnStart;
LiveMeeting.inst().meeting.customLogo = result.customLogo;
LiveMeeting.inst().meeting.customCopyright = result.customCopyright;
// assign the meeting name to the document title
ExternalInterface.call("setTitle", result.meetingName);
@ -137,6 +139,8 @@ package org.bigbluebutton.main.model.users
var e:ConferenceCreatedEvent = new ConferenceCreatedEvent(ConferenceCreatedEvent.CONFERENCE_CREATED_EVENT);
dispatcher.dispatchEvent(e);
// Send event to trigger meeting muted initialization of meeting (ralam dec 21, 2017)
dispatcher.dispatchEvent(new MeetingMutedEvent());
connect();
}
}

View File

@ -84,6 +84,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<mate:Listener type="{BBBEvent.WAITING_FOR_MODERATOR_ACCEPTANCE}" method="openWaitWindow" />
<mate:Listener type="{BBBEvent.RECONNECT_DISCONNECTED_EVENT}" method="closeWaitWindow"/>
<mate:Listener type="{RoundTripLatencyReceivedEvent.ROUND_TRIP_LATENCY_RECEIVED}" method="handleRoundTripLatencyReceivedEvent"/>
<mate:Listener type="{ConferenceCreatedEvent.CONFERENCE_CREATED_EVENT}" method="handleConferenceCreatedEvent" />
</fx:Declarations>
<fx:Script>
@ -125,6 +126,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.core.events.NewGuestWaitingEvent;
import org.bigbluebutton.core.events.RoundTripLatencyReceivedEvent;
import org.bigbluebutton.core.events.SwitchedLayoutEvent;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.core.vo.LockSettingsVO;
import org.bigbluebutton.main.events.AppVersionEvent;
import org.bigbluebutton.main.events.BBBEvent;
@ -143,6 +145,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.main.model.options.BrowserVersionsOptions;
import org.bigbluebutton.main.model.options.LanguageOptions;
import org.bigbluebutton.main.model.options.LayoutOptions;
import org.bigbluebutton.main.model.users.events.ConferenceCreatedEvent;
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
import org.bigbluebutton.modules.phone.events.AudioSelectionWindowEvent;
import org.bigbluebutton.modules.phone.events.FlashMicSettingsEvent;
@ -188,7 +191,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[Bindable] private var showToolbarOpt:Boolean = true;
[Bindable] private var _showToolbar:Boolean = true;
private const DEFAULT_TOOLBAR_HEIGHT:Number = 50;
public static const DEFAULT_TOOLBAR_HEIGHT:Number = 50;
[Bindable] private var toolbarHeight:Number = DEFAULT_TOOLBAR_HEIGHT;
[Bindable] private var showFooterOpt:Boolean = true;
[Bindable] private var _showFooter:Boolean = true;
@ -245,12 +248,20 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
_respTimer.start();
}
private function handleConferenceCreatedEvent(event:ConferenceCreatedEvent):void {
updateCopyrightText();
}
private function updateCopyrightText():void {
if (StringUtils.isEmpty(brandingOptions.copyright)) {
copyrightText = ResourceUtil.getInstance().getString('bbb.mainshell.copyrightLabel2',[appVersion]);
} else {
copyrightText = String(brandingOptions.copyright).replace("{0}", appVersion);
}
if (!StringUtils.isEmpty(LiveMeeting.inst().meeting.customCopyright)) {
copyrightText = LiveMeeting.inst().meeting.customCopyright;
}
}
private function initQuote() : void {
@ -685,7 +696,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
if (e is ConnectionFailedEvent) {
showlogoutWindow((e as ConnectionFailedEvent).type);
} else
showlogoutWindow("You have logged out of the conference");
showlogoutWindow(ConnectionFailedEvent.USER_EJECTED_FROM_MEETING);
}
private function handleExitApplicationEvent(e:ExitApplicationEvent = null):void {
@ -762,7 +773,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function updateToolbarHeight():void {
if (toolbarHeight != 0) {
toolbarHeight = Math.max(DEFAULT_TOOLBAR_HEIGHT, toolbar.logo.height + toolbar.quickLinks.includeInLayout ? toolbar.quickLinks.height : 0);
toolbarHeight = DEFAULT_TOOLBAR_HEIGHT;
if (toolbar.quickLinks.includeInLayout) {
toolbarHeight += toolbar.quickLinks.height;
}
if (UsersUtil.isBreakout()) {
toolbarHeight += toolbar.breakoutRibbon.height;
}

View File

@ -151,7 +151,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
if (!Accessibility.active) {
quickLinks.removeAllChildren();
} else {
quickLinks.includeInLayout = true;
quickLinks.visible = quickLinks.includeInLayout = true;
}
}
@ -178,14 +178,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
break;
}
}
public function displayToolbar():void{
if (toolbarOptions.showToolbar) {
showToolbar = true;
} else {
showToolbar = false;
}
if (toolbarOptions.showHelpButton) {
showHelpBtn = true;
} else {
@ -200,7 +200,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
if (!timeRemaining.visible && e.timeLeftInSec <= 1800) {
timeRemaining.visible = true;
}
TimerUtil.setCountDownTimer(timeRemaining, e.timeLeftInSec);
TimerUtil.setCountDownTimer(timeRemaining, e.timeLeftInSec, true);
}
private function retrieveMeetingName(e:ConferenceCreatedEvent):void {
@ -211,13 +211,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
}
if (UsersUtil.isBreakout()) {
breakoutRibbon.visible = breakoutRibbon.includeInLayout = true;
var sequence:String = StringUtils.substringAfterLast(UsersUtil.getMeetingName(), " ");
sequence = StringUtils.substringBefore(sequence, ")");
breakoutLabel.text = ResourceUtil.getInstance().getString("bbb.users.breakout.youareinroom", [sequence]);
var customLogo: String = LiveMeeting.inst().meeting.customLogo;
if (customLogo != "") {
logo.source = LiveMeeting.inst().meeting.customLogo;
}
initBreakoutRibbon();
if (LiveMeeting.inst().me.logoutTimer > 0 ) {
idleLogoutButton.startTimer(LiveMeeting.inst().me.logoutTimer);
} else {
@ -226,6 +227,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
logFlashPlayerCapabilities();
}
private function initBreakoutRibbon() : void {
if (UsersUtil.isBreakout()) {
breakoutRibbon.visible = breakoutRibbon.includeInLayout = true;
var sequence:String = StringUtils.substringAfterLast(UsersUtil.getMeetingName(), " ");
sequence = StringUtils.substringBefore(sequence, ")");
breakoutLabel.text = ResourceUtil.getInstance().getString("bbb.users.breakout.youareinroom", [sequence]);
}
}
private function refreshModeratorButtonsVisibility(e:*):void {
showGuestSettingsButton = UsersUtil.amIModerator() && usersOptions.enableGuestUI;
@ -422,6 +432,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
btnLogout.styleName = "logoutButtonStyle" + styleNameExt;
initBreakoutRibbon();
}
private function openSettings(e:Event = null):void{
@ -531,10 +543,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
tabIndices="{[recordBtn, webRTCAudioStatus, shortcutKeysBtn, helpBtn, btnLogout]}"/>
</fx:Declarations>
<mx:VBox id="mainBox" styleName="toolbarMainBox" width="100%" horizontalScrollPolicy="off" verticalAlign="top">
<mx:VBox id="mainBox" styleName="toolbarMainBox" width="100%" height="100%" horizontalScrollPolicy="off">
<!-- Breakout room Ribbon-->
<mx:HBox id="breakoutRibbon" width="100%" height="30"
styleName="breakoutRoomRibbon"
styleName="breakoutRoomRibbon"
visible="false" includeInLayout="false" >
<mx:Label id="breakoutLabel" />
<mx:Label text="|" visible="{timeRemaining.visible}" includeInLayout="{timeRemaining.visible}"/>
@ -543,18 +555,18 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
toolTip="{ResourceUtil.getInstance().getString('bbb.users.breakout.timerForRoom.toolTip')}"/>
</mx:HBox>
<!-- Top bar -->
<mx:HBox id="topBox" width="100%" verticalAlign="middle" horizontalScrollPolicy="off" styleName="topBoxStyle">
<mx:HBox id="titleBox" width="40%" horizontalAlign="left" verticalAlign="middle" horizontalScrollPolicy="off">
<mx:HBox id="topBox" width="100%" height="{MainApplicationShell.DEFAULT_TOOLBAR_HEIGHT}" verticalAlign="middle" horizontalScrollPolicy="off" styleName="topBoxStyle">
<mx:HBox id="titleBox" width="40%" height="100%" horizontalAlign="left" verticalAlign="middle" horizontalScrollPolicy="off">
<mx:Image id="logo" right="20" maxHeight="35" ioError="hideLogo()" />
<mx:VRule id="logoSperatator" styleName="toolbarSeparator" height="10" />
<mx:Label id="meetingNameLbl" minWidth="1" maxWidth="{titleBox.width - logo.width - 20}" styleName="meetingNameLabelStyle" truncateToFit="true"/>
</mx:HBox>
<mx:HBox id="actionBox" width="30%" horizontalAlign="center" verticalAlign="middle" horizontalScrollPolicy="off">
<mx:HBox id="actionBox" width="30%" height="100%" horizontalAlign="center" verticalAlign="middle" horizontalScrollPolicy="off">
<mx:HBox id="addedBtnsMicrophone" />
<mx:HBox id="addedBtnsWebcam" />
<mx:HBox id="addedBtnsDeskShare" />
</mx:HBox>
<mx:HBox id="rightBox" width="40%" horizontalAlign="right" verticalAlign="middle" horizontalScrollPolicy="off">
<mx:HBox id="rightBox" width="40%" height="100%" horizontalAlign="right" verticalAlign="middle" horizontalScrollPolicy="off">
<views:RecordButton id="recordBtn" visible="{showRecordButton}" includeInLayout="{showRecordButton}"/>
<views:WebRTCAudioStatus id="webRTCAudioStatus" height="30"/>
@ -592,7 +604,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</mx:HBox>
<!-- Accessibilty Quick Links -->
<mx:HBox id="quickLinks" includeInLayout="false" width="100%" horizontalAlign="center">
<mx:HBox id="quickLinks" visible="false" includeInLayout="false" width="100%" height="30" horizontalAlign="center" verticalAlign="middle">
<mx:LinkButton id="usersLinkBtn" click="onQuickLinkClicked('users')" label="{ResourceUtil.getInstance().getString('bbb.users.quickLink.label')}"
accessibilityDescription="{usersLinkBtn.label}" toolTip="{usersLinkBtn.label}"
height="30" styleName="quickWindowLinkStyle" />

View File

@ -36,28 +36,29 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<fx:Script>
<![CDATA[
import com.asfusion.mate.events.Dispatcher;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.CloseEvent;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.EventConstants;
import org.bigbluebutton.core.Options;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.events.CoreEvent;
import org.bigbluebutton.core.events.LockControlEvent;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.main.events.UserJoinedEvent;
import org.bigbluebutton.main.events.UserLeftEvent;
import org.bigbluebutton.main.model.users.events.ChangeMyRole;
import org.bigbluebutton.modules.chat.events.ChatNoiseEnabledEvent;
import org.bigbluebutton.modules.chat.events.ChatOptionsEvent;
import org.bigbluebutton.modules.chat.events.ChatToolbarButtonEvent;
import org.bigbluebutton.modules.chat.model.ChatOptions;
import org.bigbluebutton.util.i18n.ResourceUtil;
import com.asfusion.mate.events.Dispatcher;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.CloseEvent;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.EventConstants;
import org.bigbluebutton.core.Options;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.events.CoreEvent;
import org.bigbluebutton.core.events.LockControlEvent;
import org.bigbluebutton.core.model.LiveMeeting;
import org.bigbluebutton.main.events.UserJoinedEvent;
import org.bigbluebutton.main.events.UserLeftEvent;
import org.bigbluebutton.main.model.users.events.ChangeMyRole;
import org.bigbluebutton.modules.chat.events.ChatNoiseEnabledEvent;
import org.bigbluebutton.modules.chat.events.ChatOptionsEvent;
import org.bigbluebutton.modules.chat.events.ChatToolbarButtonEvent;
import org.bigbluebutton.modules.chat.model.ChatOptions;
import org.bigbluebutton.modules.users.model.BreakoutRoomsOptions;
import org.bigbluebutton.util.i18n.ResourceUtil;
private static const LOGGER:ILogger = getClassLogger(AddChatTabBox);
@ -65,7 +66,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[Bindable] public var chatView:ChatView;
[Bindable] private var fontSizes:Array = ['8', '10', '12', '14', '16', '18'];
[Bindable] public var chatOptions:ChatOptions;
[Bindable] public var chatOptions:ChatOptions;
[Bindable] public var breakoutOptions:BreakoutRoomsOptions;
[Bindable] private var clrBtnVisible:Boolean = false;
private var globalDispatcher:Dispatcher = new Dispatcher();
@ -87,11 +89,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
handler.populateAllUsers()
users = handler.users;
chatOptions = Options.getOptions(ChatOptions) as ChatOptions;
breakoutOptions = Options.getOptions(BreakoutRoomsOptions) as BreakoutRoomsOptions;
if (!chatOptions.privateEnabled) {
if ((!UsersUtil.isBreakout() && !chatOptions.privateEnabled) ||
(UsersUtil.isBreakout() && !breakoutOptions.privateChateEnabled )
) {
usersList.includeInLayout = usersList.visible = false;
lblSelect.includeInLayout = lblSelect.visible = false;
}
if (fontSizes.indexOf(chatOptions.fontSize) != -1) {
cmbFontSize.selectedItem = chatOptions.fontSize;
changeFontSize(); // have to manually call it because the change event doesn't fire
@ -200,10 +206,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</fx:Declarations>
<common:AdvancedLabel id="lblSelect" styleName="chatOptionsLabel" width="100%"
text="{ResourceUtil.getInstance().getString('bbb.chat.privateChatSelect')}"
visible="{chatOptions.privateEnabled}" includeInLayout="{chatOptions.privateEnabled}"/>
text="{ResourceUtil.getInstance().getString('bbb.chat.privateChatSelect')}" />
<mx:List id="usersList" height="50%" width="100%" dataProvider="{users}" dragEnabled="false"
visible="{chatOptions.privateEnabled}" includeInLayout="{chatOptions.privateEnabled}"
itemRenderer="org.bigbluebutton.modules.chat.views.UserRenderer"
labelField="name"
itemClick="openPrivateChat(event)"

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.modules.chat.views
{
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.model.LiveMeeting;
@ -11,11 +12,15 @@ package org.bigbluebutton.modules.chat.views
public class ChatWindowEventHandler
{
[Bindable] public var users:ArrayCollection = new ArrayCollection();
private var sort:Sort;
public function ChatWindowEventHandler()
{
users.refresh();
sort = new Sort();
sort.compareFunction = sortFunction;
users.sort = sort;
users.refresh();
}
public function populateAllUsers():void {
@ -67,6 +72,23 @@ package org.bigbluebutton.modules.chat.views
public function handleUserLeftEvent(userId: String):void {
removeUser(userId, users);
}
private function sortFunction(a:Object, b:Object, array:Array = null):int {
/*
* Check name (case-insensitive) in the event of a tie up above. If the name
* is the same then use userID which should be unique making the order the same
* across all clients.
*/
if (a.name.toLowerCase() < b.name.toLowerCase())
return -1;
else if (a.name.toLowerCase() > b.name.toLowerCase())
return 1;
else if (a.userId.toLowerCase() > b.userId.toLowerCase())
return -1;
else if (a.userId.toLowerCase() < b.userId.toLowerCase())
return 1;
return 0;
}
}
}
}

View File

@ -27,6 +27,9 @@ package org.bigbluebutton.modules.users.model {
[Bindable]
public var record:Boolean = true;
[Bindable]
public var privateChateEnabled:Boolean = true;
public function BreakoutRoomsOptions() {
name = "breakoutRooms";
}

View File

@ -63,6 +63,8 @@ package org.bigbluebutton.modules.users.services
public var onAllowedToJoin:Function = null;
private static var globalDispatcher:Dispatcher = new Dispatcher();
private static var flashWebcamPattern:RegExp = /^([A-z0-9]+)-([A-z0-9]+)-([A-z0-9]+)$/;
public function MessageReceiver() {
BBB.initConnectionManager().addMessageListener(this);
@ -434,15 +436,18 @@ package org.bigbluebutton.modules.users.services
var userId: String = media.userId as String;
var attributes: Object = media.attributes as Object;
var viewers: Array = media.viewers as Array;
var webcamStream: MediaStream = new MediaStream(streamId, userId);
webcamStream.streamId = streamId;
webcamStream.userId = userId;
webcamStream.attributes = attributes;
webcamStream.viewers = viewers;
LOGGER.debug("STREAM = " + JSON.stringify(webcamStream));
LiveMeeting.inst().webcams.add(webcamStream);
if (isValidFlashWebcamStream(streamId)) {
var webcamStream: MediaStream = new MediaStream(streamId, userId);
webcamStream.streamId = streamId;
webcamStream.userId = userId;
webcamStream.attributes = attributes;
webcamStream.viewers = viewers;
LOGGER.debug("STREAM = " + JSON.stringify(webcamStream));
LiveMeeting.inst().webcams.add(webcamStream);
}
}
}
@ -487,7 +492,7 @@ package org.bigbluebutton.modules.users.services
logData.tags = ["users"];
logData.status = "user_ejected";
logData.message = "User ejected from meeting.";
LOGGER.info(JSON.stringify(logData));
LOGGER.debug(JSON.stringify(logData));
}
private function handleUserLocked(msg:Object):void {
@ -698,21 +703,23 @@ package org.bigbluebutton.modules.users.services
private function handleUserBroadcastCamStartedEvtMsg(msg:Object):void {
var userId: String = msg.body.userId as String;
var streamId: String = msg.body.stream as String;
var logData:Object = UsersUtil.initLogData();
logData.tags = ["webcam"];
logData.message = "UserBroadcastCamStartedEvtMsg server message";
logData.user.webcamStream = streamId;
LOGGER.info(JSON.stringify(logData));
var mediaStream: MediaStream = new MediaStream(streamId, userId)
LiveMeeting.inst().webcams.add(mediaStream);
var webUser: User2x = UsersUtil.getUser(userId);
if (webUser != null) {
sendStreamStartedEvent(userId, webUser.name, streamId);
if (isValidFlashWebcamStream(streamId)) {
LOGGER.info(JSON.stringify(logData));
var mediaStream: MediaStream = new MediaStream(streamId, userId)
LiveMeeting.inst().webcams.add(mediaStream);
var webUser: User2x = UsersUtil.getUser(userId);
if (webUser != null) {
sendStreamStartedEvent(userId, webUser.name, streamId);
}
}
}
private function sendStreamStartedEvent(userId: String, name: String, stream: String):void{
@ -839,6 +846,10 @@ package org.bigbluebutton.modules.users.services
}
}
private function isValidFlashWebcamStream(streamId: String):Boolean{
return flashWebcamPattern.test(streamId);
}
public function handleGuestPolicyChanged(msg:Object):void {
var header: Object = msg.header as Object;
var body: Object = msg.body as Object;

View File

@ -85,12 +85,12 @@
<div >
<h2>BigBlueButton HTML5 client test server</h2>
<p> <a href="http://bigbluebutton.org/" target="_blank">BigBlueButton</a> is an open source web conferencing system for on-line learning. This is a public test server for the BigBlueButton <a href="http://docs.bigbluebutton.org/html/html5-overview.html">HTML5 client</a> currently under development.</p>
<p> Our goal for the upcoming release of the HTML5 client is to implement all the <a href="https://youtu.be/oh0bEk3YSwI">viewer capabilities</a> of the Flash client. Students join online classes as a viewer. The HTML5 client will give remote students the ability to join from their Android mobile devices. Users using the Flash and HTML5 clients can join the same meeting (hence the two choices above). We built the HTML5 client using web real-time communication (WebRTC), <a href="https://facebook.github.io/react/">React</a>, and <a href="https://www.mongodb.com/">MongoDB</a>.</p>
<p> The HTML5 works well with desktop and Android devices (phone and tablets) as they all support WebRTC. Apple does not (yet) support WebRTC in Safari for iOS devices, but don't worry -- we are working in parallel on app for iOS devices. What can this developer build of the HTML5 client do right now? Pretty much everything the Flash client can do for viewers except (a) view a desktop sharing stream from the presenter and (b) send/receive webcam streams. We're working on (a) and (b). For now, we are really happy to share with you our progress and get <a href="https://docs.google.com/forms/d/1gFz5JdN3vD6jxhlVskFYgtEKEcexdDnUzpkwUXwQ4OY/viewform?usp=send_for">your feedback</a> on what has been implemeted so far. Enjoy!</p>
<p> Our goal for the upcoming release of the HTML5 client is to implement all the <a href="https://youtu.be/oh0bEk3YSwI">viewer capabilities</a> of the Flash client. Students join online classes as a viewer. The HTML5 client will give remote students the ability to join from their Android and Apple (iOS 11+) devices. Users using the Flash and HTML5 clients can join the same meeting (hence the two choices above). We built the HTML5 client using web real-time communication (WebRTC), <a href="https://facebook.github.io/react/">React</a>, and <a href="https://www.mongodb.com/">MongoDB</a>.</p>
<p> What can this developer build of the HTML5 client do right now? Pretty much everything the Flash client can do for viewers except (a) view a desktop sharing stream from the presenter and (b) send/receive webcam streams. We're working on (a) and (b). For now, we are really happy to share with you our progress and get <a href="https://docs.google.com/forms/d/1gFz5JdN3vD6jxhlVskFYgtEKEcexdDnUzpkwUXwQ4OY/viewform?usp=send_for">your feedback</a> on what has been implemeted so far. Enjoy!</p>
<h4>For Developers</h4>
<p> The BigBlueButton project is <a href="http://bigbluebutton.org/support">supported</a> by a community of developers that care about good design and a streamlined user experience. </p>
<p>See <a href="/demo/demo1.jsp" target="_blank">API examples </a> for how to integrate BigBlueButton with your project.</p>
<p>See <a href="http://docs.bigblubutton.org" target="_blank">Documentation</a> for more information on how you can integrate BigBlueButton with your project.</p>
</div>
<div class="span one"></div>
@ -98,6 +98,7 @@
<hr class="featurette-divider">
<!-- BigBlueButton Features -->

View File

@ -3,7 +3,6 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
4commerce:env-settings
standard-app-packages@1.0.9
arunoda:npm@0.2.6
amplify
@ -15,11 +14,11 @@ cfs:power-queue
cfs:reactive-list
cfs:micro-queue
reactive-var@1.0.11
ecmascript@0.8.1
ecmascript@0.9.0
react-meteor-data
standard-minifier-css@1.3.4
standard-minifier-js@2.1.1
standard-minifier-css@1.3.5
standard-minifier-js@2.2.0
nathantreid:css-modules
shell-server@0.2.4
http@1.2.12
dynamic-import@0.1.1
shell-server@0.3.0
http@1.3.0
dynamic-import@0.2.0

View File

@ -1 +1 @@
METEOR@1.5.1
METEOR@1.6.0.1

View File

@ -1,16 +1,15 @@
4commerce:env-settings@1.2.0
aldeed:simple-schema@1.5.3
allow-deny@1.0.6
allow-deny@1.1.0
amplify@1.0.0
arunoda:npm@0.2.6
autoupdate@1.3.12
babel-compiler@6.19.4
babel-runtime@1.0.1
babel-compiler@6.24.7
babel-runtime@1.1.1
base64@1.0.10
binary-heap@1.0.10
blaze@2.3.2
blaze-tools@1.0.10
boilerplate-generator@1.1.1
boilerplate-generator@1.3.1
caching-compiler@1.1.9
caching-html-compiler@1.1.2
callback-hook@1.0.10
@ -21,74 +20,74 @@ cfs:reactive-list@0.0.9
cfs:reactive-property@0.0.4
check@1.2.5
clinical:nightwatch@2.0.1
coffeescript@1.12.6_1
ddp@1.3.0
ddp-client@2.0.0
ddp-common@1.2.9
ddp-server@2.0.0
coffeescript@1.12.7_3
coffeescript-compiler@1.12.7_3
ddp@1.4.0
ddp-client@2.2.0
ddp-common@1.3.0
ddp-server@2.1.1
deps@1.0.12
diff-sequence@1.0.7
dynamic-import@0.1.1
ecmascript@0.8.2
ecmascript-runtime@0.4.1
ecmascript-runtime-client@0.4.3
ecmascript-runtime-server@0.4.1
ejson@1.0.13
dynamic-import@0.2.1
ecmascript@0.9.0
ecmascript-runtime@0.5.0
ecmascript-runtime-client@0.5.0
ecmascript-runtime-server@0.5.0
ejson@1.1.0
fastclick@1.0.13
francocatena:status@1.5.3
geojson-utils@1.0.10
html-tools@1.0.11
htmljs@1.0.11
http@1.2.12
http@1.3.0
id-map@1.0.9
jquery@1.11.10
launch-screen@1.1.1
livedata@1.0.18
logging@1.1.17
logging@1.1.19
mdg:validation-error@0.5.1
meteor@1.7.0
meteor@1.8.2
meteor-platform@1.2.6
meteorblackbelt:underscore-deep@0.0.4
meteorspark:util@0.2.0
minifier-css@1.2.16
minifier-js@2.1.1
minimongo@1.2.1
minifier-js@2.2.2
minimongo@1.4.3
mizzao:timesync@0.5.0
mobile-status-bar@1.0.14
modules@0.9.2
modules-runtime@0.8.0
mongo@1.1.22
modules@0.11.2
modules-runtime@0.9.1
mongo@1.3.1
mongo-dev-server@1.1.0
mongo-id@1.0.6
nathantreid:css-modules@2.7.3
npm-mongo@2.2.30
nathantreid:css-modules@2.8.0
npm-mongo@2.2.33
observe-sequence@1.0.16
ordered-dict@1.0.9
promise@0.8.9
promise@0.10.0
raix:eventemitter@0.1.3
random@1.0.10
react-meteor-data@0.2.15
reactive-dict@1.1.9
reactive-dict@1.2.0
reactive-var@1.0.11
reload@1.1.11
retry@1.0.9
routepolicy@1.0.12
session@1.1.7
shell-server@0.2.4
shell-server@0.3.1
spacebars@1.0.15
spacebars-compiler@1.1.2
spacebars-compiler@1.1.3
standard-app-packages@1.0.9
standard-minifier-css@1.3.4
standard-minifier-js@2.1.1
standard-minifier-css@1.3.5
standard-minifier-js@2.2.3
tap:i18n@1.8.2
templating@1.3.2
templating-compiler@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tmeasday:check-npm-versions@0.3.1
tracker@1.1.3
udondan:yml@3.2.2_1
ui@1.0.13
underscore@1.0.10
url@1.1.0
webapp@1.3.17
webapp@1.4.0
webapp-hashing@1.0.9

View File

@ -53,8 +53,14 @@
<script src="/client/lib/jquery.json-2.4.min.js"></script>
<script src="/client/lib/verto-min.js"></script>
<script src="/client/lib/verto_extension.js"></script>
<script src="/client/lib/reconnecting-websocket.min.js"></script>
<script src="/client/lib/kurento-utils.js"></script>
<script src="/client/lib/kurento-extension.js"></script>
<script src="/html5client/js/adapter.js"></script>
<script src="/html5client/js/adjust-videos.js"></script>
<audio id="remote-media" autoPlay="autoplay">
<track kind="captions" /> {/* These captions are brought to you by eslint */}
<track kind="captions" /> {/* These captions are brought to you by eslint */}
</audio>
</body>

2
bigbluebutton-html5/client/stylesheets/bbb-icons.css Executable file → Normal file
View File

@ -53,7 +53,7 @@
.icon-bbb-fit_to_screen:before {
content: "\e929";
}
.icon-bbb-linte_tool:before {
.icon-bbb-line_tool:before {
content: "\e91c";
}
.icon-bbb-circle_tool:before {

View File

@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export default function clearWhiteboard(credentials, whiteboardId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ClearWhiteboardPubMsg';
@ -18,9 +18,7 @@ export default function clearWhiteboard(credentials, whiteboardId) {
const allowed = Acl.can('methods.clearWhiteboard', credentials) || getMultiUserStatus(meetingId);
if (!allowed) {
throw new Meteor.Error(
'not-allowed', `User ${requesterUserId} is not allowed to clear the whiteboard`,
);
throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to clear the whiteboard`);
}
const payload = {

View File

@ -22,7 +22,7 @@ function isLastMessage(annotation, userId) {
}
export default function sendAnnotation(credentials, annotation) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'SendWhiteboardAnnotationPubMsg';
@ -45,9 +45,7 @@ export default function sendAnnotation(credentials, annotation) {
isLastMessage(annotation, requesterUserId);
if (!allowed) {
throw new Meteor.Error(
'not-allowed', `User ${requesterUserId} is not allowed to send an annotation`,
);
throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to send an annotation`);
}
const payload = {

View File

@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export default function undoAnnotation(credentials, whiteboardId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'UndoWhiteboardPubMsg';
@ -18,9 +18,7 @@ export default function undoAnnotation(credentials, whiteboardId) {
const allowed = Acl.can('methods.undoAnnotation', credentials) || getMultiUserStatus(meetingId);
if (!allowed) {
throw new Meteor.Error(
'not-allowed', `User ${requesterUserId} is not allowed to undo the annotation`,
);
throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to undo the annotation`);
}
const payload = {

View File

@ -7,7 +7,9 @@ const ANNOTATION_TYPE_PENCIL = 'pencil';
// line, triangle, ellipse, rectangle
function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) {
const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
const {
id, status, annotationType, annotationInfo, wbId, position,
} = annotation;
const selector = {
meetingId,
@ -33,7 +35,9 @@ function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) {
}
function handleTextUpdate(meetingId, whiteboardId, userId, annotation) {
const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
const {
id, status, annotationType, annotationInfo, wbId, position,
} = annotation;
const selector = {
meetingId,
@ -67,10 +71,12 @@ function handlePencilUpdate(meetingId, whiteboardId, userId, annotation) {
const DRAW_UPDATE = ANOTATION_STATUSES.update;
const DRAW_END = ANOTATION_STATUSES.end;
const SERVER_CONFIG = Meteor.settings.app;
const SERVER_CONFIG = Meteor.settings.private.app;
const PENCIL_CHUNK_SIZE = SERVER_CONFIG.pencilChunkLength || 100;
const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
const {
id, status, annotationType, annotationInfo, wbId, position,
} = annotation;
const baseSelector = {
meetingId,

View File

@ -1,57 +1,57 @@
import _ from 'lodash';
import Captions from '/imports/api/captions';
import { check } from 'meteor/check';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionHistory({ body }, meetingId) {
const SERVER_CONFIG = Meteor.settings.app;
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
const captionHistory = body.history;
check(meetingId, String);
check(captionHistory, Object);
const captionsAdded = [];
_.each(captionHistory, (caption, locale) => {
const ownerId = caption[0];
let captions = caption[1].slice(0);
const chunks = [];
if (captions.length === 0) {
chunks.push('');
} else {
while (captions.length > 0) {
if (captions.length > CAPTION_CHUNK_LENGTH) {
chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
captions = captions.slice(CAPTION_CHUNK_LENGTH);
} else {
chunks.push(captions);
captions = captions.slice(captions.length);
}
}
}
const selectorToRemove = {
meetingId,
locale,
'captionHistory.index': { $gt: (chunks.length - 1) },
};
Captions.remove(selectorToRemove);
chunks.forEach((chunkCaptions, index) => {
const captionHistoryObject = {
locale,
ownerId,
chunkCaptions,
index,
next: (index < chunks.length - 1) ? index + 1 : undefined,
};
captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
});
});
return captionsAdded;
}
import _ from 'lodash';
import Captions from '/imports/api/captions';
import { check } from 'meteor/check';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionHistory({ body }, meetingId) {
const SERVER_CONFIG = Meteor.settings.private.app;
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
const captionHistory = body.history;
check(meetingId, String);
check(captionHistory, Object);
const captionsAdded = [];
_.each(captionHistory, (caption, locale) => {
const ownerId = caption[0];
let captions = caption[1].slice(0);
const chunks = [];
if (captions.length === 0) {
chunks.push('');
} else {
while (captions.length > 0) {
if (captions.length > CAPTION_CHUNK_LENGTH) {
chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
captions = captions.slice(CAPTION_CHUNK_LENGTH);
} else {
chunks.push(captions);
captions = captions.slice(captions.length);
}
}
}
const selectorToRemove = {
meetingId,
locale,
'captionHistory.index': { $gt: (chunks.length - 1) },
};
Captions.remove(selectorToRemove);
chunks.forEach((chunkCaptions, index) => {
const captionHistoryObject = {
locale,
ownerId,
chunkCaptions,
index,
next: (index < chunks.length - 1) ? index + 1 : undefined,
};
captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
});
});
return captionsAdded;
}

View File

@ -1,178 +1,181 @@
import Captions from '/imports/api/captions';
import { check } from 'meteor/check';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionUpdate({ body }, meetingId) {
const SERVER_CONFIG = Meteor.settings.app;
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
const { locale } = body;
check(meetingId, String);
check(locale, String);
const captionsObjects = Captions.find({
meetingId,
locale,
}, {
sort: {
locale: 1,
'captionHistory.index': 1,
},
}).fetch();
const objectsToUpdate = [];
if (captionsObjects != null) {
let startIndex;
let endIndex;
let length = 0;
let current = captionsObjects[0];
// looking for a start index and end index
// (end index only for the case when they are in the same block)
while (current != null) {
length += current.captionHistory.captions.length;
// if length is bigger than start index - we found our start index
if (length >= body.startIndex && startIndex == undefined) {
// check if it's a new character somewhere in the middle of captions text
if (length - 1 >= body.startIndex) {
startIndex = body.startIndex - (length - current.captionHistory.captions.length);
// check to see if the endIndex is in the same object as startIndex
if (length - 1 >= body.endIndex) {
endIndex = body.endIndex - (length - current.captionHistory.captions.length);
const _captions = current.captionHistory.captions;
current.captionHistory.captions = _captions.slice(0, startIndex) +
body.text +
_captions.slice(endIndex);
objectsToUpdate.push(current);
break;
// end index is not in the same object as startIndex, we will find it later
} else {
current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
body.text;
objectsToUpdate.push(current);
break;
}
// separate case for appending new characters to the very end of the string
} else if (current.captionHistory.next == null &&
length == body.startIndex &&
length == body.startIndex) {
startIndex = 1;
endIndex = 1;
current.captionHistory.captions += body.text;
objectsToUpdate.push(current);
}
}
current = captionsObjects[current.captionHistory.next];
}
// looking for end index here if it wasn't in the same object as start index
if (startIndex != undefined && endIndex == undefined) {
current = captionsObjects[current.captionHistory.next];
while (current != null) {
length += current.captionHistory.captions.length;
// check to see if the endIndex is in the current object
if (length - 1 >= body.endIndex) {
endIndex = body.endIndex - (length - current.captionHistory.captions.length);
current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
objectsToUpdate.push(current);
break;
// if endIndex wasn't in the current object, that means this whole object was deleted
// initializing string to ''
} else {
current.captionHistory.captions = '';
objectsToUpdate.push(current);
}
current = captionsObjects[current.captionHistory.next];
}
}
// looking for the strings which exceed the limit and split them into multiple objects
let maxIndex = captionsObjects.length - 1;
for (let i = 0; i < objectsToUpdate.length; i++) {
if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
// string is too large. Check if the next object exists and if it can
// accomodate the part of the string that exceeds the limits
const _nextIndex = objectsToUpdate[i].captionHistory.next;
if (_nextIndex != null &&
captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
// could assign it directly, but our linter complained
let _captions = objectsToUpdate[i].captionHistory.captions;
_captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
objectsToUpdate[i].captionHistory.captions = _captions;
// check to see if the next object was added to objectsToUpdate array
if (objectsToUpdate[i + 1] != null &&
objectsToUpdate[i].captionHistory.next == objectsToUpdate[i + 1].captionHistory.index) {
objectsToUpdate[i + 1].captionHistory.captions = extraString +
objectsToUpdate[i + 1].captionHistory.captions;
// next object wasn't added to objectsToUpdate array, adding it from captionsObjects array.
} else {
const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
objectsToUpdate.push(nextObj);
}
// next object was full already, so we create another and insert it in between them
} else {
// need to take a current object out of the objectsToUpdate and add it back after
// every other object, so that Captions collection could be updated in a proper order
const tempObj = objectsToUpdate.splice(i, 1);
let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
tempObj[0].captionHistory.captions =
tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
maxIndex += 1;
const tempIndex = tempObj[0].captionHistory.next;
tempObj[0].captionHistory.next = maxIndex;
while (extraString.length != 0) {
const entry = {
meetingId,
locale,
captionHistory: {
locale,
ownerId: tempObj[0].captionHistory.ownerId,
captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
index: maxIndex,
next: null,
},
};
maxIndex += 1;
extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
if (extraString.length > 0) {
entry.captionHistory.next = maxIndex;
} else {
entry.captionHistory.next = tempIndex;
}
objectsToUpdate.push(entry);
}
objectsToUpdate.push(tempObj[0]);
}
}
}
}
const captionsAdded = [];
objectsToUpdate.forEach((entry) => {
const { _id, meetingId, locale, captionHistory } = entry;
captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
});
return captionsAdded;
}
import Captions from '/imports/api/captions';
import { check } from 'meteor/check';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionUpdate({ body }, meetingId) {
const SERVER_CONFIG = Meteor.settings.private.app;
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
const { locale } = body;
check(meetingId, String);
check(locale, String);
const captionsObjects = Captions.find({
meetingId,
locale,
}, {
sort: {
locale: 1,
'captionHistory.index': 1,
},
}).fetch();
const objectsToUpdate = [];
if (captionsObjects != null) {
let startIndex;
let endIndex;
let length = 0;
let current = captionsObjects[0];
// looking for a start index and end index
// (end index only for the case when they are in the same block)
while (current != null) {
length += current.captionHistory.captions.length;
// if length is bigger than start index - we found our start index
if (length >= body.startIndex && startIndex === undefined) {
// check if it's a new character somewhere in the middle of captions text
if (length - 1 >= body.startIndex) {
startIndex = body.startIndex - (length - current.captionHistory.captions.length);
// check to see if the endIndex is in the same object as startIndex
if (length - 1 >= body.endIndex) {
endIndex = body.endIndex - (length - current.captionHistory.captions.length);
const _captions = current.captionHistory.captions;
current.captionHistory.captions = _captions.slice(0, startIndex) +
body.text +
_captions.slice(endIndex);
objectsToUpdate.push(current);
break;
// end index is not in the same object as startIndex, we will find it later
} else {
current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
body.text;
objectsToUpdate.push(current);
break;
}
// separate case for appending new characters to the very end of the string
} else if (current.captionHistory.next == null &&
length === body.startIndex &&
length === body.startIndex) {
startIndex = 1;
endIndex = 1;
current.captionHistory.captions += body.text;
objectsToUpdate.push(current);
}
}
current = captionsObjects[current.captionHistory.next];
}
// looking for end index here if it wasn't in the same object as start index
if (startIndex !== undefined && endIndex === undefined) {
current = captionsObjects[current.captionHistory.next];
while (current != null) {
length += current.captionHistory.captions.length;
// check to see if the endIndex is in the current object
if (length - 1 >= body.endIndex) {
endIndex = body.endIndex - (length - current.captionHistory.captions.length);
current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
objectsToUpdate.push(current);
break;
// if endIndex wasn't in the current object, that means this whole object was deleted
// initializing string to ''
} else {
current.captionHistory.captions = '';
objectsToUpdate.push(current);
}
current = captionsObjects[current.captionHistory.next];
}
}
// looking for the strings which exceed the limit and split them into multiple objects
let maxIndex = captionsObjects.length - 1;
for (let i = 0; i < objectsToUpdate.length; i += 1) {
if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
// string is too large. Check if the next object exists and if it can
// accomodate the part of the string that exceeds the limits
const _nextIndex = objectsToUpdate[i].captionHistory.next;
if (_nextIndex != null &&
captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
// could assign it directly, but our linter complained
let _captions = objectsToUpdate[i].captionHistory.captions;
_captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
objectsToUpdate[i].captionHistory.captions = _captions;
// check to see if the next object was added to objectsToUpdate array
if (objectsToUpdate[i + 1] != null &&
objectsToUpdate[i].captionHistory.next === objectsToUpdate[i + 1].captionHistory.index) {
objectsToUpdate[i + 1].captionHistory.captions = extraString +
objectsToUpdate[i + 1].captionHistory.captions;
// next object wasn't added to objectsToUpdate array
// adding it from captionsObjects array.
} else {
const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
objectsToUpdate.push(nextObj);
}
// next object was full already, so we create another and insert it in between them
} else {
// need to take a current object out of the objectsToUpdate and add it back after
// every other object, so that Captions collection could be updated in a proper order
const tempObj = objectsToUpdate.splice(i, 1);
let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
tempObj[0].captionHistory.captions =
tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
maxIndex += 1;
const tempIndex = tempObj[0].captionHistory.next;
tempObj[0].captionHistory.next = maxIndex;
while (extraString.length !== 0) {
const entry = {
meetingId,
locale,
captionHistory: {
locale,
ownerId: tempObj[0].captionHistory.ownerId,
captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
index: maxIndex,
next: null,
},
};
maxIndex += 1;
extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
if (extraString.length > 0) {
entry.captionHistory.next = maxIndex;
} else {
entry.captionHistory.next = tempIndex;
}
objectsToUpdate.push(entry);
}
objectsToUpdate.push(tempObj[0]);
}
}
}
}
const captionsAdded = [];
objectsToUpdate.forEach((entry) => {
const {
_id, captionHistory,
} = entry;
captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
});
return captionsAdded;
}

View File

@ -3,7 +3,7 @@ import { check } from 'meteor/check';
import RedisPubSub from '/imports/startup/server/redis';
export default function clearPublicChatHistory(credentials) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const { meetingId, requesterUserId, requesterToken } = credentials;

View File

@ -27,7 +27,7 @@ const parseMessage = (message) => {
};
export default function sendChat(credentials, message) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const CHAT_CONFIG = Meteor.settings.public.chat;
@ -50,6 +50,8 @@ export default function sendChat(credentials, message) {
eventName = 'SendPublicMessagePubMsg';
}
return RedisPubSub.publishUserMessage(CHANNEL, eventName, meetingId, requesterUserId,
{ message: parsedMessage });
return RedisPubSub.publishUserMessage(
CHANNEL, eventName, meetingId, requesterUserId,
{ message: parsedMessage },
);
}

View File

@ -6,7 +6,7 @@ import { check } from 'meteor/check';
export default function publishCursorUpdate(credentials, payload) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'SendCursorPositionPubMsg';
@ -22,9 +22,7 @@ export default function publishCursorUpdate(credentials, payload) {
const allowed = Acl.can('methods.moveCursor', credentials) || getMultiUserStatus(meetingId);
if (!allowed) {
throw new Meteor.Error(
'not-allowed', `User ${requesterUserId} is not allowed to move the cursor`,
);
throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to move the cursor`);
}
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);

View File

@ -4,7 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
import Logger from '/imports/startup/server/logger';
export default function endMeeting(credentials) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'LogoutAndEndMeetingCmdMsg';

View File

@ -51,6 +51,7 @@ export default function addMeeting(meeting) {
voiceConf: String,
dialNumber: String,
telVoice: String,
muteOnStart: Boolean,
},
screenshareProps: {
red5ScreenshareIp: String,

View File

@ -7,4 +7,4 @@ import handleUserVoted from './handlers/userVoted';
RedisPubSub.on('PollShowResultEvtMsg', handlePollPublished);
RedisPubSub.on('PollStartedEvtMsg', handlePollStarted);
RedisPubSub.on('PollStoppedEvtMsg', handlePollStopped);
RedisPubSub.on('UserRespondedToPollEvtMsg', handleUserVoted);
RedisPubSub.on('PollUpdatedEvtMsg', handleUserVoted);

View File

@ -3,11 +3,20 @@ import updateVotes from '../modifiers/updateVotes';
export default function userVoted({ body }, meetingId) {
const { poll } = body;
const { presenterId } = body;
check(meetingId, String);
check(poll, Object);
check(presenterId, String);
check(poll, {
id: String,
answers: [
{
id: Number,
key: String,
numVotes: Number,
},
],
numRespondents: Number,
numResponders: Number,
});
return updateVotes(poll, meetingId, presenterId);
return updateVotes(poll, meetingId);
}

View File

@ -4,12 +4,17 @@ import Polls from '/imports/api/polls';
import Logger from '/imports/startup/server/logger';
export default function publishVote(credentials, id, pollAnswerId) { // TODO discuss location
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'RespondToPollReqMsg';
const { meetingId, requesterUserId } = credentials;
/*
We keep an array of people who were in the meeting at the time the poll
was started. The poll is published to them only.
Once they vote - their ID is removed and they cannot see the poll anymore
*/
const currentPoll = Polls.findOne({
users: requesterUserId,
meetingId,

View File

@ -3,19 +3,17 @@ import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import flat from 'flat';
export default function updateVotes(poll, meetingId, requesterId) {
export default function updateVotes(poll, meetingId) {
check(meetingId, String);
check(requesterId, String);
check(poll, Object);
const {
id,
answers,
numResponders,
numRespondents,
} = poll;
const { numResponders } = poll;
const { numRespondents } = poll;
check(id, String);
check(answers, Array);
@ -24,15 +22,11 @@ export default function updateVotes(poll, meetingId, requesterId) {
const selector = {
meetingId,
requester: requesterId,
id,
};
const modifier = {
$set: Object.assign(
{ requester: requesterId },
flat(poll, { safe: true }),
),
$set: flat(poll, { safe: true }),
};
const cb = (err) => {

View File

@ -7,6 +7,7 @@ import handlePresentationConversionUpdate from './handlers/presentationConversio
RedisPubSub.on('SyncGetPresentationInfoRespMsg', handlePresentationInfoReply);
RedisPubSub.on('PresentationPageGeneratedEvtMsg', handlePresentationConversionUpdate);
RedisPubSub.on('PresentationPageCountErrorEvtMsg', handlePresentationConversionUpdate);
RedisPubSub.on('PresentationConversionUpdateEvtMsg', handlePresentationConversionUpdate);
RedisPubSub.on('PresentationConversionCompletedEvtMsg', handlePresentationAdded);
RedisPubSub.on('RemovePresentationEvtMsg', handlePresentationRemove);

View File

@ -49,6 +49,7 @@ export default function handlePresentationConversionUpdate({ body }, meetingId)
statusModifier.id = presentationId;
statusModifier.name = presentationName;
statusModifier['conversion.error'] = true;
statusModifier['conversion.done'] = true;
break;
case GENERATED_SLIDE_KEY:

View File

@ -4,7 +4,7 @@ import Presentations from '/imports/api/presentations';
export default function removePresentation(credentials, presentationId) {
const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'RemovePresentationPubMsg';

View File

@ -3,7 +3,7 @@ import { check } from 'meteor/check';
import Presentations from '/imports/api/presentations';
export default function setPresentation(credentials, presentationId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'SetCurrentPresentationPubMsg';

View File

@ -1,5 +1,7 @@
import VertoBridge from './verto';
import KurentoBridge from './kurento';
const screenshareBridge = new VertoBridge();
//const screenshareBridge = new VertoBridge();
const screenshareBridge = new KurentoBridge();
export default screenshareBridge;

View File

@ -0,0 +1,51 @@
import Users from '/imports/api/users';
import Auth from '/imports/ui/services/auth';
import BridgeService from './service';
const CHROME_EXTENSION_KEY = Meteor.settings.public.kurento.chromeExtensionKey;
const getUserId = () => {
const userID = Auth.userID;
return userID;
}
const getMeetingId = () => {
const meetingID = Auth.meetingID;
return meetingID;
}
const getUsername = () => {
return Users.findOne({ userId: getUserId() }).name;
}
export default class KurentoScreenshareBridge {
kurentoWatchVideo() {
window.kurentoWatchVideo(
'screenshareVideo',
BridgeService.getConferenceBridge(),
getUsername(),
getMeetingId(),
null,
null,
);
}
kurentoExitVideo() {
window.kurentoExitVideo();
}
kurentoShareScreen() {
window.kurentoShareScreen(
'screenshareVideo',
BridgeService.getConferenceBridge(),
getUsername(),
getMeetingId(),
null,
CHROME_EXTENSION_KEY,
);
}
kurentoExitScreenShare() {
window.kurentoExitScreenShare();
}
}

View File

@ -1,7 +1,7 @@
import { check } from 'meteor/check';
import addScreenshare from '../modifiers/addScreenshare';
export default function handleBroadcastStartedVoice({ body }, meetingId) {
export default function handleScreenshareStarted({ body }, meetingId) {
check(meetingId, String);
check(body, Object);

View File

@ -1,7 +1,7 @@
import { check } from 'meteor/check';
import clearScreenshare from '../modifiers/clearScreenshare';
export default function handleBroadcastStartedVoice({ body }, meetingId) {
export default function handleScreenshareStopped({ body }, meetingId) {
const { screenshareConf } = body;
check(meetingId, String);

View File

@ -5,7 +5,7 @@ import { check } from 'meteor/check';
import RedisPubSub from '/imports/startup/server/redis';
export default function switchSlide(credentials, slideNumber) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'SetCurrentPagePubMsg';
@ -25,8 +25,7 @@ export default function switchSlide(credentials, slideNumber) {
const Presentation = Presentations.findOne(selector);
if (!Presentation) {
throw new Meteor.Error(
'presentation-not-found', 'You need a presentation to be able to switch slides');
throw new Meteor.Error('presentation-not-found', 'You need a presentation to be able to switch slides');
}
const Slide = Slides.findOne({
@ -36,8 +35,7 @@ export default function switchSlide(credentials, slideNumber) {
});
if (!Slide) {
throw new Meteor.Error(
'slide-not-found', `Slide number ${slideNumber} not found in the current presentation`);
throw new Meteor.Error('slide-not-found', `Slide number ${slideNumber} not found in the current presentation`);
}
const payload = {

View File

@ -9,7 +9,7 @@ import { SVG, PNG } from '/imports/utils/mimeTypes';
import calculateSlideData from '/imports/api/slides/server/helpers';
const requestWhiteboardHistory = (meetingId, slideId) => {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'GetWhiteboardAnnotationsReqMsg';
const USER_ID = 'nodeJSapp';
@ -27,9 +27,7 @@ const fetchImageSizes = imageUri =>
probe(imageUri)
.then((result) => {
if (!SUPPORTED_TYPES.includes(result.mime)) {
throw new Meteor.Error(
'invalid-image-type', `received ${result.mime} expecting ${SUPPORTED_TYPES.join()}`,
);
throw new Meteor.Error('invalid-image-type', `received ${result.mime} expecting ${SUPPORTED_TYPES.join()}`);
}
return {

View File

@ -69,9 +69,7 @@ export default function handleValidateAuthToken({ body }, meetingId) {
addWelcomeChatMessage(meetingId, userId);
}
return Logger.info(`Validated auth token as ${valid
}${+' user='}${userId} meeting=${meetingId}`,
);
return Logger.info(`Validated auth token as ${valid} user=${userId} meeting=${meetingId}`);
}
return Logger.info('No auth to validate');

View File

@ -5,7 +5,7 @@ import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
export default function assignPresenter(credentials, userId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'AssignPresenterReqMsg';

View File

@ -5,7 +5,7 @@ import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
export default function changeRole(credentials, userId, role) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ChangeUserRoleCmdMsg';
@ -22,8 +22,7 @@ export default function changeRole(credentials, userId, role) {
});
if (!User) {
throw new Meteor.Error(
'user-not-found', `You need a valid user to be able to set '${role}'`);
throw new Meteor.Error('user-not-found', `You need a valid user to be able to set '${role}'`);
}
const payload = {

View File

@ -3,7 +3,7 @@ import { check } from 'meteor/check';
import RedisPubSub from '/imports/startup/server/redis';
export default function kickUser(credentials, userId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'EjectUserFromMeetingCmdMsg';

View File

@ -4,7 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
import Logger from '/imports/startup/server/logger';
export default function setEmojiStatus(credentials, userId, status) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ChangeUserEmojiCmdMsg';

View File

@ -4,7 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
import Logger from '/imports/startup/server/logger';
export default function userJoin(meetingId, userId, authToken) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'UserJoinMeetingReqMsg';

View File

@ -7,7 +7,7 @@ import Users from '/imports/api/users';
const OFFLINE_CONNECTION_STATUS = 'offline';
export default function userLeaving(credentials, userId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'UserLeaveReqMsg';
@ -24,8 +24,7 @@ export default function userLeaving(credentials, userId) {
const User = Users.findOne(selector);
if (!User) {
throw new Meteor.Error(
'user-not-found', `Could not find ${userId} in ${meetingId}: cannot complete userLeaving`);
throw new Meteor.Error('user-not-found', `Could not find ${userId} in ${meetingId}: cannot complete userLeaving`);
}
if (User.connectionStatus === OFFLINE_CONNECTION_STATUS) {

View File

@ -9,7 +9,7 @@ import setConnectionStatus from '../modifiers/setConnectionStatus';
const ONLINE_CONNECTION_STATUS = 'online';
export default function validateAuthToken(credentials) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ValidateAuthTokenReqMsg';
@ -37,7 +37,7 @@ export default function validateAuthToken(credentials) {
Logger.info(`User '${
requesterUserId
}' is trying to validate auth token for meeting '${meetingId}'`);
}' is trying to validate auth token for meeting '${meetingId}'`);
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -27,10 +27,8 @@ export default function createDummyUser(meetingId, userId, authToken) {
return;
}
if (numChanged) {
Logger.info(`Created dummy user 2x id=${userId} token=${authToken} meeting=${meetingId}`);
Logger.info(`Created dummy user id=${userId} token=${authToken} meeting=${meetingId}`);
}
Logger.info(`Created dummy user id=${userId} token=${authToken} meeting=${meetingId}`);
};
return Users.insert(doc, cb);

View File

@ -0,0 +1,6 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleUserSharedHtml5Webcam from './handlers/userSharedHtml5Webcam';
import handleUserUnsharedHtml5Webcam from './handlers/userUnsharedHtml5Webcam';
RedisPubSub.on('UserBroadcastCamStartedEvtMsg', handleUserSharedHtml5Webcam);
RedisPubSub.on('UserBroadcastCamStoppedEvtMsg', handleUserUnsharedHtml5Webcam);

View File

@ -0,0 +1,19 @@
import sharedWebcam from '../modifiers/sharedWebcam';
import { check } from 'meteor/check';
export default function handleUserSharedHtml5Webcam({ header, body }, meetingId ) {
const { userId, stream } = body;
const isValidStream = Match.Where((stream) => {
check(stream, String);
// Checking if the stream name is a flash one
const regexp = /^([A-z0-9]+)-([A-z0-9]+)-([A-z0-9]+)$/;
return !regexp.test(stream);
});
check(header, Object);
check(meetingId, String);
check(userId, String);
check(stream, isValidStream);
return sharedWebcam(meetingId, userId);
}

View File

@ -0,0 +1,19 @@
import unsharedWebcam from '../modifiers/unsharedWebcam';
import { check } from 'meteor/check';
export default function handleUserUnsharedHtml5Webcam({ header, body }, meetingId) {
const { userId, stream } = body;
const isValidStream = Match.Where((stream) => {
check(stream, String);
// Checking if the stream name is a flash one
const regexp = /^([A-z0-9]+)-([A-z0-9]+)-([A-z0-9]+)$/;
return !regexp.test(stream);
});
check(header, Object);
check(meetingId, String);
check(userId, String);
check(stream, isValidStream);
return unsharedWebcam(meetingId, userId);
}

View File

@ -0,0 +1,2 @@
import './eventHandlers';
import './methods';

View File

@ -0,0 +1,7 @@
import { Meteor } from 'meteor/meteor';
import userShareWebcam from './methods/userShareWebcam';
import userUnshareWebcam from './methods/userUnshareWebcam';
Meteor.methods({
userShareWebcam, userUnshareWebcam,
});

View File

@ -0,0 +1,31 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import RedisPubSub from '/imports/startup/server/redis';
export default function userShareWebcam(credentials, message) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'UserBroadcastCamStartMsg';
const { meetingId, requesterUserId, requesterToken } = credentials;
Logger.info(' user sharing webcam: ', credentials);
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
// check(message, Object);
// const actionName = 'joinVideo';
/* TODO throw an error if user has no permission to share webcam
if (!isAllowedTo(actionName, credentials)) {
throw new Meteor.Error('not-allowed', `You are not allowed to share webcam`);
} */
const payload = {
stream: message,
};
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -0,0 +1,31 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import RedisPubSub from '/imports/startup/server/redis';
export default function userUnshareWebcam(credentials, message) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'UserBroadcastCamStopMsg';
const { meetingId, requesterUserId, requesterToken } = credentials;
Logger.info(' user unsharing webcam: ', credentials);
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
// check(message, Object);
// const actionName = 'joinVideo';
/* TODO throw an error if user has no permission to share webcam
if (!isAllowedTo(actionName, credentials)) {
throw new Meteor.Error('not-allowed', `You are not allowed to share webcam`);
} */
const payload = {
stream: message,
};
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -0,0 +1,33 @@
import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
import { check } from 'meteor/check';
export default function sharedWebcam(meetingId, userId) {
check(meetingId, String);
check(userId, String);
const selector = {
meetingId,
userId,
};
const modifier = {
$set: {
meetingId,
userId,
has_stream: true,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding user to collection: ${err}`);
}
if (numChanged) {
return Logger.info(`Upserted user id=${userId} meeting=${meetingId}`);
}
};
return Users.upsert(selector, modifier, cb);
}

View File

@ -0,0 +1,33 @@
import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
import { check } from 'meteor/check';
export default function unsharedWebcam(meetingId, userId) {
check(meetingId, String);
check(userId, String);
const selector = {
meetingId,
userId,
};
const modifier = {
$set: {
meetingId,
userId,
has_stream: false,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding user to collection: ${err}`);
}
if (numChanged) {
return Logger.info(`Upserted user id=${userId} meeting=${meetingId}`);
}
};
return Users.upsert(selector, modifier, cb);
}

View File

@ -1,5 +1,7 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
import addVoiceUser from '../modifiers/addVoiceUser';
export default function handleJoinVoiceUser({ body }, meetingId) {
@ -7,6 +9,74 @@ export default function handleJoinVoiceUser({ body }, meetingId) {
voiceUser.joined = true;
check(meetingId, String);
check(voiceUser, {
voiceConf: String,
intId: String,
voiceUserId: String,
callerName: String,
callerNum: String,
muted: Boolean,
talking: Boolean,
callingWith: String,
listenOnly: Boolean,
joined: Boolean,
});
const {
intId,
callerName,
} = voiceUser;
if (intId.toString().startsWith('v_')) {
/* voice-only user - called into the conference */
const selector = {
meetingId,
userId: intId,
};
const USER_CONFIG = Meteor.settings.public.user;
const ROLE_VIEWER = USER_CONFIG.role_viewer;
const modifier = {
$set: {
meetingId,
connectionStatus: 'online',
roles: [ROLE_VIEWER.toLowerCase()],
sortName: callerName.trim().toLowerCase(),
color: '#ffffff', // TODO
intId,
extId: intId, // TODO
name: callerName,
role: ROLE_VIEWER.toLowerCase(),
guest: false,
authed: true,
waitingForAcceptance: false,
emoji: 'none',
presenter: false,
locked: false, // TODO
avatar: '',
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding call-in user to VoiceUser collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added a call-in user id=${intId} meeting=${meetingId}`);
}
return Logger.info(`Upserted a call-in user id=${intId} meeting=${meetingId}`);
};
Users.upsert(selector, modifier, cb);
} else {
/* there is a corresponding web user in Users collection -- no need to add new one */
}
return addVoiceUser(meetingId, voiceUser);
}

View File

@ -1,11 +1,20 @@
import { check } from 'meteor/check';
import removeVoiceUser from '../modifiers/removeVoiceUser';
import removeVoiceUser from '/imports/api/voice-users/server/modifiers/removeVoiceUser';
import removeUser from '/imports/api/users/server/modifiers/removeUser';
export default function handleVoiceUpdate({ body }, meetingId) {
const voiceUser = body;
check(meetingId, String);
check(voiceUser, {
voiceConf: String,
intId: String,
voiceUserId: String,
});
const { intId } = voiceUser;
removeUser(meetingId, intId);
return removeVoiceUser(meetingId, voiceUser);
}

View File

@ -2,10 +2,12 @@ import { Meteor } from 'meteor/meteor';
import mapToAcl from '/imports/startup/mapToAcl';
import listenOnlyToggle from './methods/listenOnlyToggle';
import muteToggle from './methods/muteToggle';
import ejectUserFromVoice from './methods/ejectUserFromVoice';
Meteor.methods(mapToAcl(['methods.listenOnlyToggle', 'methods.toggleSelfVoice', 'methods.toggleVoice',
], {
Meteor.methods(mapToAcl(['methods.listenOnlyToggle', 'methods.toggleSelfVoice',
'methods.toggleVoice', 'methods.ejectUserFromVoice'], {
listenOnlyToggle,
toggleSelfVoice: (credentials) => { muteToggle(credentials, credentials.requesterUserId); },
toggleVoice: muteToggle,
ejectUserFromVoice,
}));

View File

@ -0,0 +1,22 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import RedisPubSub from '/imports/startup/server/redis';
export default function ejectUserFromVoice(credentials, userId) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'EjectUserFromVoiceCmdMsg';
const { requesterUserId, meetingId } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(userId, String);
const payload = {
userId,
ejectedBy: requesterUserId,
};
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -6,7 +6,7 @@ import Meetings from '/imports/api/meetings';
import VoiceUsers from '/imports/api/voice-users';
export default function listenOnlyToggle(credentials, isJoining = true) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const { meetingId, requesterUserId } = credentials;
@ -30,8 +30,7 @@ export default function listenOnlyToggle(credentials, isJoining = true) {
const Meeting = Meetings.findOne({ meetingId });
if (!VoiceUser) {
throw new Meteor.Error(
'user-not-found', 'You need a valid user to be able to toggle audio');
throw new Meteor.Error('user-not-found', 'You need a valid user to be able to toggle audio');
}
// check(User.user.name, String);

View File

@ -5,7 +5,7 @@ import Users from '/imports/api/users';
import VoiceUsers from '/imports/api/voice-users';
export default function muteToggle(credentials, userId) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'MuteUserCmdMsg';
const APP_CONFIG = Meteor.settings.public.app;

View File

@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export default function changeWhiteboardAccess(credentials, multiUser) {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { withTracker } from 'meteor/react-meteor-data';
import { withRouter } from 'react-router';
import PropTypes from 'prop-types';
import Auth from '/imports/ui/services/auth';
import AppContainer from '/imports/ui/components/app/container';
@ -83,25 +84,32 @@ Base.defaultProps = defaultProps;
const SUBSCRIPTIONS_NAME = [
'users', 'chat', 'cursor', 'meetings', 'polls', 'presentations', 'annotations',
'slides', 'captions', 'breakouts', 'voiceUsers', 'whiteboard-multi-user',
'slides', 'captions', 'breakouts', 'voiceUsers', 'whiteboard-multi-user', 'screenshare',
];
const BaseContainer = createContainer(({ params }) => {
const BaseContainer = withRouter(withTracker(({ params, router }) => {
if (params.errorCode) return params;
if (!Auth.loggedIn) {
return {
errorCode: 401,
error: 'You are unauthorized to access this meeting',
};
return router.push('/logout');
}
const credentials = Auth.credentials;
const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name => Meteor.subscribe(name, credentials));
const { credentials } = Auth;
const subscriptionErrorHandler = {
onError: (error) => {
console.error(error);
return router.push('/logout');
},
};
const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name =>
Meteor.subscribe(name, credentials, subscriptionErrorHandler));
return {
locale: Settings.application.locale,
subscriptionsReady: subscriptionsHandlers.every(handler => handler.ready()),
};
}, Base);
})(Base));
export default BaseContainer;

View File

@ -1,13 +1,14 @@
import Acl from '/imports/startup/acl';
import { Meteor } from 'meteor/meteor';
import Logger from '/imports/startup/server/logger';
const injectAclActionCheck = (name, handler) => (
(...args) => {
const credentials = args[0];
if (!Acl.can(name, credentials)) {
throw new Meteor.Error('acl-not-allowed',
`The user can't perform the action "${name}".`);
throw new Meteor.Error(
'acl-not-allowed',
`The user can't perform the action "${name}".`,
);
}
return handler(...args);
@ -18,8 +19,10 @@ const injectAclSubscribeCheck = (name, handler) => (
(...args) => {
const credentials = args[args.length - 1];
if (!Acl.can(name, ...credentials)) {
Logger.error(`acl-not-allowed, the user can't perform the subscription "${name}".`);
return [];
throw new Meteor.Error(
'acl-not-allowed',
`The user can't perform the subscription "${name}".`,
);
}
return handler(...credentials);

View File

@ -3,14 +3,11 @@ import Logger from './logger';
import Redis from './redis';
import locales from '../../utils/locales';
let DEFAULT_LANGUAGE = null;
const availableLocales = [];
Meteor.startup(() => {
const APP_CONFIG = Meteor.settings.public.app;
Logger.info(`SERVER STARTED. ENV=${Meteor.settings.runtime.env}`, APP_CONFIG);
DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.locale
Logger.info(`SERVER STARTED. DEV_ENV=${Meteor.isDevelopment} PROD_ENV=${Meteor.isProduction}`, APP_CONFIG);
});
WebApp.connectHandlers.use('/check', (req, res) => {
@ -24,7 +21,7 @@ WebApp.connectHandlers.use('/check', (req, res) => {
WebApp.connectHandlers.use('/locale', (req, res) => {
const APP_CONFIG = Meteor.settings.public.app;
const defaultLocale = APP_CONFIG.defaultSettings.application.locale;
const localeRegion = req.query.locale.split(/[-_]/g);;
const localeRegion = req.query.locale.split(/[-_]/g);
const localeList = [defaultLocale, localeRegion[0]];
let normalizedLocale = localeRegion[0];
@ -39,7 +36,7 @@ WebApp.connectHandlers.use('/locale', (req, res) => {
try {
const data = Assets.getText(`locales/${locale}.json`);
messages = Object.assign(messages, JSON.parse(data));
normalizedLocale = locale
normalizedLocale = locale;
} catch (e) {
// Getting here means the locale is not available on the files.
}

View File

@ -4,7 +4,9 @@ import Winston from 'winston';
const Logger = new Winston.Logger();
Logger.configure({
levels: { error: 0, warn: 1, info: 2, verbose: 3, debug: 4 },
levels: {
error: 0, warn: 1, info: 2, verbose: 3, debug: 4,
},
colors: {
error: 'red',
warn: 'yellow',
@ -23,15 +25,15 @@ Logger.add(Winston.transports.Console, {
});
Meteor.startup(() => {
const LOG_CONFIG = Meteor.settings.log || {};
let filename = LOG_CONFIG.filename;
const LOG_CONFIG = Meteor.settings.private.log || {};
let { filename } = LOG_CONFIG;
// Set Logger message level priority for the console
Logger.transports.console.level = LOG_CONFIG.level;
// Determine file to write logs to
if (filename) {
if (Meteor.settings.runtime.env === 'development') {
if (Meteor.isDevelopment) {
const path = Npm.require('path');
filename = path.join(process.env.PWD, filename);
}

View File

@ -47,8 +47,8 @@ class MettingMessageQueue {
const { envelope } = data.parsedMessage;
const { header } = data.parsedMessage.core;
const { body } = data.parsedMessage.core;
const { meetingId } = header;
const eventName = header.name;
const meetingId = header.meetingId;
const isAsync = this.asyncMessages.includes(channel)
|| this.asyncMessages.includes(eventName);
@ -91,7 +91,6 @@ class MettingMessageQueue {
}
class RedisPubSub {
static handlePublishError(err) {
if (err) {
Logger.error(err);
@ -136,7 +135,7 @@ class RedisPubSub {
if (this.didSendRequestEvent) return;
// populate collections with pre-existing data
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'GetAllMeetingsReqMsg';
@ -229,7 +228,7 @@ class RedisPubSub {
const RedisPubSubSingleton = new RedisPubSub();
Meteor.startup(() => {
const REDIS_CONFIG = Meteor.settings.redis;
const REDIS_CONFIG = Meteor.settings.private.redis;
RedisPubSubSingleton.updateConfig(REDIS_CONFIG);
RedisPubSubSingleton.init();

View File

@ -1,5 +1,5 @@
import React from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { withTracker } from 'meteor/react-meteor-data';
import AboutComponent from './component';
@ -16,4 +16,4 @@ const getClientBuildInfo = function () {
};
};
export default createContainer(() => getClientBuildInfo(), AboutContainer);
export default withTracker(() => getClientBuildInfo())(AboutContainer);

View File

@ -1,17 +1,15 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import Button from '/imports/ui/components/button/component';
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 PresentationUploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
import { withModalMounter } from '/imports/ui/components/modal/service';
import styles from '../styles';
import { styles } from '../styles';
const propTypes = {
isUserPresenter: PropTypes.bool.isRequired,
@ -32,6 +30,22 @@ const intlMessages = defineMessages({
id: 'app.actionsBar.actionsDropdown.presentationDesc',
description: 'adds context to upload presentation option',
},
desktopShareLabel: {
id: 'app.actionsBar.actionsDropdown.desktopShareLabel',
description: 'Desktop Share option label',
},
stopDesktopShareLabel: {
id: 'app.actionsBar.actionsDropdown.stopDesktopShareLabel',
description: 'Stop Desktop Share option label',
},
desktopShareDesc: {
id: 'app.actionsBar.actionsDropdown.desktopShareDesc',
description: 'adds context to desktop share option',
},
stopDesktopShareDesc: {
id: 'app.actionsBar.actionsDropdown.stopDesktopShareDesc',
description: 'adds context to stop desktop share option',
},
});
class ActionsDropdown extends Component {
@ -53,7 +67,13 @@ class ActionsDropdown extends Component {
}
render() {
const { intl, isUserPresenter } = this.props;
const {
intl,
isUserPresenter,
handleShareScreen,
handleUnshareScreen,
isVideoBroadcasting,
} = this.props;
if (!isUserPresenter) return null;
@ -61,6 +81,8 @@ class ActionsDropdown extends Component {
<Dropdown ref={(ref) => { this._dropdown = ref; }} >
<DropdownTrigger tabIndex={0} >
<Button
hideLabel
aria-label={intl.formatMessage(intlMessages.actionsLabel)}
className={styles.button}
label={intl.formatMessage(intlMessages.actionsLabel)}
icon="plus"
@ -78,6 +100,18 @@ class ActionsDropdown extends Component {
description={intl.formatMessage(intlMessages.presentationDesc)}
onClick={this.handlePresentationClick}
/>
<DropdownListItem
icon="desktop"
label={intl.formatMessage(intlMessages.desktopShareLabel)}
description={intl.formatMessage(intlMessages.desktopShareDesc)}
onClick={handleShareScreen}
/>
<DropdownListItem
icon="desktop"
label={intl.formatMessage(intlMessages.stopDesktopShareLabel)}
description={intl.formatMessage(intlMessages.stopDesktopShareDesc)}
onClick={handleUnshareScreen}
/>
</DropdownList>
</DropdownContent>
</Dropdown>

View File

@ -1,22 +1,33 @@
import React from 'react';
import styles from './styles.scss';
import { styles } from './styles.scss';
import EmojiSelect from './emoji-select/component';
import ActionsDropdown from './actions-dropdown/component';
import AudioControlsContainer from '../audio/audio-controls/container';
import JoinVideoOptionsContainer from '../video-dock/video-menu/container';
const ActionsBar = ({
isUserPresenter,
handleExitVideo,
handleJoinVideo,
handleShareScreen,
handleUnshareScreen,
isVideoBroadcasting,
emojiList,
emojiSelected,
handleEmojiChange,
}) => (
<div className={styles.actionsbar}>
<div className={styles.left}>
<ActionsDropdown {...{ isUserPresenter }} />
<ActionsDropdown {...{ isUserPresenter, handleShareScreen, handleUnshareScreen, isVideoBroadcasting}} />
</div>
<div className={styles.center}>
<AudioControlsContainer />
{/* <JoinVideo /> */}
{Meteor.settings.public.kurento.enableVideo ?
<JoinVideoOptionsContainer
handleJoinVideo={handleJoinVideo}
handleCloseVideo={handleExitVideo}
/>
: null}
<EmojiSelect options={emojiList} selected={emojiSelected} onChange={handleEmojiChange} />
</div>
</div>

View File

@ -1,13 +1,21 @@
import React from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { withTracker } from 'meteor/react-meteor-data';
import ActionsBar from './component';
import Service from './service';
import VideoService from '../video-dock/service';
import { shareScreen, unshareScreen, isVideoBroadcasting } from '../screenshare/service';
const ActionsBarContainer = props => <ActionsBar {...props} />;
export default createContainer(() => ({
export default withTracker(() => ({
isUserPresenter: Service.isUserPresenter(),
emojiList: Service.getEmojiList(),
emojiSelected: Service.getEmoji(),
handleEmojiChange: Service.setEmoji,
}), ActionsBarContainer);
handleExitVideo: () => VideoService.exitVideo(),
handleJoinVideo: () => VideoService.joinVideo(),
handleShareScreen: () => shareScreen(),
handleUnshareScreen: () => unshareScreen(),
isVideoBroadcasting: () => isVideoBroadcasting(),
}))(ActionsBarContainer);

View File

@ -9,7 +9,7 @@ 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';
import styles from '../styles';
import { styles } from '../styles';
const intlMessages = defineMessages({
statusTriggerLabel: {
@ -42,17 +42,21 @@ const EmojiSelect = ({
const statuses = Object.keys(options);
const lastStatus = statuses.pop();
const statusLabel = statuses.indexOf(selected) === -1 ?
intl.formatMessage(intlMessages.statusTriggerLabel)
: intl.formatMessage({ id: `app.actionsBar.emojiMenu.${selected}Label` });
return (
<Dropdown autoFocus>
<DropdownTrigger tabIndex={0}>
<Button
className={styles.button}
label={intl.formatMessage(intlMessages.statusTriggerLabel)}
aria-label={intl.formatMessage(intlMessages.changeStatusLabel)}
label={statusLabel}
aria-label={statusLabel}
aria-describedby="currentStatus"
icon={options[selected !== lastStatus ? selected : statuses[1]]}
ghost={false}
hideLabel={false}
hideLabel
circle
size="lg"
color="primary"

View File

@ -1,6 +1,6 @@
import React from 'react';
import Button from '/imports/ui/components/button/component';
// import styles from '../styles.scss';
// import { styles } from '../styles.scss';
export default class JoinVideo extends React.Component {

View File

@ -9,7 +9,7 @@ import ModalContainer from '../modal/container';
import NotificationsBarContainer from '../notifications-bar/container';
import AudioContainer from '../audio/container';
import ChatNotificationContainer from '../chat/notification/container';
import styles from './styles';
import { styles } from './styles';
const intlMessages = defineMessages({
userListLabel: {

View File

@ -1,5 +1,5 @@
import React, { cloneElement } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { withTracker } from 'meteor/react-meteor-data';
import { withRouter } from 'react-router';
import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
@ -27,7 +27,7 @@ const propTypes = {
navbar: PropTypes.node,
actionsbar: PropTypes.node,
media: PropTypes.node,
location: PropTypes.object.isRequired,
location: PropTypes.shape({}).isRequired,
};
const defaultProps = {
@ -39,7 +39,7 @@ const defaultProps = {
const intlMessages = defineMessages({
kickedMessage: {
id: 'app.error.kicked',
description: 'Message when the user is kicked out of the meeting',
description: 'Message when the user is removed from the conference',
},
waitingApprovalMessage: {
id: 'app.guest.waiting',
@ -72,8 +72,7 @@ const AppContainer = (props) => {
);
};
export default withRouter(injectIntl(withModalMounter(createContainer((
{ router, intl, baseControls }) => {
export default withRouter(injectIntl(withModalMounter(withTracker(({ router, intl, baseControls }) => {
const currentUser = Users.findOne({ userId: Auth.userID });
const isMeetingBreakout = meetingIsBreakout();
@ -84,10 +83,10 @@ export default withRouter(injectIntl(withModalMounter(createContainer((
// Displayed error messages according to the mode (kicked, end meeting)
const sendToError = (code, message) => {
Auth.clearCredentials()
.then(() => {
router.push(`/error/${code}`);
baseControls.updateErrorState(message);
});
.then(() => {
router.push(`/error/${code}`);
baseControls.updateErrorState(message);
});
};
// Check if user is kicked out of the session
@ -118,7 +117,7 @@ export default withRouter(injectIntl(withModalMounter(createContainer((
closedCaption: getCaptionsStatus() ? <ClosedCaptionsContainer /> : null,
fontSize: getFontSize(),
};
}, AppContainer))));
})(AppContainer))));
AppContainer.defaultProps = defaultProps;
AppContainer.propTypes = propTypes;

View File

@ -1,8 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { defineMessages, intlShape, injectIntl } from 'react-intl';
import Button from '/imports/ui/components/button/component';
import styles from './styles';
import { styles } from './styles';
import cx from 'classnames';
const intlMessages = defineMessages({
joinAudio: {
id: 'app.audio.joinAudio',
description: 'Join audio button label',
},
leaveAudio: {
id: 'app.audio.leaveAudio',
description: 'Leave audio button label',
},
muteAudio: {
id: 'app.actionsBar.muteLabel',
description: 'Mute audio button label',
},
unmuteAudio: {
id: 'app.actionsBar.unmuteLabel',
description: 'Unmute audio button label',
},
});
const propTypes = {
handleToggleMuteMicrophone: PropTypes.func.isRequired,
@ -12,6 +32,7 @@ const propTypes = {
unmute: PropTypes.bool.isRequired,
mute: PropTypes.bool.isRequired,
join: PropTypes.bool.isRequired,
intl: intlShape.isRequired,
glow: PropTypes.bool,
};
@ -28,6 +49,7 @@ const AudioControls = ({
disable,
glow,
join,
intl,
}) => (
<span className={styles.container}>
{mute ?
@ -35,7 +57,9 @@ const AudioControls = ({
className={glow ? cx(styles.button, styles.glow) : styles.button}
onClick={handleToggleMuteMicrophone}
disabled={disable}
label={unmute ? 'Unmute' : 'Mute'}
hideLabel
label={unmute ? intl.formatMessage(intlMessages.unmuteAudio) : intl.formatMessage(intlMessages.muteAudio)}
aria-label={unmute ? intl.formatMessage(intlMessages.unmuteAudio) : intl.formatMessage(intlMessages.muteAudio)}
color="primary"
icon={unmute ? 'mute' : 'unmute'}
size="lg"
@ -45,7 +69,9 @@ const AudioControls = ({
className={styles.button}
onClick={join ? handleLeaveAudio : handleJoinAudio}
disabled={disable}
label={join ? 'Leave Audio' : 'Join Audio'}
hideLabel
aria-label={join ? intl.formatMessage(intlMessages.leaveAudio) : intl.formatMessage(intlMessages.joinAudio)}
label={join ? intl.formatMessage(intlMessages.leaveAudio) : intl.formatMessage(intlMessages.joinAudio)}
color={join ? 'danger' : 'primary'}
icon={join ? 'audio_off' : 'audio_on'}
size="lg"
@ -56,4 +82,4 @@ const AudioControls = ({
AudioControls.propTypes = propTypes;
AudioControls.defaultProps = defaultProps;
export default AudioControls;
export default injectIntl(AudioControls);

Some files were not shown because too many files have changed in this diff Show More