diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/setUserEffectiveConnectionType.js b/bigbluebutton-html5/imports/api/users/server/modifiers/setUserEffectiveConnectionType.js index ce640717137eed4083f45712eb58d53c199fd22c..e5cf8272ba50f9059a810db99304369a648fb9f1 100644 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/setUserEffectiveConnectionType.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/setUserEffectiveConnectionType.js @@ -24,7 +24,7 @@ export default function setUserEffectiveConnectionType(meetingId, userId, effect } if (numChanged) { - Logger.info(`Update user ${userId} effective connection to ${effectiveConnectionType}`); + Logger.info(`Updated user ${userId} effective connection to ${effectiveConnectionType}`); } }; diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index 79645856ea824b5a75c8987cbc65f2427667eb39..34bcff2115b6eb941ea236f0660bcacbe6acc064 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -7,7 +7,6 @@ import browser from 'browser-detect'; import PanelManager from '/imports/ui/components/panel-manager/component'; import PollingContainer from '/imports/ui/components/polling/container'; import logger from '/imports/startup/client/logger'; -import { makeCall } from '/imports/ui/services/api'; import ActivityCheckContainer from '/imports/ui/components/activity-check/container'; import ToastContainer from '../toast/container'; import ModalContainer from '../modal/container'; @@ -15,6 +14,7 @@ import NotificationsBarContainer from '../notifications-bar/container'; import AudioContainer from '../audio/container'; import ChatAlertContainer from '../chat/alert/container'; import WaitingNotifierContainer from '/imports/ui/components/waiting-users/alert/container'; +import { startBandwidthMonitoring, updateNavigatorConnection } from '/imports/ui/services/network-information/index'; import { styles } from './styles'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; @@ -98,6 +98,8 @@ class App extends Component { navigator.connection.addEventListener('change', this.handleNetworkConnection); } + startBandwidthMonitoring(); + logger.info({ logCode: 'app_component_componentdidmount' }, 'Client loaded successfully'); } @@ -115,8 +117,7 @@ class App extends Component { } handleNetworkConnection() { - const { effectiveType } = navigator.connection; - makeCall('setUserEffectiveConnectionType', effectiveType); + updateNavigatorConnection(navigator.connection); } renderPanel() { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 84e24fe1a8e4d0a412e027a6e6db5b091d5ea878..c1b53892010031b5db6eb2f90f986fba01c04893 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -7,6 +7,13 @@ import ReconnectingWebSocket from 'reconnecting-websocket'; import logger from '/imports/startup/client/logger'; import { Session } from 'meteor/session'; import browser from 'browser-detect'; +import { + currentWebcamConnections, + getCurrentWebcams, + deleteWebcamConnection, + newWebcamConnection, + updateWebcamStats, +} from '/imports/ui/services/network-information/index'; import { tryGenerateIceCandidates } from '../../../utils/safari-webrtc'; import Auth from '/imports/ui/services/auth'; @@ -157,6 +164,28 @@ class VideoProvider extends Component { this.visibility.onVisible(this.unpauseViewers); this.visibility.onHidden(this.pauseViewers); + + this.currentWebcamsStatsInterval = setInterval(() => { + const currentWebcams = getCurrentWebcams(); + if (!currentWebcams) return; + + const { payload } = currentWebcams; + + payload.forEach((id) => { + const peer = this.webRtcPeers[id]; + + const hasLocalStream = peer && peer.started === true + && peer.peerConnection.getLocalStreams().length > 0; + const hasRemoteStream = peer && peer.started === true + && peer.peerConnection.getRemoteStreams().length > 0; + + if (hasLocalStream) { + this.customGetStats(peer.peerConnection, peer.peerConnection.getLocalStreams()[0].getVideoTracks()[0], (stats => updateWebcamStats(id, stats))); + } else if (hasRemoteStream) { + this.customGetStats(peer.peerConnection, peer.peerConnection.getRemoteStreams()[0].getVideoTracks()[0], (stats => updateWebcamStats(id, stats))); + } + }); + }, 10000); } componentWillUpdate({ users, userId }) { @@ -194,6 +223,8 @@ class VideoProvider extends Component { this.stopWebRTCPeer(id); }); + clearInterval(this.currentWebcamsStatsInterval); + // Close websocket connection to prevent multiple reconnects from happening this.ws.close(); } @@ -446,6 +477,8 @@ class VideoProvider extends Component { webRtcPeer.dispose(); } delete this.webRtcPeers[id]; + deleteWebcamConnection(id); + currentWebcamConnections(this.webRtcPeers); } else { this.logger('warn', 'No WebRTC peer to stop (not an error)', { cameraId: id }); } @@ -527,6 +560,9 @@ 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); } } @@ -765,9 +801,9 @@ class VideoProvider extends Component { let videoBitrate; if (videoStats.packetsReceived > 0) { // Remote video - videoLostPercentage = ((videoStats.packetsLost / ((videoStats.packetsLost + videoStats.packetsReceived) * 100)) || 0).toFixed(1); + videoLostPercentage = ((videoStats.packetsLost / ((videoStats.packetsLost + videoStats.packetsReceived)) * 100) || 0).toFixed(1); videoBitrate = Math.floor(videoKbitsReceivedPerSecond || 0); - videoLostRecentPercentage = ((videoIntervalPacketsLost / ((videoIntervalPacketsLost + videoIntervalPacketsReceived) * 100)) || 0).toFixed(1); + videoLostRecentPercentage = ((videoIntervalPacketsLost / ((videoIntervalPacketsLost + videoIntervalPacketsReceived)) * 100) || 0).toFixed(1); } else { videoLostPercentage = (((videoStats.packetsLost / (videoStats.packetsLost + videoStats.packetsSent)) * 100) || 0).toFixed(1); videoBitrate = Math.floor(videoKbitsSentPerSecond || 0); diff --git a/bigbluebutton-html5/imports/ui/services/network-information/index.js b/bigbluebutton-html5/imports/ui/services/network-information/index.js new file mode 100644 index 0000000000000000000000000000000000000000..414b4943870b08bfd9b2a1c2bd0615abc840334d --- /dev/null +++ b/bigbluebutton-html5/imports/ui/services/network-information/index.js @@ -0,0 +1,99 @@ +const NetworkInformationCollection = new Mongo.Collection(null); + +const NAVIGATOR_CONNECTION = 'NAVIGATOR_CONNECTION'; +const NUMBER_OF_WEBCAMS_CHANGED = 'NUMBER_OF_WEBCAMS_CHANGED'; +const STARTED_WEBCAM_SHARING = 'STARTED_WEBCAM_SHARING'; +const STOPPED_WEBCAM_SHARING = 'STOPPED_WEBCAM_SHARING'; +const WEBCAMS_GET_STATUS = 'WEBCAMS_GET_STATUS'; + +let monitoringIntervalRef; + +export const currentWebcamConnections = (webrtcConnections) => { + const doc = { + timestamp: new Date().getTime(), + event: NUMBER_OF_WEBCAMS_CHANGED, + payload: Object.keys(webrtcConnections), + }; + + NetworkInformationCollection.insert(doc); +}; + +export const deleteWebcamConnection = (id) => { + const doc = { + timestamp: new Date().getTime(), + event: STOPPED_WEBCAM_SHARING, + payload: { id }, + }; + + NetworkInformationCollection.insert(doc); +}; + +export const getCurrentWebcams = () => NetworkInformationCollection + .findOne({ + event: NUMBER_OF_WEBCAMS_CHANGED, + }, { sort: { timestamp: -1 } }); + +export const newWebcamConnection = (webRtcPeer) => { + const { userId, peer } = webRtcPeer; + const { id: mediaStreamId } = peer.getLocalStream(); + const doc = { + timestamp: new Date().getTime(), + event: STARTED_WEBCAM_SHARING, + payload: { + userId, + mediaStreamId, + }, + }; + + NetworkInformationCollection.insert(doc); +}; + +export const startBandwidthMonitoring = () => { + monitoringIntervalRef = setInterval(() => { + console.log('startBandwidthMonitoring', NetworkInformationCollection.find({}).fetch()); + }, 15000); +}; + +export const stopBandwidthMonitoring = () => { + clearInterval(monitoringIntervalRef); +}; + +export const updateNavigatorConnection = ({ effectiveType, downlink, rtt }) => { + const doc = { + timestamp: new Date().getTime(), + event: NAVIGATOR_CONNECTION, + payload: { + effectiveType, + downlink, + rtt, + }, + }; + + NetworkInformationCollection.insert(doc); +}; + +export const updateWebcamStats = (id, stats) => { + if (!stats) return; + + const { video } = stats; + + const doc = { + timestamp: new Date().getTime(), + event: WEBCAMS_GET_STATUS, + payload: { id, stats: video }, + }; + + NetworkInformationCollection.insert(doc); +}; + +export default { + NetworkInformationCollection, + currentWebcamConnections, + deleteWebcamConnection, + getCurrentWebcams, + newWebcamConnection, + startBandwidthMonitoring, + stopBandwidthMonitoring, + updateNavigatorConnection, + updateWebcamStats, +}; diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index af009fa54a5209716f57aeef4d53865f5974fa88..8d1bbe2700561fd5b40aebaf50291941e1e9c75b 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -564,5 +564,5 @@ "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop sharing video", "app.network.connection.effective.slow": "We're noticing connectivity issues.", "app.network.connection.effective.slow.help": "How to fix it?", - "app.externalVideo.noteLabel": "Note: Shared YouTube videos will not appear in the recording", + "app.externalVideo.noteLabel": "Note: Shared YouTube videos will not appear in the recording" }