Browse Source

Merge pull request #1738 from virtuacoplenny/lenny/connection-stats-pub-sub

ref(stats): process stats through one pub/sub
master
George Politis 7 years ago
parent
commit
68d40b4fa4

+ 2
- 12
conference.js View File

@@ -48,6 +48,7 @@ import {
48 48
     trackAdded,
49 49
     trackRemoved
50 50
 } from './react/features/base/tracks';
51
+import { statsEmitter } from './react/features/connection-indicator';
51 52
 import { showDesktopPicker } from  './react/features/desktop-picker';
52 53
 import {
53 54
     mediaPermissionPromptVisibilityChanged,
@@ -64,8 +65,6 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
64 65
 const TrackEvents = JitsiMeetJS.events.track;
65 66
 const TrackErrors = JitsiMeetJS.errors.track;
66 67
 
67
-const ConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
68
-
69 68
 const eventEmitter = new EventEmitter();
70 69
 
71 70
 let room;
@@ -1726,16 +1725,7 @@ export default {
1726 1725
             }
1727 1726
         });
1728 1727
 
1729
-        room.on(ConnectionQualityEvents.LOCAL_STATS_UPDATED,
1730
-            (stats) => {
1731
-                APP.UI.updateLocalStats(stats.connectionQuality, stats);
1732
-
1733
-        });
1734
-
1735
-        room.on(ConnectionQualityEvents.REMOTE_STATS_UPDATED,
1736
-            (id, stats) => {
1737
-                APP.UI.updateRemoteStats(id, stats.connectionQuality, stats);
1738
-        });
1728
+        statsEmitter.startListeningForStats(room);
1739 1729
 
