Browse Source

feat(stats) add stats for mobile

master
tmoldovan8x8 4 years ago
parent
commit
5ecb5717c7
No account linked to committer's email address

+ 2
- 2
ios/Podfile.lock View File

293
     - React
293
     - React
294
   - react-native-splash-screen (3.2.0):
294
   - react-native-splash-screen (3.2.0):
295
     - React
295
     - React
296
-  - react-native-webrtc (1.87.1):
296
+  - react-native-webrtc (1.87.2):
297
     - React-Core
297
     - React-Core
298
   - react-native-webview (11.0.2):
298
   - react-native-webview (11.0.2):
299
     - React-Core
299
     - React-Core
562
   react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
562
   react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
563
   react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
563
   react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
564
   react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
564
   react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
565
-  react-native-webrtc: 40eca4cac200fda34fb843da07e3402211bbbd10
565
+  react-native-webrtc: e6fca8432542dd1c77afa6c59629f0176ed78ee6
566
   react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
566
   react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
567
   React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
567
   React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
568
   React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
568
   React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6

+ 1
- 0
lang/main-enGB.json View File

83
         "bandwidth": "Estimated bandwidth:",
83
         "bandwidth": "Estimated bandwidth:",
84
         "bitrate": "Bitrate:",
84
         "bitrate": "Bitrate:",
85
         "bridgeCount": "Server count: ",
85
         "bridgeCount": "Server count: ",
86
+        "codecs": "Codecs (A/V): ",
86
         "connectedTo": "Connected to:",
87
         "connectedTo": "Connected to:",
87
         "framerate": "Frame rate:",
88
         "framerate": "Frame rate:",
88
         "less": "Show less",
89
         "less": "Show less",

+ 1
- 0
lang/main.json View File

844
         "standardDefinition": "Standard definition"
844
         "standardDefinition": "Standard definition"
845
     },
845
     },
846
     "videothumbnail": {
846
     "videothumbnail": {
847
+        "connectionInfo": "Connection Info",
847
         "domute": "Mute",
848
         "domute": "Mute",
848
         "domuteOthers": "Mute everyone else",
849
         "domuteOthers": "Mute everyone else",
849
         "flip": "Flip",
850
         "flip": "Flip",

+ 5
- 5
package-lock.json View File

10776
       }
10776
       }
10777
     },
10777
     },
