-
+
{conn.name}
- {conn.offline ? ` (${intl.formatMessage(intlMessages.offline)})` : null}
@@ -116,19 +146,63 @@ class ConnectionStatusComponent extends PureComponent {
});
}
+ renderDataSaving() {
+ const {
+ intl,
+ dataSaving,
+ } = this.props;
+
+ const {
+ viewParticipantsWebcams,
+ viewScreenshare,
+ } = dataSaving;
+
+ return (
+
+
+ {intl.formatMessage(intlMessages.dataSaving)}
+
+
+
+ this.handleDataSavingChange('viewParticipantsWebcams')}
+ ariaLabelledBy="webcam"
+ ariaLabel={intl.formatMessage(intlMessages.webcam)}
+ />
+
+
+
+ this.handleDataSavingChange('viewScreenshare')}
+ ariaLabelledBy="screenshare"
+ ariaLabel={intl.formatMessage(intlMessages.screenshare)}
+ />
+
+
+ );
+ }
+
render() {
const {
closeModal,
intl,
} = this.props;
- const isValidUrl = new RegExp(/^(http|https):\/\/[^ "]+$/).test(STATS.help);
+ const { dataSaving } = this.state;
return (
closeModal(dataSaving, intl)}
hideBorder
contentLabel={intl.formatMessage(intlMessages.ariaTitle)}
>
@@ -140,14 +214,15 @@ class ConnectionStatusComponent extends PureComponent {
+ {this.renderDataSaving()}
{this.renderConnections()}
diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/container.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/modal/container.jsx
index c6b1aeb54f..35f2dc9846 100644
--- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/container.jsx
@@ -1,12 +1,18 @@
import React from 'react';
+import _ from 'lodash';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/modal/service';
+import Settings from '/imports/ui/services/settings';
import ConnectionStatusService from '../service';
import ConnectionStatusComponent from './component';
const connectionStatusContainer = props => ;
export default withModalMounter(withTracker(({ mountModal }) => ({
- closeModal: () => mountModal(null),
+ closeModal: (dataSaving, intl) => {
+ ConnectionStatusService.updateDataSavingSettings(dataSaving, intl);
+ mountModal(null);
+ },
connectionStatus: ConnectionStatusService.getConnectionStatus(),
+ dataSaving: _.clone(Settings.dataSaving),
}))(connectionStatusContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss
index 42fd946ea5..5769055251 100644
--- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss
@@ -1,5 +1,13 @@
+@import '/imports/ui/stylesheets/mixins/focus';
+@import '/imports/ui/stylesheets/variables/_all';
@import "/imports/ui/components/modal/simple/styles";
+:root {
+ --modal-margin: 3rem;
+ --title-position-left: 2.2rem;
+ --closeBtn-position-left: 2.5rem;
+}
+
.title {
left: var(--title-position-left);
right: auto;
@@ -57,7 +65,7 @@
.wrapper {
display: block;
width: 100%;
- max-height: 24rem;
+ max-height: 16rem;
}
.item {
@@ -101,10 +109,6 @@
overflow: hidden;
text-overflow: ellipsis;
}
-
- .offline {
- font-style: italic;
- }
}
.status {
@@ -128,3 +132,18 @@
height: 100%;
}
}
+
+.dataSaving {
+ background-color: var(--color-off-white);
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ padding: 1rem;
+ width: 100%;
+}
+
+.saving {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+}
diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/service.js b/bigbluebutton-html5/imports/ui/components/connection-status/service.js
index feb7b6b578..979d404296 100644
--- a/bigbluebutton-html5/imports/ui/components/connection-status/service.js
+++ b/bigbluebutton-html5/imports/ui/components/connection-status/service.js
@@ -1,33 +1,68 @@
+import { defineMessages } from 'react-intl';
import ConnectionStatus from '/imports/api/connection-status';
import Users from '/imports/api/users';
import Auth from '/imports/ui/services/auth';
+import Settings from '/imports/ui/services/settings';
+import Logger from '/imports/startup/client/logger';
+import _ from 'lodash';
+import { Session } from 'meteor/session';
+import { notify } from '/imports/ui/services/notification';
import { makeCall } from '/imports/ui/services/api';
const STATS = Meteor.settings.public.stats;
+const NOTIFICATION = STATS.notification;
+const STATS_LENGTH = STATS.length;
+const STATS_INTERVAL = STATS.interval;
+const STATS_LOG = STATS.log;
+const RTT_INTERVAL = STATS_LENGTH * STATS_INTERVAL;
+// Set a bottom threshold to avoid log flooding
+const RTT_LOG_THRESHOLD = STATS.rtt[STATS.level.indexOf('danger')];
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
-let audioStats = '';
-const audioStatsDep = new Tracker.Dependency();
+const intlMessages = defineMessages({
+ saved: {
+ id: 'app.settings.save-notification.label',
+ description: 'Label shown in toast when data savings are saved',
+ },
+ notification: {
+ id: 'app.connection-status.notification',
+ description: 'Label shown in toast when connection loss is detected',
+ },
+});
+
+let stats = -1;
+const statsDep = new Tracker.Dependency();
let statsTimeout = null;
-const getHelp = () => STATS.help;
+const URL_REGEX = new RegExp(/^(http|https):\/\/[^ "]+$/);
+const getHelp = () => {
+ if (URL_REGEX.test(STATS.help)) return STATS.help;
+
+ return null;
+};
const getLevel = () => STATS.level;
-const getAudioStats = () => {
- audioStatsDep.depend();
- return audioStats;
+const getStats = () => {
+ statsDep.depend();
+ return STATS.level[stats];
};
-const setAudioStats = (level = '') => {
- if (audioStats !== level) {
- audioStats = level;
- audioStatsDep.changed();
+const setStats = (level = -1) => {
+ if (stats !== level) {
+ stats = level;
+ statsDep.changed();
addConnectionStatus(level);
}
};
+const handleStats = (level) => {
+ if (level > stats) {
+ setStats(level);
+ }
+};
+
const handleAudioStatsEvent = (event) => {
const { detail } = event;
if (detail) {
@@ -37,21 +72,64 @@ const handleAudioStatsEvent = (event) => {
for (let i = STATS.level.length - 1; i >= 0; i--) {
if (loss > STATS.loss[i] || jitter > STATS.jitter[i]) {
active = true;
- setAudioStats(STATS.level[i]);
+ handleStats(i);
break;
}
}
- if (active) {
- if (statsTimeout !== null) clearTimeout(statsTimeout);
- statsTimeout = setTimeout(() => {
- setAudioStats();
- }, STATS.length * STATS.interval);
- }
+
+ if (active) startStatsTimeout();
}
};
+const handleSocketStatsEvent = (event) => {
+ const { detail } = event;
+ if (detail) {
+ const { rtt } = detail;
+ let active = false;
+ // From higher to lower
+ for (let i = STATS.level.length - 1; i >= 0; i--) {
+ if (rtt > STATS.rtt[i]) {
+ active = true;
+ handleStats(i);
+ break;
+ }
+ }
+
+ if (active) startStatsTimeout();
+ }
+};
+
+const startStatsTimeout = () => {
+ if (statsTimeout !== null) clearTimeout(statsTimeout);
+
+ statsTimeout = setTimeout(() => {
+ setStats();
+ }, STATS.timeout);
+};
+
const addConnectionStatus = (level) => {
- if (level !== '') makeCall('addConnectionStatus', level);
+ if (level !== -1) makeCall('addConnectionStatus', STATS.level[level]);
+};
+
+const fetchRoundTripTime = () => {
+ const t0 = Date.now();
+ makeCall('voidConnection').then(() => {
+ const tf = Date.now();
+ const rtt = tf - t0;
+
+ if (STATS_LOG && rtt > RTT_LOG_THRESHOLD) {
+ Logger.info(
+ {
+ logCode: 'rtt',
+ extraInfo: { rtt },
+ },
+ 'Calculated round-trip time in milliseconds',
+ );
+ }
+
+ const event = new CustomEvent('socketstats', { detail: { rtt } });
+ window.dispatchEvent(event);
+ });
};
const sortLevel = (a, b) => {
@@ -63,7 +141,51 @@ const sortLevel = (a, b) => {
if (indexOfA > indexOfB) return -1;
};
+const getMyConnectionStatus = () => {
+ const myConnectionStatus = ConnectionStatus.findOne(
+ {
+ meetingId: Auth.meetingID,
+ userId: Auth.userID,
+ },
+ { fields:
+ {
+ level: 1,
+ timestamp: 1,
+ },
+ },
+ );
+
+ const me = Users.findOne(
+ {
+ meetingId: Auth.meetingID,
+ userId: Auth.userID,
+ },
+ { fields:
+ {
+ avatar: 1,
+ color: 1,
+ }
+ },
+ );
+
+ if (myConnectionStatus) {
+ return [{
+ name: Auth.fullname,
+ avatar: me.avatar,
+ you: true,
+ moderator: false,
+ color: me.color,
+ level: myConnectionStatus.level,
+ timestamp: myConnectionStatus.timestamp,
+ }];
+ }
+
+ return [];
+};
+
const getConnectionStatus = () => {
+ if (!isModerator()) return getMyConnectionStatus();
+
const connectionStatus = ConnectionStatus.find(
{ meetingId: Auth.meetingID },
).fetch().map(status => {
@@ -89,7 +211,6 @@ const getConnectionStatus = () => {
role: 1,
avatar: 1,
color: 1,
- connectionStatus: 1,
},
},
).fetch().reduce((result, user) => {
@@ -99,7 +220,6 @@ const getConnectionStatus = () => {
role,
avatar,
color,
- connectionStatus: userStatus,
} = user;
const status = connectionStatus.find(status => status.userId === userId);
@@ -108,7 +228,6 @@ const getConnectionStatus = () => {
result.push({
name,
avatar,
- offline: userStatus === 'offline',
you: Auth.userID === userId,
moderator: role === ROLE_MODERATOR,
color,
@@ -123,15 +242,81 @@ const getConnectionStatus = () => {
const isEnabled = () => STATS.enabled;
+let roundTripTimeInterval = null;
+
+const startRoundTripTime = () => {
+ if (!isEnabled()) return;
+
+ stopRoundTripTime();
+
+ roundTripTimeInterval = setInterval(fetchRoundTripTime, RTT_INTERVAL);
+};
+
+const stopRoundTripTime = () => {
+ if (roundTripTimeInterval) {
+ clearInterval(roundTripTimeInterval);
+ }
+}
+
+const isModerator = () => {
+ const user = Users.findOne(
+ {
+ meetingId: Auth.meetingID,
+ userId: Auth.userID,
+ },
+ { fields: { role: 1 }},
+ );
+
+ if (user && user.role === ROLE_MODERATOR) {
+ return true;
+ }
+
+ return false;
+};
+
if (STATS.enabled) {
window.addEventListener('audiostats', handleAudioStatsEvent);
+ window.addEventListener('socketstats', handleSocketStatsEvent);
}
+const updateDataSavingSettings = (dataSaving, intl) => {
+ if (!_.isEqual(Settings.dataSaving, dataSaving)) {
+ Settings.dataSaving = dataSaving;
+ Settings.save();
+ if (intl) notify(intl.formatMessage(intlMessages.saved), 'info', 'settings');
+ }
+};
+
+const getNotified = () => {
+ const notified = Session.get('connectionStatusNotified');
+
+ // Since notified can be undefined we need a boolean verification
+ return notified === true;
+};
+
+const notification = (level, intl) => {
+ if (!NOTIFICATION[level]) return null;
+
+ // Avoid toast spamming
+ const notified = getNotified();
+ if (notified) {
+ return null;
+ } else {
+ Session.set('connectionStatusNotified', true);
+ }
+
+ if (intl) notify(intl.formatMessage(intlMessages.notification), level, 'network');
+};
+
export default {
addConnectionStatus,
getConnectionStatus,
- getAudioStats,
+ getStats,
getHelp,
getLevel,
isEnabled,
+ notification,
+ startRoundTripTime,
+ stopRoundTripTime,
+ updateDataSavingSettings,
};
diff --git a/bigbluebutton-html5/imports/ui/components/media/container.jsx b/bigbluebutton-html5/imports/ui/components/media/container.jsx
index 2998887190..61a94e9561 100755
--- a/bigbluebutton-html5/imports/ui/components/media/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/container.jsx
@@ -16,6 +16,8 @@ import DefaultContent from '../presentation/default-content/component';
import ExternalVideoContainer from '../external-video-player/container';
import Storage from '../../services/storage/session';
import { withLayoutConsumer } from '/imports/ui/components/layout/context';
+import Auth from '/imports/ui/services/auth';
+import breakoutService from '/imports/ui/components/breakout-room/service';
const LAYOUT_CONFIG = Meteor.settings.public.layout;
const KURENTO_CONFIG = Meteor.settings.public.kurento;
@@ -106,6 +108,8 @@ class MediaContainer extends Component {
}
}
+let userWasInBreakout = false;
+
export default withLayoutConsumer(withModalMounter(withTracker(() => {
const { dataSaving } = Settings;
const { viewParticipantsWebcams, viewScreenshare } = dataSaving;
@@ -127,6 +131,31 @@ export default withLayoutConsumer(withModalMounter(withTracker(() => {
data.children = ;
}
+ const userIsInBreakout = breakoutService.getBreakoutUserIsIn(Auth.userID);
+ let deviceIds = Session.get('deviceIds');
+
+ if (!userIsInBreakout && userWasInBreakout && deviceIds && deviceIds !== '') {
+ /* used when re-sharing cameras after leaving a breakout room.
+ it is needed in cases where the user has more than one active camera
+ so we only share the second camera after the first
+ has finished loading (can't share more than one at the same time) */
+ const canConnect = Session.get('canConnect');
+
+ deviceIds = deviceIds.split(',');
+
+ if (canConnect) {
+ const deviceId = deviceIds.shift();
+
+ Session.set('canConnect', false);
+ Session.set('WebcamDeviceId', deviceId);
+ Session.set('deviceIds', deviceIds.join(','));
+
+ VideoService.joinVideo(deviceId);
+ }
+ } else {
+ userWasInBreakout = userIsInBreakout;
+ }
+
const { streams: usersVideo } = VideoService.getVideoStreams();
data.usersVideo = usersVideo;
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
index 5acf94dcb4..831f308bdd 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
@@ -11,6 +11,8 @@ import { styles } from './styles.scss';
import Button from '/imports/ui/components/button/component';
import RecordingIndicator from './recording-indicator/container';
import TalkingIndicatorContainer from '/imports/ui/components/nav-bar/talking-indicator/container';
+import ConnectionStatusButton from '/imports/ui/components/connection-status/button/container';
+import ConnectionStatusService from '/imports/ui/components/connection-status/service';
import SettingsDropdownContainer from './settings-dropdown/container';
const intlMessages = defineMessages({
@@ -125,6 +127,7 @@ class NavBar extends Component {
/>
+ {ConnectionStatusService.isEnabled() ? : null}
diff --git a/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
index a6f8c61f17..e9dc29e41c 100644
--- a/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
@@ -8,7 +8,6 @@ import Meetings, { MeetingTimeRemaining } from '/imports/api/meetings';
import Users from '/imports/api/users';
import BreakoutRemainingTime from '/imports/ui/components/breakout-room/breakout-remaining-time/container';
import SlowConnection from '/imports/ui/components/slow-connection/component';
-import ConnectionStatusService from '/imports/ui/components/connection-status/service';
import { styles } from './styles.scss';
import breakoutService from '/imports/ui/components/breakout-room/service';
@@ -150,22 +149,6 @@ export default injectIntl(withTracker(({ intl }) => {
}
}
- if (ConnectionStatusService.isEnabled()) {
- const stats = ConnectionStatusService.getAudioStats();
- if (stats) {
- if (ConnectionStatusService.getLevel().includes(stats)) {
- data.message = (
-
- {intl.formatMessage(intlMessages.slowEffectiveConnectionDetected)}{' '}
-
- {intl.formatMessage(intlMessages.slowEffectiveConnectionHelpLink)}
-
-
- );
- }
- }
- }
-
if (!connected) {
data.color = 'primary';
switch (status) {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-close-button/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-close-button/component.jsx
index 6f7952c5cb..c8f559f740 100755
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-close-button/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-close-button/component.jsx
@@ -12,15 +12,13 @@ const intlMessages = defineMessages({
const ClosePresentationComponent = ({ intl, toggleSwapLayout }) => (
diff --git a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx
index 32b4698e35..72c73e42e4 100755
--- a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx
@@ -36,6 +36,8 @@ class Subscriptions extends Component {
}
}
+let usersPersistentDataHandler = null;
+
export default withTracker(() => {
const { credentials } = Auth;
const { meetingId, requesterUserId } = credentials;
@@ -105,6 +107,9 @@ export default withTracker(() => {
const chatIds = chats.map(chat => chat.chatId);
groupChatMessageHandler = Meteor.subscribe('group-chat-msg', chatIds, subscriptionErrorHandler);
}
+ if (ready && !usersPersistentDataHandler) {
+ usersPersistentDataHandler = Meteor.subscribe('users-persistent-data');
+ }
return {
subscriptionsReady: ready,
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx
index d68d12a29b..267edf7039 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx
@@ -3,10 +3,18 @@ import { withTracker } from 'meteor/react-meteor-data';
import UserListService from '/imports/ui/components/user-list/service';
import UserParticipants from './component';
import { meetingIsBreakout } from '/imports/ui/components/app/service';
+import ChatService from '/imports/ui/components/chat/service';
const UserParticipantsContainer = props =>
;
-export default withTracker(() => ({
- users: UserListService.getUsers(),
- meetingIsBreakout: meetingIsBreakout(),
-}))(UserParticipantsContainer);
+export default withTracker(() => {
+ ChatService.removePackagedClassAttribute(
+ ['ReactVirtualized__Grid', 'ReactVirtualized__Grid__innerScrollContainer'],
+ 'role',
+ );
+
+ return ({
+ users: UserListService.getUsers(),
+ meetingIsBreakout: meetingIsBreakout(),
+ });
+})(UserParticipantsContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
index b2e1583df1..d275f15642 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
@@ -10,11 +10,9 @@ 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 LockViewersContainer from '/imports/ui/components/lock-viewers/container';
-import ConnectionStatusContainer from '/imports/ui/components/connection-status/modal/container';
import GuestPolicyContainer from '/imports/ui/components/waiting-users/guest-policy/container';
import BreakoutRoom from '/imports/ui/components/actions-bar/create-breakout-room/container';
import CaptionsService from '/imports/ui/components/captions/service';
-import ConnectionStatusService from '/imports/ui/components/connection-status/service';
import CaptionsWriterMenu from '/imports/ui/components/captions/writer-menu/container';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import { styles } from './styles';
@@ -74,14 +72,6 @@ const intlMessages = defineMessages({
id: 'app.userList.userOptions.lockViewersDesc',
description: 'Lock viewers description',
},
- connectionStatusLabel: {
- id: 'app.userList.userOptions.connectionStatusLabel',
- description: 'Connection status label',
- },
- connectionStatusDesc: {
- id: 'app.userList.userOptions.connectionStatusDesc',
- description: 'Connection status description',
- },
guestPolicyLabel: {
id: 'app.userList.userOptions.guestPolicyLabel',
description: 'Guest policy label',
@@ -148,7 +138,6 @@ class UserOptions extends PureComponent {
this.muteId = _.uniqueId('list-item-');
this.muteAllId = _.uniqueId('list-item-');
this.lockId = _.uniqueId('list-item-');
- this.connectionStatusId = _.uniqueId('list-item-');
this.guestPolicyId = _.uniqueId('list-item-');
this.createBreakoutId = _.uniqueId('list-item-');
this.saveUsersNameId = _.uniqueId('list-item-');
@@ -298,15 +287,6 @@ class UserOptions extends PureComponent {
onClick={() => mountModal(
)}
/>) : null
),
- (ConnectionStatusService.isEnabled() && isMeteorConnected ? (
-
mountModal()}
- />) : null
- ),
(!meetingIsBreakout && isMeteorConnected ? (
{
+ deviceIds.push(s.deviceId);
+ }
+ );
+ Session.set('deviceIds', deviceIds.join());
+ }
+
exitVideo() {
if (this.isConnected) {
logger.info({
@@ -801,6 +817,7 @@ class VideoService {
const videoService = new VideoService();
export default {
+ storeDeviceIds: () => videoService.storeDeviceIds(),
exitVideo: () => videoService.exitVideo(),
joinVideo: deviceId => videoService.joinVideo(deviceId),
stopVideo: cameraId => videoService.stopVideo(cameraId),
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
index 1fcb49cc09..21bb8333bd 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
@@ -122,6 +122,12 @@ class VideoListItem extends Component {
const { videoIsReady } = this.state;
if (!videoIsReady) this.setState({ videoIsReady: true });
window.dispatchEvent(new Event('resize'));
+
+ /* used when re-sharing cameras after leaving a breakout room.
+ it is needed in cases where the user has more than one active camera
+ so we only share the second camera after the first
+ has finished loading (can't share more than one at the same time) */
+ Session.set('canConnect', true);
}
getAvailableActions() {
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx
index 2da8142e46..676b407782 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx
@@ -470,26 +470,21 @@ class PollDrawComponent extends Component {
fill={backgroundColor}
strokeWidth={thickness}
/>
-
- {extendedTextArray.map(line => (
-
- {line.keyColumn.keyString}
-
- ))}
-
+ {extendedTextArray.map(line => (
+
+ {line.keyColumn.keyString}
+
+ ))}
{extendedTextArray.map(line => (
{
bytes: {
received: diff(single, first.bytes.received, last.bytes.received)
},
- jitter: single ? first.jitter : last.jitter
+ jitter: Math.max.apply(Math, stats.map(s => s.jitter))
};
};
@@ -190,7 +190,7 @@ const logResult = (id, result) => {
if (!iteration || iteration % STATS_LENGTH !== 0) return null;
const duration = STATS_LENGTH * STATS_INTERVAL / 1000;
- logger.info(
+ logger.debug(
{
logCode: 'stats_monitor_result',
extraInfo: {
diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml
index adcac388bf..6dc19449bb 100755
--- a/bigbluebutton-html5/private/config/settings.yml
+++ b/bigbluebutton-html5/private/config/settings.yml
@@ -163,10 +163,10 @@ public:
mediaTimeouts:
maxConnectionAttempts: 2
# Base screen media timeout (send|recv)
- baseTimeout: 15000
+ baseTimeout: 30000
# Max timeout: used as the max camera subscribe reconnection timeout. Each
# subscribe reattempt increases the reconnection timer up to this
- maxTimeout: 35000
+ maxTimeout: 60000
timeoutIncreaseFactor: 1.5
constraints:
video:
@@ -420,7 +420,11 @@ public:
enabled: true
interval: 2000
length: 5
+ timeout: 30000
log: false
+ notification:
+ warning: false
+ error: true
jitter:
- 10
- 20
@@ -429,6 +433,10 @@ public:
- 0.05
- 0.1
- 0.2
+ rtt:
+ - 500
+ - 1000
+ - 2000
level:
- warning
- danger
diff --git a/bigbluebutton-html5/public/compatibility/kurento-utils.js b/bigbluebutton-html5/public/compatibility/kurento-utils.js
index 67542dee30..09424f4606 100755
--- a/bigbluebutton-html5/public/compatibility/kurento-utils.js
+++ b/bigbluebutton-html5/public/compatibility/kurento-utils.js
@@ -277,17 +277,6 @@ function WebRtcPeer(mode, options, callback) {
callback(null, localDescription.sdp, self.processAnswer.bind(self));
}
- const isSafari = ((userAgent.indexOf('iphone') > -1 || userAgent.indexOf('ipad') > -1) || browser.name.toLowerCase() == 'safari');
-
- // Bind the SDP release to the gathering state on Safari-based envs
- if (isSafari) {
- pc.onicegatheringstatechange = function (event) {
- if(event.target.iceGatheringState == "complete") {
- descriptionCallback();
- }
- }
- }
-
var offerAudio = true;
var offerVideo = true;
if (mediaConstraints) {
@@ -305,10 +294,6 @@ function WebRtcPeer(mode, options, callback) {
offer = mangleSdpToAddSimulcast(offer);
return pc.setLocalDescription(offer);
}).then(() => {
- // The Safari offer release was already binded to the gathering state
- if (isSafari) {
- return;
- }
descriptionCallback();
}).catch(callback);
};
diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json
index 9f51280204..8eff494847 100755
--- a/bigbluebutton-html5/public/locales/en.json
+++ b/bigbluebutton-html5/public/locales/en.json
@@ -104,8 +104,6 @@
"app.userList.userOptions.lockViewersDesc": "Lock certain functionalities for attendees of the meeting",
"app.userList.userOptions.guestPolicyLabel": "Guest policy",
"app.userList.userOptions.guestPolicyDesc": "Change meeting guest policy setting",
- "app.userList.userOptions.connectionStatusLabel": "Connection status",
- "app.userList.userOptions.connectionStatusDesc": "View users' connection status",
"app.userList.userOptions.disableCam": "Viewers' webcams are disabled",
"app.userList.userOptions.disableMic": "Viewers' microphones are disabled",
"app.userList.userOptions.disablePrivChat": "Private chat is disabled",
@@ -618,7 +616,8 @@
"app.connection-status.description": "View users' connection status",
"app.connection-status.empty": "There are currently no reported connection issues",
"app.connection-status.more": "more",
- "app.connection-status.offline": "offline",
+ "app.connection-status.label": "Connection status",
+ "app.connection-status.notification": "Loss in your connection was detected",
"app.recording.startTitle": "Start recording",
"app.recording.stopTitle": "Pause recording",
"app.recording.resumeTitle": "Resume recording",
diff --git a/bigbluebutton-html5/server/main.js b/bigbluebutton-html5/server/main.js
index f80a2ed45c..3ff24d74a2 100755
--- a/bigbluebutton-html5/server/main.js
+++ b/bigbluebutton-html5/server/main.js
@@ -21,6 +21,7 @@ import '/imports/api/whiteboard-multi-user/server';
import '/imports/api/video-streams/server';
import '/imports/api/network-information/server';
import '/imports/api/users-infos/server';
+import '/imports/api/users-persistent-data/server';
import '/imports/api/connection-status/server';
import '/imports/api/note/server';
import '/imports/api/external-videos/server';
diff --git a/record-and-playback/core/lib/recordandplayback/events_archiver.rb b/record-and-playback/core/lib/recordandplayback/events_archiver.rb
index fc7269c743..d42a417d22 100755
--- a/record-and-playback/core/lib/recordandplayback/events_archiver.rb
+++ b/record-and-playback/core/lib/recordandplayback/events_archiver.rb
@@ -23,10 +23,11 @@ require 'builder'
require 'yaml'
require 'fileutils'
-module BigBlueButton
+module BigBlueButton
$bbb_props = YAML::load(File.open(File.expand_path('../../../scripts/bigbluebutton.yml', __FILE__)))
$recording_dir = $bbb_props['recording_dir']
$raw_recording_dir = "#{$recording_dir}/raw"
+ $store_recording_status = $bbb_props['store_recording_status']
# Class to wrap Redis so we can mock
# for testing
@@ -39,23 +40,23 @@ module BigBlueButton
@redis = Redis.new(:host => @host, :port => @port, :password => @password)
end
end
-
- def connect
- @redis.client.connect
+
+ def connect
+ @redis.client.connect
end
-
+
def disconnect
@redis.client.disconnect
end
-
+
def connected?
@redis.client.connected?
end
-
+
def metadata_for(meeting_id)
@redis.hgetall("meeting:info:#{meeting_id}")
end
-
+
def has_breakout_metadata_for(meeting_id)
@redis.exists("meeting:breakout:#{meeting_id}")
end
@@ -63,7 +64,7 @@ module BigBlueButton
def breakout_metadata_for(meeting_id)
@redis.hgetall("meeting:breakout:#{meeting_id}")
end
-
+
def has_breakout_rooms_for(meeting_id)
@redis.exists("meeting:breakout:rooms:#{meeting_id}")
end
@@ -75,11 +76,11 @@ module BigBlueButton
def num_events_for(meeting_id)
@redis.llen("meeting:#{meeting_id}:recordings")
end
-
+
def events_for(meeting_id)
@redis.lrange("meeting:#{meeting_id}:recordings", 0, num_events_for(meeting_id))
end
-
+
def event_info_for(meeting_id, event)
@redis.hgetall("recording:#{meeting_id}:#{event}")
end
@@ -135,6 +136,7 @@ module BigBlueButton
end
RECORDINGS_CHANNEL = "bigbluebutton:from-rap"
+ RAP_STATUS_LIST = "bigbluebutton:rap:status"
def put_message(message_type, meeting_id, additional_payload = {})
events_xml = "#{$raw_recording_dir}/#{meeting_id}/events.xml"
@@ -149,6 +151,11 @@ module BigBlueButton
"meeting_id" => meeting_id
})
@redis.publish RECORDINGS_CHANNEL, msg.to_json
+
+ if $store_recording_status
+ @redis.lpush RAP_STATUS_LIST, msg.to_json
+ end
+
end
def put_message_workflow(message_type, workflow, meeting_id, additional_payload = {})
@@ -157,6 +164,10 @@ module BigBlueButton
})
end
+ def put_archive_norecord(meeting_id, additional_payload = {})
+ put_message "archive_norecord", meeting_id, additional_payload
+ end
+
def put_archive_started(meeting_id, additional_payload = {})
put_message "archive_started", meeting_id, additional_payload
end
@@ -221,7 +232,7 @@ module BigBlueButton
MEETINGID = 'meetingId'
MEETINGNAME = 'meetingName'
ISBREAKOUT = 'isBreakout'
-
+
def initialize(redis)
@redis = redis
end
@@ -378,16 +389,16 @@ module BigBlueButton
def delete_events(meeting_id)
meeting_metadata = @redis.metadata_for(meeting_id)
if (meeting_metadata != nil)
- msgs = @redis.events_for(meeting_id)
+ msgs = @redis.events_for(meeting_id)
msgs.each do |msg|
- @redis.delete_event_info_for(meeting_id, msg)
+ @redis.delete_event_info_for(meeting_id, msg)
end
@redis.delete_events_for(meeting_id)
end
- @redis.delete_metadata_for(meeting_id)
- @redis.delete_breakout_metadata_for(meeting_id)
+ @redis.delete_metadata_for(meeting_id)
+ @redis.delete_breakout_metadata_for(meeting_id)
@redis.delete_breakout_rooms_for(meeting_id)
end
-
+
end
end
diff --git a/record-and-playback/core/lib/recordandplayback/workers/archive_worker.rb b/record-and-playback/core/lib/recordandplayback/workers/archive_worker.rb
old mode 100644
new mode 100755
index 747fbb79e1..3a58c6696f
--- a/record-and-playback/core/lib/recordandplayback/workers/archive_worker.rb
+++ b/record-and-playback/core/lib/recordandplayback/workers/archive_worker.rb
@@ -42,6 +42,10 @@ module BigBlueButton
!File.exist?(@archived_fail)
)
+ if File.exist?(@archived_norecord)
+ @publisher.put_archive_norecord(@meeting_id)
+ end
+
@publisher.put_archive_ended(@meeting_id, success: step_succeeded, step_time: step_time)
if step_succeeded
diff --git a/record-and-playback/core/lib/recordandplayback/workers/process_worker.rb b/record-and-playback/core/lib/recordandplayback/workers/process_worker.rb
old mode 100644
new mode 100755
diff --git a/record-and-playback/core/lib/recordandplayback/workers/publish_worker.rb b/record-and-playback/core/lib/recordandplayback/workers/publish_worker.rb
old mode 100644
new mode 100755
index 982e7d61fb..5c676aa34b
--- a/record-and-playback/core/lib/recordandplayback/workers/publish_worker.rb
+++ b/record-and-playback/core/lib/recordandplayback/workers/publish_worker.rb
@@ -35,6 +35,7 @@ module BigBlueButton
# If the publish directory exists, the script does nothing
FileUtils.rm_rf("#{@recording_dir}/publish/#{@format_name}/#{@full_id}")
+
remove_status_files
# For legacy reasons, the meeting ID passed to the publish script contains
diff --git a/record-and-playback/core/lib/recordandplayback/workers/sanity_worker.rb b/record-and-playback/core/lib/recordandplayback/workers/sanity_worker.rb
old mode 100644
new mode 100755
diff --git a/record-and-playback/core/scripts/bigbluebutton.yml b/record-and-playback/core/scripts/bigbluebutton.yml
index b600778d23..8d58a20dde 100755
--- a/record-and-playback/core/scripts/bigbluebutton.yml
+++ b/record-and-playback/core/scripts/bigbluebutton.yml
@@ -22,6 +22,12 @@ redis_port: 6379
# redis_workers_host: 127.0.0.1
# redis_workers_port: 6379
+# Set to true to insert recording process status into
+# redis list with key "store_recording_status: true".
+# This is useful if you want to track progress status
+# and have another script process it.
+store_recording_status: false
+
# Sequence of recording steps. Keys are the current step, values
# are the next step(s). Examples:
# current_step: next_step