diff --git a/bigbluebutton-html5/imports/api/network-information/index.js b/bigbluebutton-html5/imports/api/network-information/index.js new file mode 100644 index 0000000000000000000000000000000000000000..de033433a7584c7145c9778ac205ecc165c91b78 --- /dev/null +++ b/bigbluebutton-html5/imports/api/network-information/index.js @@ -0,0 +1,9 @@ +import { Meteor } from 'meteor/meteor'; + +const NetworkInformation = new Mongo.Collection('network-information'); + +if (Meteor.isServer) { + NetworkInformation._ensureIndex({ meetingId: 1 }); +} + +export default NetworkInformation; diff --git a/bigbluebutton-html5/imports/api/network-information/server/index.js b/bigbluebutton-html5/imports/api/network-information/server/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c043da1bfd8f0bb2867c94b40f8dacbf5bf17bf7 --- /dev/null +++ b/bigbluebutton-html5/imports/api/network-information/server/index.js @@ -0,0 +1,2 @@ +import './methods'; +import './publisher'; diff --git a/bigbluebutton-html5/imports/api/network-information/server/methods.js b/bigbluebutton-html5/imports/api/network-information/server/methods.js new file mode 100644 index 0000000000000000000000000000000000000000..6eaf424c0d48693e695690f5330d48ed3cb74300 --- /dev/null +++ b/bigbluebutton-html5/imports/api/network-information/server/methods.js @@ -0,0 +1,6 @@ +import { Meteor } from 'meteor/meteor'; +import userInstabilityDetected from './methods/userInstabilityDetected'; + +Meteor.methods({ + userInstabilityDetected, +}); diff --git a/bigbluebutton-html5/imports/api/network-information/server/methods/userInstabilityDetected.js b/bigbluebutton-html5/imports/api/network-information/server/methods/userInstabilityDetected.js new file mode 100644 index 0000000000000000000000000000000000000000..52e52d360397c2b186969c79f9315a17ac3b5819 --- /dev/null +++ b/bigbluebutton-html5/imports/api/network-information/server/methods/userInstabilityDetected.js @@ -0,0 +1,22 @@ +import { check } from 'meteor/check'; +import NetworkInformation from '/imports/api/network-information'; +import Logger from '/imports/startup/server/logger'; + +export default function userInstabilityDetected(credentials, sender) { + const { meetingId, requesterUserId: receiver } = credentials; + + check(meetingId, String); + check(receiver, String); + check(sender, String); + + const payload = { + time: new Date().getTime(), + meetingId, + receiver, + sender, + }; + + Logger.debug(`Receiver ${receiver} reported a network instability`); + + return NetworkInformation.insert(payload); +} diff --git a/bigbluebutton-html5/imports/api/network-information/server/publisher.js b/bigbluebutton-html5/imports/api/network-information/server/publisher.js new file mode 100644 index 0000000000000000000000000000000000000000..0e83b5cb9e987e80eb03f395f64fbf57a6aa4062 --- /dev/null +++ b/bigbluebutton-html5/imports/api/network-information/server/publisher.js @@ -0,0 +1,21 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import NetworkInformation from '/imports/api/network-information'; + +function networkInformation(credentials) { + const { meetingId } = credentials; + + check(meetingId, String); + + return NetworkInformation.find({ + meetingId, + }); +} + +function publish(...args) { + const boundNetworkInformation = networkInformation.bind(this); + + return boundNetworkInformation(...args); +} + +Meteor.publish('network-information', publish); diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setUserEffectiveConnectionType.js b/bigbluebutton-html5/imports/api/users/server/methods/setUserEffectiveConnectionType.js index 7a50c580e6a4f76117f0dd978eede1b657f382a1..fd6a632d90cfb6425b9d6d661558a94c1f92a79f 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods/setUserEffectiveConnectionType.js +++ b/bigbluebutton-html5/imports/api/users/server/methods/setUserEffectiveConnectionType.js @@ -4,7 +4,6 @@ import RedisPubSub from '/imports/startup/server/redis'; import Logger from '/imports/startup/server/logger'; import setEffectiveConnectionType from '../modifiers/setUserEffectiveConnectionType'; -// http://wicg.github.io/netinfo/#effective-connection-types export default function setUserEffectiveConnectionType(credentials, effectiveConnectionType) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index fe043dd91c295838f60ae307b481463adc3961d0..630d62f03343053beedae7bb6f6c7eac68e73897 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -202,7 +202,7 @@ Base.defaultProps = defaultProps; const SUBSCRIPTIONS_NAME = [ 'users', 'meetings', 'polls', 'presentations', 'slides', 'captions', 'voiceUsers', 'whiteboard-multi-user', 'screenshare', - 'group-chat', 'presentation-pods', 'users-settings', 'guestUser', + 'group-chat', 'presentation-pods', 'users-settings', 'guestUser', 'network-information', ]; const BaseContainer = withTracker(() => { diff --git a/bigbluebutton-html5/imports/ui/components/slow-connection/component.jsx b/bigbluebutton-html5/imports/ui/components/slow-connection/component.jsx index ef494623b93c928dd736785b6e2a683270ab7c08..9d8c102a12689e5999912fe07d9a1ba63dbd7834 100644 --- a/bigbluebutton-html5/imports/ui/components/slow-connection/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/slow-connection/component.jsx @@ -3,15 +3,15 @@ import cx from 'classnames'; import { styles } from './styles'; const SLOW_CONNECTIONS_TYPES = { - 'slow-2g': { + critical: { level: styles.bad, bars: styles.oneBar, }, - '2g': { + danger: { level: styles.bad, bars: styles.twoBars, }, - '3g': { + warning: { level: styles.warning, bars: styles.threeBars, }, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx index dc50a5b67e6c2f5f5c837ec749baa3eb93d24292..2660b4f6739ace53d2b476c7606ef45b7234f7de 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx @@ -4,7 +4,6 @@ import Icon from '/imports/ui/components/icon/component'; import SlowConnection from '/imports/ui/components/slow-connection/component'; import { styles } from './styles'; -// https://github.com/bigbluebutton/bigbluebutton/issues/5286#issuecomment-465342716 const SLOW_CONNECTIONS_TYPES = Meteor.settings.public.app.effectiveConnection; const propTypes = { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index c1b53892010031b5db6eb2f90f986fba01c04893..a0edfa4fecc62269a9039fc1ca5c3a951c7059ea 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -185,7 +185,7 @@ class VideoProvider extends Component { this.customGetStats(peer.peerConnection, peer.peerConnection.getRemoteStreams()[0].getVideoTracks()[0], (stats => updateWebcamStats(id, stats))); } }); - }, 10000); + }, 5000); } componentWillUpdate({ users, userId }) { @@ -560,7 +560,6 @@ class VideoProvider extends Component { if (this.webRtcPeers[id].peerConnection) { this.webRtcPeers[id].peerConnection.oniceconnectionstatechange = this._getOnIceConnectionStateChangeCallback(id); } - console.log('VideoProvider.createWebRTCPeer', this.webRtcPeers); newWebcamConnection({ userId: id, peer: this.webRtcPeers[id] }); currentWebcamConnections(this.webRtcPeers); } @@ -769,6 +768,7 @@ class VideoProvider extends Component { encodeUsagePercent: videoInOrOutbound.encodeUsagePercent, rtt: videoInOrOutbound.rtt, currentDelay: videoInOrOutbound.currentDelay, + pliCount: videoInOrOutbound.pliCount, }; const videoStatsArray = statsState; @@ -827,6 +827,7 @@ class VideoProvider extends Component { encodeUsagePercent: videoStats.encodeUsagePercent, rtt: videoStats.rtt, currentDelay: videoStats.currentDelay, + pliCount: videoStats.pliCount, }, }; diff --git a/bigbluebutton-html5/imports/ui/services/network-information/index.js b/bigbluebutton-html5/imports/ui/services/network-information/index.js index 414b4943870b08bfd9b2a1c2bd0615abc840334d..78e0e37b3edf1be0b5d23056111622d8cc2bb597 100644 --- a/bigbluebutton-html5/imports/ui/services/network-information/index.js +++ b/bigbluebutton-html5/imports/ui/services/network-information/index.js @@ -1,4 +1,8 @@ -const NetworkInformationCollection = new Mongo.Collection(null); +import NetworkInformation from '/imports/api/network-information'; +import { makeCall } from '/imports/ui/services/api'; +import Auth from '/imports/ui/services/auth'; + +const NetworkInformationLocal = new Mongo.Collection(null); const NAVIGATOR_CONNECTION = 'NAVIGATOR_CONNECTION'; const NUMBER_OF_WEBCAMS_CHANGED = 'NUMBER_OF_WEBCAMS_CHANGED'; @@ -6,6 +10,11 @@ const STARTED_WEBCAM_SHARING = 'STARTED_WEBCAM_SHARING'; const STOPPED_WEBCAM_SHARING = 'STOPPED_WEBCAM_SHARING'; const WEBCAMS_GET_STATUS = 'WEBCAMS_GET_STATUS'; +const DANGER_BEGIN_TIME = 5000; +const DANGER_END_TIME = 30000; + +const WARNING_END_TIME = 60000; + let monitoringIntervalRef; export const currentWebcamConnections = (webrtcConnections) => { @@ -15,7 +24,7 @@ export const currentWebcamConnections = (webrtcConnections) => { payload: Object.keys(webrtcConnections), }; - NetworkInformationCollection.insert(doc); + NetworkInformationLocal.insert(doc); }; export const deleteWebcamConnection = (id) => { @@ -25,10 +34,10 @@ export const deleteWebcamConnection = (id) => { payload: { id }, }; - NetworkInformationCollection.insert(doc); + NetworkInformationLocal.insert(doc); }; -export const getCurrentWebcams = () => NetworkInformationCollection +export const getCurrentWebcams = () => NetworkInformationLocal .findOne({ event: NUMBER_OF_WEBCAMS_CHANGED, }, { sort: { timestamp: -1 } }); @@ -45,13 +54,74 @@ export const newWebcamConnection = (webRtcPeer) => { }, }; - NetworkInformationCollection.insert(doc); + NetworkInformationLocal.insert(doc); }; export const startBandwidthMonitoring = () => { monitoringIntervalRef = setInterval(() => { - console.log('startBandwidthMonitoring', NetworkInformationCollection.find({}).fetch()); - }, 15000); + const monitoringTime = new Date().getTime(); + + const dangerLowerBoundary = monitoringTime - DANGER_BEGIN_TIME; + + const warningLowerBoundary = monitoringTime - DANGER_END_TIME; + const warningUpperBoundary = monitoringTime - WARNING_END_TIME; + + const warningZone = NetworkInformationLocal + .find({ + event: WEBCAMS_GET_STATUS, + timestamp: { $lte: warningLowerBoundary, $gt: warningUpperBoundary }, + $or: [ + { + 'payload.id': Auth.userID, + 'payload.stats.deltaPliCount': { $gt: 0 }, + }, + { + 'payload.id': { $ne: Auth.userID }, + 'payload.stats.deltaPacketsLost': { $gt: 0 }, + }, + ], + }).count(); + + const warningZoneReceivers = NetworkInformation + .find({ + sender: Auth.userID, + time: { $lte: warningLowerBoundary, $gt: warningUpperBoundary }, + }).count(); + + const dangerZone = NetworkInformationLocal + .find({ + event: WEBCAMS_GET_STATUS, + timestamp: { $lt: dangerLowerBoundary, $gte: warningLowerBoundary }, + $or: [ + { + 'payload.id': Auth.userID, + 'payload.stats.deltaPliCount': { $gt: 0 }, + }, + { + 'payload.id': { $ne: Auth.userID }, + 'payload.stats.deltaPacketsLost': { $gt: 0 }, + }, + ], + }).count(); + + const dangerZoneReceivers = NetworkInformation + .find({ + sender: Auth.userID, + time: { $lt: dangerLowerBoundary, $gte: warningLowerBoundary }, + }).count(); + + if (warningZoneReceivers && !dangerZone) { + if (!warningZoneReceivers) { + makeCall('setUserEffectiveConnectionType', 'warning'); + } + } else if (dangerZone) { + if (!dangerZoneReceivers) { + makeCall('setUserEffectiveConnectionType', 'danger'); + } + } else { + makeCall('setUserEffectiveConnectionType', ''); + } + }, 5000); }; export const stopBandwidthMonitoring = () => { @@ -69,12 +139,18 @@ export const updateNavigatorConnection = ({ effectiveType, downlink, rtt }) => { }, }; - NetworkInformationCollection.insert(doc); + NetworkInformationLocal.insert(doc); }; export const updateWebcamStats = (id, stats) => { if (!stats) return; + const lastStatus = NetworkInformationLocal + .findOne( + { event: WEBCAMS_GET_STATUS, 'payload.id': id }, + { sort: { timestamp: -1 } }, + ); + const { video } = stats; const doc = { @@ -83,11 +159,39 @@ export const updateWebcamStats = (id, stats) => { payload: { id, stats: video }, }; - NetworkInformationCollection.insert(doc); + if (lastStatus) { + const { + payload: { + stats: { + packetsLost, + packetsReceived, + packetsSent, + pliCount, + }, + }, + } = lastStatus; + const normalizedVideo = { ...video }; + + normalizedVideo.deltaPacketsLost = video.packetsLost - packetsLost; + normalizedVideo.deltaPacketsReceived = video.packetsReceived - packetsReceived; + normalizedVideo.deltaPacketsSent = video.packetsSent - packetsSent; + normalizedVideo.deltaPliCount = video.pliCount - pliCount; + + doc.payload = { + id, + stats: normalizedVideo, + }; + + if (normalizedVideo.deltaPacketsLost > 0) { + makeCall('userInstabilityDetected', id); + } + } + + NetworkInformationLocal.insert(doc); }; export default { - NetworkInformationCollection, + NetworkInformationLocal, currentWebcamConnections, deleteWebcamConnection, getCurrentWebcams, diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 90a0987f385940250a0d92465c85e80768b9622c..d7f5b9174300bf74870eab4ca2732adb9068fd31 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -83,9 +83,10 @@ public: showHelpButton: true enableExternalVideo: true effectiveConnection: - - slow-2g - - 2g - - 3g + - critical + - danger + - warning + - good kurento: wsUrl: HOST chromeDefaultExtensionKey: akgoaoikmbmhcopjgakkcepdgdgkjfbc diff --git a/bigbluebutton-html5/server/main.js b/bigbluebutton-html5/server/main.js index 82bffd06f5b058f992cec8e24cc9788ba4b04e0f..24ca70c43bc2f022be0f60945f729fe89c20219f 100755 --- a/bigbluebutton-html5/server/main.js +++ b/bigbluebutton-html5/server/main.js @@ -19,6 +19,7 @@ import '/imports/api/users-settings/server'; import '/imports/api/voice-users/server'; import '/imports/api/whiteboard-multi-user/server'; import '/imports/api/video/server'; +import '/imports/api/network-information/server'; import '/imports/api/external-videos/server'; import '/imports/api/guest-users/server';