10778
     "lib-jitsi-meet": {
10778
     "lib-jitsi-meet": {
10779
-      "version": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
10780
-      "from": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
10779
+      "version": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
10780
+      "from": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
10781
       "requires": {
10781
       "requires": {
10782
         "@jitsi/js-utils": "1.0.2",
10782
         "@jitsi/js-utils": "1.0.2",
10783
         "@jitsi/sdp-interop": "1.0.3",
10783
         "@jitsi/sdp-interop": "1.0.3",
14284
       "integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
14284
       "integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
14285
     },
14285
     },
14286
     "react-native-webrtc": {
14286
     "react-native-webrtc": {
14287
-      "version": "1.87.1",
14288
-      "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.1.tgz",
14289
-      "integrity": "sha512-XIztid40ohLUoOIDqpavskyAPzopWIjNOoC/y3AtTymt+o+W/rIHZ9Qw8JZCaIjWh2AIrcO2wtb/f1aMWSz2Zw==",
14287
+      "version": "1.87.2",
14288
+      "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.2.tgz",
14289
+      "integrity": "sha512-bUMoMvfK17nT8S2w16bpL1uMMyDvDwOmhVjGrP6FDrCS7lAx/w2jVMUtZlNVS6zCJXN92wTkYJ6P3z+nr2hhNg==",
14290
       "requires": {
14290
       "requires": {
14291
         "base64-js": "^1.1.2",
14291
         "base64-js": "^1.1.2",
14292
         "cross-os": "^1.3.0",
14292
         "cross-os": "^1.3.0",

+ 2
- 2
package.json View File

56
     "jquery-i18next": "1.2.1",
56
     "jquery-i18next": "1.2.1",
57
     "js-md5": "0.6.1",
57
     "js-md5": "0.6.1",
58
     "jwt-decode": "2.2.0",
58
     "jwt-decode": "2.2.0",
59
-    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
59
+    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
60
     "libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
60
     "libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
61
     "lodash": "4.17.19",
61
     "lodash": "4.17.19",
62
     "moment": "2.19.4",
62
     "moment": "2.19.4",
84
     "react-native-svg-transformer": "0.14.3",
84
     "react-native-svg-transformer": "0.14.3",
85
     "react-native-url-polyfill": "1.2.0",
85
     "react-native-url-polyfill": "1.2.0",
86
     "react-native-watch-connectivity": "0.4.3",
86
     "react-native-watch-connectivity": "0.4.3",
87
-    "react-native-webrtc": "1.87.1",
87
+    "react-native-webrtc": "1.87.2",
88
     "react-native-webview": "11.0.2",
88
     "react-native-webview": "11.0.2",
89
     "react-native-youtube-iframe": "1.2.3",
89
     "react-native-youtube-iframe": "1.2.3",
90
     "react-redux": "7.1.0",
90
     "react-redux": "7.1.0",

+ 3
- 0
react/features/base/icons/svg/arrow_down_large.svg View File

1
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2
+<path d="M12 24l-8-9h6v-15h4v15h6z"/>
3
+</svg>

+ 3
- 0
react/features/base/icons/svg/arrow_up_large.svg View File

1
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2
+<path d="M12 0l8 9h-6v15h-4v-15h-6z"/>
3
+</svg>

+ 2
- 0
react/features/base/icons/svg/index.js View File

4
 export { default as IconAddPeople } from './link.svg';
4
 export { default as IconAddPeople } from './link.svg';
5
 export { default as IconArrowBack } from './arrow_back.svg';
5
 export { default as IconArrowBack } from './arrow_back.svg';
6
 export { default as IconArrowDown } from './arrow_down.svg';
6
 export { default as IconArrowDown } from './arrow_down.svg';
7
+export { default as IconArrowDownLarge } from './arrow_down_large.svg';
7
 export { default as IconArrowDownSmall } from './arrow-down-small.svg';
8
 export { default as IconArrowDownSmall } from './arrow-down-small.svg';
8
 export { default as IconArrowUp } from './arrow_up.svg';
9
 export { default as IconArrowUp } from './arrow_up.svg';
10
+export { default as IconArrowUpLarge } from './arrow_up_large.svg';
9
 export { default as IconArrowLeft } from './arrow-left.svg';
11
 export { default as IconArrowLeft } from './arrow-left.svg';
10
 export { default as IconAudioOnly } from './visibility.svg';
12
 export { default as IconAudioOnly } from './visibility.svg';
11
 export { default as IconAudioOnlyOff } from './visibility-off.svg';
13
 export { default as IconAudioOnlyOff } from './visibility-off.svg';

+ 4
- 1
react/features/base/react/components/native/BaseIndicator.js View File

7
 import { type StyleType } from '../../../styles';
7
 import { type StyleType } from '../../../styles';
8
 
8
 
9
 import styles from './indicatorstyles';
9
 import styles from './indicatorstyles';
10
+import { BASE_INDICATOR } from './styles';
10
 
11
 
11
 type Props = {
12
 type Props = {
12
 
13
 
40
         const { highlight, icon, iconStyle } = this.props;
41
         const { highlight, icon, iconStyle } = this.props;
41
 
42
 
42
         return (
43
         return (
43
-            <View style = { highlight ? styles.highlightedIndicator : null }>
44
+            <View
45
+                style = { [ BASE_INDICATOR,
46
+                    highlight ? styles.highlightedIndicator : null ] }>
44
                 <Icon
47
                 <Icon
45
                     src = { icon }
48
                     src = { icon }
46
                     style = { [
49
                     style = { [

+ 5
- 0
react/features/base/react/components/native/styles.js View File

208
     opacity: 0.8
208
     opacity: 0.8
209
 };
209
 };
210
 
210
 
211
+export const BASE_INDICATOR = {
212
+    alignItems: 'center',
213
+    justifyContent: 'center'
214
+};
215
+
211
 /**
216
 /**
212
  * The styles of the generic React {@code Component}s implemented by the feature
217
  * The styles of the generic React {@code Component}s implemented by the feature
213
  * base/react.
218
  * base/react.

+ 14
- 7
react/features/filmstrip/components/native/Thumbnail.js View File

21
 import { ConnectionIndicator } from '../../../connection-indicator';
21
 import { ConnectionIndicator } from '../../../connection-indicator';
22
 import { DisplayNameLabel } from '../../../display-name';
22
 import { DisplayNameLabel } from '../../../display-name';
23
 import { RemoteVideoMenu } from '../../../remote-video-menu';
23
 import { RemoteVideoMenu } from '../../../remote-video-menu';
24
+import ConnectionStatusComponent from '../../../remote-video-menu/components/native/ConnectionStatusComponent';
24
 import { toggleToolboxVisible } from '../../../toolbox/actions.native';
25
 import { toggleToolboxVisible } from '../../../toolbox/actions.native';
25
 
26
 
26
 import AudioMutedIndicator from './AudioMutedIndicator';
27
 import AudioMutedIndicator from './AudioMutedIndicator';
54
     /**
55
     /**
55
      * Handles long press on the thumbnail.
56
      * Handles long press on the thumbnail.
56
      */
57
      */
57
-    _onShowRemoteVideoMenu: ?Function,
58
+    _onThumbnailLongPress: ?Function,
58
 
59
 
59
     /**
60
     /**
60
      * Whether to show the dominant speaker indicator or not.
61
      * Whether to show the dominant speaker indicator or not.
120
         _audioMuted: audioMuted,
121
         _audioMuted: audioMuted,
121
         _largeVideo: largeVideo,
122
         _largeVideo: largeVideo,
122
         _onClick,
123
         _onClick,
123
-        _onShowRemoteVideoMenu,
124
+        _onThumbnailLongPress,
124
         _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
125
         _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
125
         _renderModeratorIndicator: renderModeratorIndicator,
126
         _renderModeratorIndicator: renderModeratorIndicator,
126
         _styles,
127
         _styles,
140
     return (
141
     return (
141
         <Container
142
         <Container
142
             onClick = { _onClick }
143
             onClick = { _onClick }
143
-            onLongPress = { participant.local ? undefined : _onShowRemoteVideoMenu }
144
+            onLongPress = { _onThumbnailLongPress }
144
             style = { [
145
             style = { [
145
                 styles.thumbnail,
146
                 styles.thumbnail,
146
                 participant.pinned && !tileView
147
                 participant.pinned && !tileView
230
          *
231
          *
231
          * @returns {void}
232
          * @returns {void}
232
          */
233
          */
233
-        _onShowRemoteVideoMenu() {
234
+        _onThumbnailLongPress() {
234
             const { participant } = ownProps;
235
             const { participant } = ownProps;
235
 
236
 
236
-            dispatch(openDialog(RemoteVideoMenu, {
237
-                participant
238
-            }));
237
+            if (participant.local) {
238
+                dispatch(openDialog(ConnectionStatusComponent, {
239
+                    participantID: participant.id
240
+                }));
241
+            } else {
242
+                dispatch(openDialog(RemoteVideoMenu, {
243
+                    participant
244
+                }));
245
+            }
239
         }
246
         }
240
     };
247
     };
241
 }
248
 }

+ 51
- 0
react/features/remote-video-menu/components/native/ConnectionStatusButton.js View File

1
+// @flow
2
+
3
+import { openDialog } from '../../../base/dialog';
4
+import { translate } from '../../../base/i18n';
5
+import { IconInfo } from '../../../base/icons';
6
+import { connect } from '../../../base/redux';
7
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
8
+
9
+import ConnectionStatusComponent from './ConnectionStatusComponent';
10
+
11
+export type Props = AbstractButtonProps & {
12
+
13
+    /**
14
+     * The redux {@code dispatch} function.
15
+     */
16
+    dispatch: Function,
17
+
18
+    /**
19
+     * The ID of the participant that this button is supposed to pin.
20
+     */
21
+    participantID: string,
22
+
23
+    /**
24
+     * The function to be used to translate i18n labels.
25
+     */
26
+    t: Function
27
+};
28
+
29
+/**
30
+ * A remote video menu button which shows the connection statistics.
31
+ */
32
+class ConnectionStatusButton extends AbstractButton<Props, *> {
33
+    icon = IconInfo;
34
+    label = 'videothumbnail.connectionInfo';
35
+
36
+    /**
37
+     * Handles clicking / pressing the button, and kicks the participant.
38
+     *
39
+     * @private
40
+     * @returns {void}
41
+     */
42
+    _handleClick() {
43
+        const { dispatch, participantID } = this.props;
44
+
45
+        dispatch(openDialog(ConnectionStatusComponent, {
46
+            participantID
47
+        }));
48
+    }
49
+}
50
+
51
+export default translate(connect()(ConnectionStatusButton));

+ 431
- 0
react/features/remote-video-menu/components/native/ConnectionStatusComponent.js View File

1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { Text, View } from 'react-native';
5
+
6
+import { Avatar } from '../../../base/avatar';
7
+import { ColorSchemeRegistry } from '../../../base/color-scheme';
8
+import { BottomSheet, isDialogOpen, hideDialog } from '../../../base/dialog';
9
+import { translate } from '../../../base/i18n';
10
+import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons';
11
+import { getParticipantDisplayName } from '../../../base/participants';
12
+import { BaseIndicator } from '../../../base/react';
13
+import { connect } from '../../../base/redux';
14
+import { StyleType, ColorPalette } from '../../../base/styles';
15
+import statsEmitter from '../../../connection-indicator/statsEmitter';
16
+
17
+import styles from './styles';
18
+
19
+/**
20
+ * Size of the rendered avatar in the menu.
21
+ */
22
+const AVATAR_SIZE = 25;
23
+
24
+const CONNECTION_QUALITY = [
25
+    'Low',
26
+    'Medium',
27
+    'Good'
28
+];
29
+
30
+export type Props = {
31
+
32
+    /**
33
+     * The Redux dispatch function.
34
+     */
35
+    dispatch: Function,
36
+
37
+    /**
38
+     * The ID of the participant that this button is supposed to pin.
39
+     */
40
+    participantID: string,
41
+
42
+    /**
43
+     * The color-schemed stylesheet of the BottomSheet.
44
+     */
45
+    _bottomSheetStyles: StyleType,
46
+
47
+    /**
48
+     * True if the menu is currently open, false otherwise.
49
+     */
50
+    _isOpen: boolean,
51
+
52
+    /**
53
+     * Display name of the participant retreived from Redux.
54
+     */
55
+    _participantDisplayName: string,
56
+
57
+    /**
58
+     * The function to be used to translate i18n labels.
59
+     */
60
+    t: Function
61
+}
62
+
63
+/**
64
+ * The type of the React {@code Component} state of {@link ConnectionStatusComponent}.
65
+ */
66
+type State = {
67
+    resolutionString: string,
68
+    downloadString: string,
69
+    uploadString: string,
70
+    packetLostDownloadString: string,
71
+    packetLostUploadString: string,
72
+    serverRegionString: string,
73
+    codecString: string,
74
+    connectionString: string
75
+};
76
+
77
+// eslint-disable-next-line prefer-const
78
+let ConnectionStatusComponent_;
79
+
80
+/**
81
+ * Class to implement a popup menu that show the connection statistics.
82
+ */
83
+class ConnectionStatusComponent extends Component<Props, State> {
84
+
85
+    /**
86
+     * Constructor of the component.
87
+     *
88
+     * @param {P} props - The read-only properties with which the new
89
+     * instance is to be initialized.
90
+     *
91
+     * @inheritdoc
92
+     */
93
+    constructor(props: Props) {
94
+        super(props);
95
+
96
+        this._onStatsUpdated = this._onStatsUpdated.bind(this);
97
+        this._onCancel = this._onCancel.bind(this);
98
+        this._renderMenuHeader = this._renderMenuHeader.bind(this);
99
+
100
+        this.state = {
101
+            resolutionString: 'N/A',
102
+            downloadString: 'N/A',
103
+            uploadString: 'N/A',
104
+            packetLostDownloadString: 'N/A',
105
+            packetLostUploadString: 'N/A',
106
+            serverRegionString: 'N/A',
107
+            codecString: 'N/A',
108
+            connectionString: 'N/A'
109
+        };
110
+    }
111
+
112
+    /**
113
+     * Implements React's {@link Component#render()}.
114
+     *
115
+     * @inheritdoc
116
+     * @returns {React$Node}
117
+     */
118
+    render(): React$Node {
119
+        const { t } = this.props;
120
+
121
+        return (
122
+            <BottomSheet
123
+                onCancel = { this._onCancel }
124
+                renderHeader = { this._renderMenuHeader }>
125
+                <View style = { styles.statsWrapper }>
126
+                    <View style = { styles.statsInfoCell }>
127
+                        <Text style = { styles.statsTitleText }>
128
+                            { `${t('connectionindicator.status')} ` }
129
+                        </Text>
130
+                        <Text style = { styles.statsInfoText }>
131
+                            { this.state.connectionString }
132
+                        </Text>
133
+                    </View>
134
+                    <View style = { styles.statsInfoCell }>
135
+                        <Text style = { styles.statsTitleText }>
136
+                            { `${t('connectionindicator.bitrate')}` }
137
+                        </Text>
138
+                        <BaseIndicator
139
+                            icon = { IconArrowDownLarge }
140
+                            iconStyle = {{
141
+                                color: ColorPalette.darkGrey
142
+                            }} />
143
+                        <Text style = { styles.statsInfoText }>
144
+                            { this.state.downloadString }
145
+                        </Text>
146
+                        <BaseIndicator
147
+                            icon = { IconArrowUpLarge }
148
+                            iconStyle = {{
149
+                                color: ColorPalette.darkGrey
150
+                            }} />
151
+                        <Text style = { styles.statsInfoText }>
152
+                            { `${this.state.uploadString} Kbps` }
153
+                        </Text>
154
+                    </View>
155
+                    <View style = { styles.statsInfoCell }>
156
+                        <Text style = { styles.statsTitleText }>
157
+                            { `${t('connectionindicator.packetloss')}` }
158
+                        </Text>
159
+                        <BaseIndicator
160
+                            icon = { IconArrowDownLarge }
161
+                            iconStyle = {{
162
+                                color: ColorPalette.darkGrey
163
+                            }} />
164
+                        <Text style = { styles.statsInfoText }>
165
+                            { this.state.packetLostDownloadString }
166
+                        </Text>
167
+                        <BaseIndicator
168
+                            icon = { IconArrowUpLarge }
169
+                            iconStyle = {{
170
+                                color: ColorPalette.darkGrey
171
+                            }} />
172
+                        <Text style = { styles.statsInfoText }>
173
+                            { this.state.packetLostUploadString }
174
+                        </Text>
175
+                    </View>
176
+                    <View style = { styles.statsInfoCell }>
177
+                        <Text style = { styles.statsTitleText }>
178
+                            { `${t('connectionindicator.resolution')} ` }
179
+                        </Text>
180
+                        <Text style = { styles.statsInfoText }>
181
+                            { this.state.resolutionString }
182
+                        </Text>
183
+                    </View>
184
+                    <View style = { styles.statsInfoCell }>
185
+                        <Text style = { styles.statsTitleText }>
186
+                            { `${t('connectionindicator.codecs')}` }
187
+                        </Text>
188
+                        <Text style = { styles.statsInfoText }>
189
+                            { this.state.codecString }
190
+                        </Text>
191
+                    </View>
192
+                </View>
193
+            </BottomSheet>
194
+        );
195
+    }
196
+
197
+    /**
198
+     * Starts listening for stat updates.
199
+     *
200
+     * @inheritdoc
201
+     * returns {void}
202
+     */
203
+    componentDidMount() {
204
+        statsEmitter.subscribeToClientStats(
205
+            this.props.participantID, this._onStatsUpdated);
206
+    }
207
+
208
+    /**
209
+     * Updates which user's stats are being listened to.
210
+     *
211
+     * @inheritdoc
212
+     * returns {void}
213
+     */
214
+    componentDidUpdate(prevProps: Props) {
215
+        if (prevProps.participantID !== this.props.participantID) {
216
+            statsEmitter.unsubscribeToClientStats(
217
+                prevProps.participantID, this._onStatsUpdated);
218
+            statsEmitter.subscribeToClientStats(
219
+                this.props.participantID, this._onStatsUpdated);
220
+        }
221
+    }
222
+
223
+    _onStatsUpdated: Object => void;
224
+
225
+    /**
226
+     * Callback invoked when new connection stats associated with the passed in
227
+     * user ID are available. Will update the component's display of current
228
+     * statistics.
229
+     *
230
+     * @param {Object} stats - Connection stats from the library.
231
+     * @private
232
+     * @returns {void}
233
+     */
234
+    _onStatsUpdated(stats = {}) {
235
+        const newState = this._buildState(stats);
236
+
237
+        this.setState(newState);
238
+    }
239
+
240
+    /**
241
+     * Extracts statistics and builds the state object.
242
+     *
243
+     * @param {Object} stats - Connection stats from the library.
244
+     * @private
245
+     * @returns {State}
246
+     */
247
+    _buildState(stats) {
248
+        const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {};
249
+
250
+        const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {};
251
+
252
+        return {
253
+            resolutionString: this._extractResolutionString(stats) ?? this.state.resolutionString,
254
+            downloadString: downloadBitrate ?? this.state.downloadString,
255
+            uploadString: uploadBitrate ?? this.state.uploadString,
256
+            packetLostDownloadString: downloadPacketLost === undefined
257
+                ? this.state.packetLostDownloadString : `${downloadPacketLost}%`,
258
+            packetLostUploadString: uploadPacketLost === undefined
259
+                ? this.state.packetLostUploadString : `${uploadPacketLost}%`,
260
+            serverRegionString: this._extractServer(stats) ?? this.state.serverRegionString,
261
+            codecString: this._extractCodecs(stats) ?? this.state.codecString,
262
+            connectionString: this._extractConnection(stats) ?? this.state.connectionString
263
+        };
264
+    }
265
+
266
+    /**
267
+     * Extracts the resolution and framerate.
268
+     *
269
+     * @param {Object} stats - Connection stats from the library.
270
+     * @private
271
+     * @returns {string}
272
+     */
273
+    _extractResolutionString(stats) {
274
+        const { framerate, resolution } = stats;
275
+
276
+        const resolutionString = Object.keys(resolution || {})
277
+        .map(ssrc => {
278
+            const { width, height } = resolution[ssrc];
279
+
280
+            return `${width}x${height}`;
281
+        })
282
+        .join(', ') || null;
283
+
284
+        const frameRateString = Object.keys(framerate || {})
285
+            .map(ssrc => framerate[ssrc])
286
+            .join(', ') || null;
287
+
288
+        return resolutionString && frameRateString ? `${resolutionString}@${frameRateString}fps` : undefined;
289
+    }
290
+
291
+    /**
292
+     * Extracts the download and upload bitrates.
293
+     *
294
+     * @param {Object} stats - Connection stats from the library.
295
+     * @private
296
+     * @returns {{ download, upload }}
297
+     */
298
+    _extractBitrate(stats) {
299
+        return stats.bitrate;
300
+    }
301
+
302
+    /**
303
+     * Extracts the download and upload packet lost.
304
+     *
305
+     * @param {Object} stats - Connection stats from the library.
306
+     * @private
307
+     * @returns {{ download, upload }}
308
+     */
309
+    _extractPacketLost(stats) {
310
+        return stats.packetLoss;
311
+    }
312
+
313
+    /**
314
+     * Extracts the server name.
315
+     *
316
+     * @param {Object} stats - Connection stats from the library.
317
+     * @private
318
+     * @returns {string}
319
+     */
320
+    _extractServer(stats) {
321
+        return stats.serverRegion;
322
+    }
323
+
324
+    /**
325
+     * Extracts the audio and video codecs names.
326
+     *
327
+     * @param {Object} stats - Connection stats from the library.
328
+     * @private
329
+     * @returns {string}
330
+     */
331
+    _extractCodecs(stats) {
332
+        const { codec } = stats;
333
+
334
+        let codecString;
335
+
336
+        // Only report one codec, in case there are multiple for a user.
337
+        Object.keys(codec || {})
338
+            .forEach(ssrc => {
339
+                const { audio, video } = codec[ssrc];
340
+
341
+                codecString = `${audio}, ${video}`;
342
+            });
343
+
344
+        return codecString;
345
+    }
346
+
347
+    /**
348
+     * Extracts the connection percentage and sets connection quality.
349
+     *
350
+     * @param {Object} stats - Connection stats from the library.
351
+     * @private
352
+     * @returns {string}
353
+     */
354
+    _extractConnection(stats) {
355
+        const { connectionQuality } = stats;
356
+
357
+        if (connectionQuality) {
358
+            const signalLevel = Math.floor(connectionQuality / 33.4);
359
+
360
+            return CONNECTION_QUALITY[signalLevel];
361
+        }
362
+    }
363
+
364
+    _onCancel: () => boolean;
365
+
366
+    /**
367
+     * Callback to hide the {@code ConnectionStatusComponent}.
368
+     *
369
+     * @private
370
+     * @returns {boolean}
371
+     */
372
+    _onCancel() {
373
+        statsEmitter.unsubscribeToClientStats(
374
+            this.props.participantID, this._onStatsUpdated);
375
+
376
+        if (this.props._isOpen) {
377
+            this.props.dispatch(hideDialog(ConnectionStatusComponent_));
378
+
379
+            return true;
380
+        }
381
+
382
+        return false;
383
+    }
384
+
385
+    _renderMenuHeader: () => React$Element<any>;
386
+
387
+    /**
388
+     * Function to render the menu's header.
389
+     *
390
+     * @returns {React$Element}
391
+     */
392
+    _renderMenuHeader() {
393
+        const { _bottomSheetStyles, participantID } = this.props;
394
+
395
+        return (
396
+            <View
397
+                style = { [
398
+                    _bottomSheetStyles.sheet,
399
+                    styles.participantNameContainer ] }>
400
+                <Avatar
401
+                    participantId = { participantID }
402
+                    size = { AVATAR_SIZE } />
403
+                <Text style = { styles.participantNameLabel }>
404
+                    { this.props._participantDisplayName }
405
+                </Text>
406
+            </View>
407
+        );
408
+    }
409
+}
410
+
411
+/**
412
+ * Function that maps parts of Redux state tree into component props.
413
+ *
414
+ * @param {Object} state - Redux state.
415
+ * @param {Object} ownProps - Properties of component.
416
+ * @private
417
+ * @returns {Props}
418
+ */
419
+function _mapStateToProps(state, ownProps) {
420
+    const { participantID } = ownProps;
421
+
422
+    return {
423
+        _bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
424
+        _isOpen: isDialogOpen(state, ConnectionStatusComponent_),
425
+        _participantDisplayName: getParticipantDisplayName(state, participantID)
426
+    };
427
+}
428
+
429
+ConnectionStatusComponent_ = translate(connect(_mapStateToProps)(ConnectionStatusComponent));
430
+
431
+export default ConnectionStatusComponent_;

+ 2
- 0
react/features/remote-video-menu/components/native/RemoteVideoMenu.js View File

13
 import { PrivateMessageButton } from '../../../chat';
13
 import { PrivateMessageButton } from '../../../chat';
14
 import { hideRemoteVideoMenu } from '../../actions';
14
 import { hideRemoteVideoMenu } from '../../actions';
15
 
15
 
16
+import ConnectionStatusButton from './ConnectionStatusButton';
16
 import GrantModeratorButton from './GrantModeratorButton';
17
 import GrantModeratorButton from './GrantModeratorButton';
17
 import KickButton from './KickButton';
18
 import KickButton from './KickButton';
18
 import MuteButton from './MuteButton';
19
 import MuteButton from './MuteButton';
106
                 <PinButton { ...buttonProps } />
107
                 <PinButton { ...buttonProps } />
107
                 <PrivateMessageButton { ...buttonProps } />
108
                 <PrivateMessageButton { ...buttonProps } />
108
                 <MuteEveryoneElseButton { ...buttonProps } />
109
                 <MuteEveryoneElseButton { ...buttonProps } />
110
+                <ConnectionStatusButton { ...buttonProps } />
109
             </BottomSheet>
111
             </BottomSheet>
110
         );
112
         );
111
     }
113
     }

+ 23
- 0
react/features/remote-video-menu/components/native/styles.js View File

25
         fontSize: MD_FONT_SIZE,
25
         fontSize: MD_FONT_SIZE,
26
         marginLeft: MD_ITEM_MARGIN_PADDING,
26
         marginLeft: MD_ITEM_MARGIN_PADDING,
27
         opacity: 0.90
27
         opacity: 0.90
28
+    },
29
+
30
+    statsTitleText: {
31
+        fontSize: 16,
32
+        fontWeight: 'bold',
33
+        marginRight: 3
34
+    },
35
+
36
+    statsInfoText: {
37
+        fontSize: 16,
38
+        marginRight: 2,
39
+        marginLeft: 2
40
+    },
41
+
42
+    statsInfoCell: {
43
+        alignItems: 'center',
44
+        flexDirection: 'row',
45
+        height: 30,
46
+        justifyContent: 'flex-start'
47
+    },
48
+
49
+    statsWrapper: {
50
+        marginVertical: 10
28
     }
51
     }
29
 });
52
 });

Loading…
Cancel
Save