1740 1730
         room.addCommandListener(this.commands.defaults.ETHERPAD, ({value}) => {
1741 1731
             APP.UI.initEtherpad(value);

+ 0
- 19
modules/UI/UI.js View File

@@ -972,25 +972,6 @@ UI.hideStats = function () {
972 972
     VideoLayout.hideStats();
973 973
 };
974 974
 
975
-/**
976
- * Update local connection quality statistics.
977
- * @param {number} percent
978
- * @param {object} stats
979
- */
980
-UI.updateLocalStats = function (percent, stats) {
981
-    VideoLayout.updateLocalConnectionStats(percent, stats);
982
-};
983
-
984
-/**
985
- * Update connection quality statistics for remote user.
986
- * @param {string} id user id
987
- * @param {number} percent
988
- * @param {object} stats
989
- */
990
-UI.updateRemoteStats = function (id, percent, stats) {
991
-    VideoLayout.updateConnectionStats(id, percent, stats);
992
-};
993
-
994 975
 /**
995 976
  * Mark video as interrupted or not.
996 977
  * @param {boolean} interrupted if video is interrupted

+ 1
- 1
modules/UI/videolayout/LocalVideo.js View File

@@ -37,7 +37,7 @@ function LocalVideo(VideoLayout, emitter) {
37 37
     this.setDisplayName();
38 38
 
39 39
     this.addAudioLevelIndicator();
40
-    this.updateConnectionIndicator();
40
+    this.updateIndicators();
41 41
 }
42 42
 
43 43
 LocalVideo.prototype = Object.create(SmallVideo.prototype);

+ 1
- 13
modules/UI/videolayout/RemoteVideo.js View File

@@ -48,7 +48,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
48 48
     this.hasRemoteVideoMenu = false;
49 49
     this._supportsRemoteControl = false;
50 50
     this.addRemoteVideoContainer();
51
-    this.updateConnectionIndicator();
51
+    this.updateIndicators();
52 52
     this.setDisplayName();
53 53
     this.bindHoverHandler();
54 54
     this.flipX = false;
@@ -632,18 +632,6 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
632 632
     }
633 633
 };
634 634
 
635
-RemoteVideo.prototype.updateResolution = function (resolution) {
636
-    this.updateConnectionIndicator({ resolution });
637
-};
638
-
639
-/**
640
- * Updates this video framerate indication.
641
- * @param framerate the value to update
642
- */
643
-RemoteVideo.prototype.updateFramerate = function (framerate) {
644
-    this.updateConnectionIndicator({ framerate });
645
-};
646
-
647 635
 /**
648 636
  * Sets the display name for the given video span id.
649 637
  *

+ 9
- 33
modules/UI/videolayout/SmallVideo.js View File

@@ -84,13 +84,14 @@ function SmallVideo(VideoLayout) {
84 84
     this.disableUpdateView = false;
85 85
 
86 86
     /**
87
-     * Statistics to display within the connection indicator. With new updates,
88
-     * only changed values are updated through assignment to a new reference.
87
+     * The current state of the user's bridge connection. The value should be
88
+     * a string as enumerated in the library's participantConnectionStatus
89
+     * constants.
89 90
      *
90 91
      * @private
91
-     * @type {object}
92
+     * @type {string|null}
92 93
      */
93
-    this._cachedConnectionStats = {};
94
+    this._connectionStatus = null;
94 95
 
95 96
     /**
96 97
      * Whether or not the ConnectionIndicator's popover is hovered. Modifies
@@ -260,18 +261,6 @@ SmallVideo.prototype.bindHoverHandler = function () {
260 261
     );
261 262
 };
262 263
 
263
-/**
264
- * Updates the data for the indicator
265
- * @param id the id of the indicator
266
- * @param percent the percent for connection quality
267
- * @param object the data
268
- */
269
-SmallVideo.prototype.updateConnectionStats = function (percent, object) {
270
-    const newStats = Object.assign({}, object, { percent });
271
-
272
-    this.updateConnectionIndicator(newStats);
273
-};
274
-
275 264
 /**
276 265
  * Unmounts the ConnectionIndicator component.
277 266
 
@@ -289,7 +278,8 @@ SmallVideo.prototype.removeConnectionIndicator = function () {
289 278
  * @returns {void}
290 279
  */
291 280
 SmallVideo.prototype.updateConnectionStatus = function (connectionStatus) {
292
-    this.updateConnectionIndicator({ connectionStatus });
281
+    this._connectionStatus = connectionStatus;
282
+    this.updateIndicators();
293 283
 };
294 284
 
295 285
 /**
@@ -741,21 +731,6 @@ SmallVideo.prototype.initBrowserSpecificProperties = function() {
741 731
     }
742 732
 };
743 733
 
744
-/**
745
- * Creates or updates the connection indicator. Updates the previously known
746
- * statistics about the participant's connection.
747
- *
748
- * @param {Object} newStats - New statistics to merge with previously known
749
- * statistics about the participant's connection.
750
- * @returns {void}
751
- */
752
-SmallVideo.prototype.updateConnectionIndicator = function (newStats = {}) {
753
-    this._cachedConnectionStats
754
-        = Object.assign({}, this._cachedConnectionStats, newStats);
755
-
756
-    this.updateIndicators();
757
-};
758
-
759 734
 /**
760 735
  * Updates the React element responsible for showing connection status, dominant
761 736
  * speaker, and raised hand icons. Uses instance variables to get the necessary
@@ -775,11 +750,12 @@ SmallVideo.prototype.updateIndicators = function () {
775 750
         <div>
776 751
             { this._showConnectionIndicator
777 752
                 ? <ConnectionIndicator
753
+                    connectionStatus = { this._connectionStatus }
778 754
                     iconSize = { iconSize }
779 755
                     isLocalVideo = { this.isLocal }
780 756
                     onHover = { this._onPopoverHover }
781 757
                     showMoreLink = { this.isLocal }
782
-                    stats = { this._cachedConnectionStats } />
758
+                    userID = { this.id } />
783 759
                 : null }
784 760
             { this._showRaisedHand
785 761
                 ? <RaisedHandIndicator iconSize = { iconSize } /> : null }

+ 5
- 54
modules/UI/videolayout/VideoLayout.js View File

@@ -211,6 +211,11 @@ var VideoLayout = {
211 211
         if (largeVideo && !largeVideo.id) {
212 212
             this.updateLargeVideo(APP.conference.getMyUserId(), true);
213 213
         }
214
+
215
+        // FIXME: replace this call with a generic update call once SmallVideo
216
+        // only contains a ReactElement. Then remove this call once the
217
+        // Filmstrip is fully in React.
218
+        localVideoThumbnail.updateIndicators();
214 219
     },
215 220
 
216 221
     /**
@@ -766,60 +771,6 @@ var VideoLayout = {
766 771
         }
767 772
     },
768 773
 
769
-    /**
770
-     * Updates local stats
771
-     * @param percent
772
-     * @param object
773
-     */
774
-    updateLocalConnectionStats (percent, object) {
775
-        const { framerate, resolution } = object;
776
-
777
-        // FIXME overwrites 'lib-jitsi-meet' internal object
778
-        // Why library internal objects are passed as event's args ?
779
-        object.resolution = resolution[APP.conference.getMyUserId()];
780
-        object.framerate = framerate[APP.conference.getMyUserId()];
781
-        localVideoThumbnail.updateConnectionStats(percent, object);
782
-
783
-        Object.keys(resolution).forEach(function (id) {
784
-            if (APP.conference.isLocalId(id)) {
785
-                return;
786
-            }
787
-
788
-            let resolutionValue = resolution[id];
789
-            let remoteVideo = remoteVideos[id];
790
-
791
-            if (resolutionValue && remoteVideo) {
792
-                remoteVideo.updateResolution(resolutionValue);
793
-            }
794
-        });
795
-
796
-        Object.keys(framerate).forEach(function (id) {
797
-            if (APP.conference.isLocalId(id)) {
798
-                return;
799
-            }
800
-
801
-            const framerateValue = framerate[id];
802
-            const remoteVideo = remoteVideos[id];
803
-
804
-            if (framerateValue && remoteVideo) {
805
-                remoteVideo.updateFramerate(framerateValue);
806
-            }
807
-        });
808
-    },
809
-
810
-    /**
811
-     * Updates remote stats.
812
-     * @param id the id associated with the stats
813
-     * @param percent the connection quality percent
814
-     * @param object the stats data
815
-     */
816
-    updateConnectionStats (id, percent, object) {
817
-        let remoteVideo = remoteVideos[id];
818
-        if (remoteVideo) {
819
-            remoteVideo.updateConnectionStats(percent, object);
820
-        }
821
-    },
822
-
823 774
     /**
824 775
      * Hides the connection indicator
825 776
      * @param id

+ 66
- 20
react/features/connection-indicator/components/ConnectionIndicator.js View File

@@ -5,6 +5,8 @@ import JitsiPopover from '../../../../modules/UI/util/JitsiPopover';
5 5
 import { JitsiParticipantConnectionStatus } from '../../base/lib-jitsi-meet';
6 6
 import { ConnectionStatsTable } from '../../connection-stats';
7 7
 
8
+import statsEmitter from '../statsEmitter';
9
+
8 10
 declare var $: Object;
9 11
 declare var interfaceConfig: Object;
10 12
 
@@ -57,6 +59,14 @@ class ConnectionIndicator extends Component {
57 59
      * @static
58 60
      */
59 61
     static propTypes = {
62
+        /**
63
+         * The current condition of the user's connection, matching one of the
64
+         * enumerated values in the library.
65
+         *
66
+         * @type {JitsiParticipantConnectionStatus}
67
+         */
68
+        connectionStatus: React.PropTypes.string,
69
+
60 70
         /**
61 71
          * Whether or not the displays stats are for local video.
62 72
          */
@@ -74,25 +84,15 @@ class ConnectionIndicator extends Component {
74 84
         showMoreLink: React.PropTypes.bool,
75 85
 
76 86
         /**
77
-         * An object that contains statistics related to connection quality.
78
-         *
79
-         * {
80
-         *     bandwidth: Object,
81
-         *     bitrate: Object,
82
-         *     connectionStatus: String,
83
-         *     framerate: Object,
84
-         *     packetLoss: Object,
85
-         *     percent: Number,
86
-         *     resolution: Object,
87
-         *     transport:  Array
88
-         * }
87
+         * Invoked to obtain translated strings.
89 88
          */
90
-        stats: React.PropTypes.object,
89
+        t: React.PropTypes.func,
91 90
 
92 91
         /**
93
-         * Invoked to obtain translated strings.
92
+         * The user ID associated with the displayed connection indication and
93
+         * stats.
94 94
          */
95
-        t: React.PropTypes.func
95
+        userID: React.PropTypes.string
96 96
     };
97 97
 
98 98
     /**
@@ -121,10 +121,19 @@ class ConnectionIndicator extends Component {
121 121
              *
122 122
              * @type {boolean}
123 123
              */
124
-            showMoreStats: false
124
+            showMoreStats: false,
125
+
126
+            /**
127
+             * Cache of the stats received from subscribing to stats emitting.
128
+             * The keys should be the name of the stat. With each stat update,
129
+             * updates stats are mixed in with cached stats and a new stats
130
+             * object is set in state.
131
+             */
132
+            stats: {}
125 133
         };
126 134
 
127 135
         // Bind event handlers so they are only bound once for every instance.
136
+        this._onStatsUpdated = this._onStatsUpdated.bind(this);
128 137
         this._onToggleShowMore = this._onToggleShowMore.bind(this);
129 138
         this._setRootElement = this._setRootElement.bind(this);
130 139
     }
@@ -136,6 +145,9 @@ class ConnectionIndicator extends Component {
136 145
      * returns {void}
137 146
      */
138 147
     componentDidMount() {
148
+        statsEmitter.subscribeToClientStats(
149
+            this.props.userID, this._onStatsUpdated);
150
+
139 151
         this.popover = new JitsiPopover($(this._rootElement), {
140 152
             content: this._renderStatisticsTable(),
141 153
             skin: 'black',
@@ -153,7 +165,14 @@ class ConnectionIndicator extends Component {
153 165
      * @inheritdoc
154 166
      * returns {void}
155 167
      */
156
-    componentDidUpdate() {
168
+    componentDidUpdate(prevProps) {
169
+        if (prevProps.userID !== this.props.userID) {
170
+            statsEmitter.unsubscribeToClientStats(
171
+                prevProps.userID, this._onStatsUpdated);
172
+            statsEmitter.subscribeToClientStats(
173
+                this.props.userID, this._onStatsUpdated);
174
+        }
175
+
157 176
         this.popover.updateContent(this._renderStatisticsTable());
158 177
     }
159 178
 
@@ -164,6 +183,9 @@ class ConnectionIndicator extends Component {
164 183
      * returns {void}
165 184
      */
166 185
     componentWillUnmount() {
186
+        statsEmitter.unsubscribeToClientStats(
187
+            this.props.userID, this._onStatsUpdated);
188
+
167 189
         this.popover.forceHide();
168 190
         this.popover.remove();
169 191
     }
@@ -186,6 +208,30 @@ class ConnectionIndicator extends Component {
186 208
         );
187 209
     }
188 210
 
211
+    /**
212
+     * Callback invoked when new connection stats associated with the passed in
213
+     * user ID are available. Will update the component's display of current
214
+     * statistics.
215
+     *
216
+     * @param {Object} stats - Connection stats from the library.
217
+     * @private
218
+     * @returns {void}
219
+     */
220
+    _onStatsUpdated(stats = {}) {
221
+        const { connectionQuality } = stats;
222
+        const newPercentageState = typeof connectionQuality === 'undefined'
223
+            ? {} : { percent: connectionQuality };
224
+        const newStats = Object.assign(
225
+            {},
226
+            this.state.stats,
227
+            stats,
228
+            newPercentageState);
229
+
230
+        this.setState({
231
+            stats: newStats
232
+        });
233
+    }
234
+
189 235
     /**
190 236
      * Callback to invoke when the show more link in the popover content is
191 237
      * clicked. Sets the state which will determine if the popover should show
@@ -204,7 +250,7 @@ class ConnectionIndicator extends Component {
204 250
      * @returns {ReactElement}
205 251
      */
206 252
     _renderIcon() {
207
-        switch (this.props.stats.connectionStatus) {
253
+        switch (this.props.connectionStatus) {
208 254
         case JitsiParticipantConnectionStatus.INTERRUPTED:
209 255
             return (
210 256
                 <span className = 'connection_lost'>
@@ -218,7 +264,7 @@ class ConnectionIndicator extends Component {
218 264
                 </span>
219 265
             );
220 266
         default: {
221
-            const { percent } = this.props.stats;
267
+            const { percent } = this.state.stats;
222 268
             const width = QUALITY_TO_WIDTH.find(x => percent >= x.percent);
223 269
             const iconWidth = width && width.width
224 270
                 ? { width: width && width.width } : {};
@@ -253,7 +299,7 @@ class ConnectionIndicator extends Component {
253 299
             packetLoss,
254 300
             resolution,
255 301
             transport
256
-        } = this.props.stats;
302
+        } = this.state.stats;
257 303
 
258 304
         return (
259 305
             <ConnectionStatsTable

+ 2
- 0
react/features/connection-indicator/index.js View File

@@ -1 +1,3 @@
1 1
 export * from './components';
2
+
3
+export { default as statsEmitter } from './statsEmitter';

+ 153
- 0
react/features/connection-indicator/statsEmitter.js View File

@@ -0,0 +1,153 @@
1
+import _ from 'lodash';
2
+
3
+import JitsiMeetJS from '../base/lib-jitsi-meet';
4
+
5
+declare var APP: Object;
6
+
7
+/**
8
+ * Contains all the callbacks to be notified when stats are updated.
9
+ *
10
+ * {
11
+ *     userId: Function[]
12
+ * }
13
+ */
14
+const subscribers = {};
15
+
16
+/**
17
+ * A singleton that acts as a pub/sub service for connection stat updates.
18
+ */
19
+const statsEmitter = {
20
+    /**
21
+     * Have {@code statsEmitter} subscribe to stat updates from a given
22
+     * conference.
23
+     *
24
+     * @param {JitsiConference} conference - The conference for which
25
+     * {@code statsEmitter} should subscribe for stat updates.
26
+     * @returns {void}
27
+     */
28
+    startListeningForStats(conference) {
29
+        const { connectionQuality } = JitsiMeetJS.events;
30
+
31
+        conference.on(connectionQuality.LOCAL_STATS_UPDATED,
32
+            stats => this._onStatsUpdated(conference.myUserId(), stats));
33
+
34
+        conference.on(connectionQuality.REMOTE_STATS_UPDATED,
35
+            (id, stats) => this._emitStatsUpdate(id, stats));
36
+    },
37
+
38
+    /**
39
+     * Add a subscriber to be notified when stats are updated for a specified
40
+     * user id.
41
+     *
42
+     * @param {string} id - The user id whose stats updates are of interest.
43
+     * @param {Function} callback - The function to invoke when stats for the
44
+     * user have been updated.
45
+     * @returns {void}
46
+     */
47
+    subscribeToClientStats(id, callback) {
48
+        if (!id) {
49
+            return;
50
+        }
51
+
52
+        if (!subscribers[id]) {
53
+            subscribers[id] = [];
54
+        }
55
+
56
+        subscribers[id].push(callback);
57
+    },
58
+
59
+    /**
60
+     * Remove a subscriber that is listening for stats updates for a specified
61
+     * user id.
62
+     *
63
+     * @param {string} id - The user id whose stats updates are no longer of
64
+     * interest.
65
+     * @param {Function} callback - The function that is currently subscribed to
66
+     * stat updates for the specified user id.
67
+     * @returns {void}
68
+     */
69
+    unsubscribeToClientStats(id, callback) {
70
+        if (!subscribers[id]) {
71
+            return;
72
+        }
73
+
74
+        const filteredSubscribers = subscribers[id].filter(
75
+            subscriber => subscriber !== callback);
76
+
77
+        if (filteredSubscribers.length) {
78
+            subscribers[id] = filteredSubscribers;
79
+        } else {
80
+            delete subscribers[id];
81
+        }
82
+    },
83
+
84
+    /**
85
+     * Emit a stat update to all those listening for a specific user's
86
+     * connection stats.
87
+     *
88
+     * @param {string} id - The user id the stats are associated with.
89
+     * @param {Object} stats - New connection stats for the user.
90
+     * @returns {void}
91
+     */
92
+    _emitStatsUpdate(id, stats = {}) {
93
+        const callbacks = subscribers[id] || [];
94
+
95
+        callbacks.forEach(callback => {
96
+            callback(stats);
97
+        });
98
+    },
99
+
100
+    /**
101
+     * Emit a stat update to all those listening for local stat updates. Will
102
+     * also update listeners of remote user stats of changes related to their
103
+     * stats.
104
+     *
105
+     * @param {string} currentUserId - The user id for the local user.
106
+     * @param {Object} stats - Connection stats for the local user as provided
107
+     * by the library.
108
+     * @returns {void}
109
+     */
110
+    _onStatsUpdated(currentUserId, stats) {
111
+        const allUserFramerates = stats.framerate;
112
+        const allUserResolutions = stats.resolution;
113
+
114
+        const currentUserFramerate = allUserFramerates[currentUserId];
115
+        const currentUserResolution = allUserResolutions[currentUserId];
116
+
117
+        // FIXME resolution and framerate are hashes keyed off of user ids with
118
+        // stat values. Receivers of stats expect resolution and framerate to
119
+        // be primatives, not hashes, so overwrites the 'lib-jitsi-meet' stats
120
+        // objects.
121
+        stats.framerate = currentUserFramerate;
122
+        stats.resolution = currentUserResolution;
123
+
124
+        this._emitStatsUpdate(currentUserId, stats);
125
+
126
+        // Get all the unique user ids from the framerate and resolution stats
127
+        // and update remote user stats as needed.
128
+        const framerateUserIds = Object.keys(allUserFramerates);
129
+        const resolutionUserIds = Object.keys(allUserResolutions);
130
+
131
+        _.union(framerateUserIds, resolutionUserIds)
132
+            .filter(id => id !== currentUserId)
133
+            .forEach(id => {
134
+                const remoteUserStats = {};
135
+
136
+                const framerate = allUserFramerates[id];
137
+
138
+                if (framerate) {
139
+                    remoteUserStats.framerate = framerate;
140
+                }
141
+
142
+                const resolution = allUserResolutions[id];
143
+
144
+                if (resolution) {
145
+                    remoteUserStats.resolution = resolution;
146
+                }
147
+
148
+                this._emitStatsUpdate(id, remoteUserStats);
149
+            });
150
+    }
151
+};
152
+
153
+export default statsEmitter;

Loading…
Cancel
Save