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';