浏览代码

feat: Adds more debug information in the GSM bars popover (#7627)

j8
George Politis 5 年前
父节点
当前提交
b5310573fc
没有帐户链接到提交者的电子邮件

+ 3
- 13
conference.js 查看文件

108
     getBackendSafePath,
108
     getBackendSafePath,
109
     getJitsiMeetGlobalNS
109
     getJitsiMeetGlobalNS
110
 } from './react/features/base/util';
110
 } from './react/features/base/util';
111
+import { downloadJSON } from './react/features/base/util/downloadJSON';
111
 import { showDesktopPicker } from './react/features/desktop-picker';
112
 import { showDesktopPicker } from './react/features/desktop-picker';
112
 import { appendSuffix } from './react/features/display-name';
113
 import { appendSuffix } from './react/features/display-name';
113
 import {
114
 import {
1221
         // this can be called from console and will not have reference to this
1222
         // this can be called from console and will not have reference to this
1222
         // that's why we reference the global var
1223
         // that's why we reference the global var
1223
         const logs = APP.connection.getLogs();
1224
         const logs = APP.connection.getLogs();
1224
-        const data = encodeURIComponent(JSON.stringify(logs, null, '  '));
1225
-
1226
-        const elem = document.createElement('a');
1227
-
1228
-        elem.download = filename;
1229
-        elem.href = `data:application/json;charset=utf-8,\n${data}`;
1230
-        elem.dataset.downloadurl
1231
-            = [ 'text/json', elem.download, elem.href ].join(':');
1232
-        elem.dispatchEvent(new MouseEvent('click', {
1233
-            view: window,
1234
-            bubbles: true,
1235
-            cancelable: false
1236
-        }));
1225
+
1226
+        downloadJSON(logs, filename);
1237
     },
1227
     },
1238
 
1228
 
1239
     /**
1229
     /**

+ 1
- 3
css/_connection-info.scss 查看文件

45
         @extend .connection-info__icon;
45
         @extend .connection-info__icon;
46
     }
46
     }
47
 
47
 
48
-    .showmore {
49
-        display: block;
48
+    .connection-actions {
50
         margin: 10px auto;
49
         margin: 10px auto;
51
         text-align: center;
50
         text-align: center;
52
-        width: 90px;
53
     }
51
     }
54
 }
52
 }

+ 5
- 1
lang/main.json 查看文件

99
     },
99
     },
100
     "connectionindicator": {
100
     "connectionindicator": {
101
         "address": "Address:",
101
         "address": "Address:",
102
+        "audio_ssrc": "Audio SSRC:",
102
         "bandwidth": "Estimated bandwidth:",
103
         "bandwidth": "Estimated bandwidth:",
103
         "bitrate": "Bitrate:",
104
         "bitrate": "Bitrate:",
104
         "bridgeCount": "Server count: ",
105
         "bridgeCount": "Server count: ",
126
         "remoteport": "Remote port:",
127
         "remoteport": "Remote port:",
127
         "remoteport_plural": "Remote ports:",
128
         "remoteport_plural": "Remote ports:",
128
         "resolution": "Resolution:",
129
         "resolution": "Resolution:",
130
+        "savelogs": "Save logs",
131
+        "participant_id": "Participant id:",
129
         "status": "Connection:",
132
         "status": "Connection:",
130
         "transport": "Transport:",
133
         "transport": "Transport:",
131
-        "transport_plural": "Transports:"
134
+        "transport_plural": "Transports:",
135
+        "video_ssrc": "Video SSRC:"
132
     },
136
     },
133
     "dateUtils": {
137
     "dateUtils": {
134
         "earlier": "Earlier",
138
         "earlier": "Earlier",

+ 23
- 0
react/features/base/util/downloadJSON.js 查看文件

1
+// @flow
2
+
3
+/**
4
+ * Downloads a JSON object.
5
+ *
6
+ * @param {Object} json - The JSON object to download.
7
+ * @param {string} filename - The filename to give to the downloaded file.
8
+ * @returns {void}
9
+ */
10
+export function downloadJSON(json: Object, filename: string) {
11
+    const data = encodeURIComponent(JSON.stringify(json, null, '  '));
12
+
13
+    const elem = document.createElement('a');
14
+
15
+    elem.download = filename;
16
+    elem.href = `data:application/json;charset=utf-8,\n${data}`;
17
+    elem.dataset.downloadurl = [ 'text/json', elem.download, elem.href ].join(':');
18
+    elem.dispatchEvent(new MouseEvent('click', {
19
+        view: window,
20
+        bubbles: true,
21
+        cancelable: false
22
+    }));
23
+}

+ 18
- 0
react/features/connection-indicator/actions.js 查看文件

1
+import getRoomName from '../base/config/getRoomName';
2
+import { downloadJSON } from '../base/util/downloadJSON';
3
+
4
+
5
+/**
6
+ * Create an action for saving the conference logs.
7
+ *
8
+ * @returns {Function}
9
+ */
10
+export function saveLogs() {
11
+    return (dispatch, getState) => {
12
+
13
+        const logs = getState()['features/base/connection'].connection.getLogs();
14
+        const roomName = getRoomName() || '';
15
+
16
+        downloadJSON(logs, `meetlog-${roomName}.json`);
17
+    };
18
+}

+ 77
- 3
react/features/connection-indicator/components/web/ConnectionIndicator.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
+import type { Dispatch } from 'redux';
4
 
5
 
5
 import { translate } from '../../../base/i18n';
6
 import { translate } from '../../../base/i18n';
6
 import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
7
 import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
7
 import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
8
 import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
9
+import { MEDIA_TYPE } from '../../../base/media';
8
 import { Popover } from '../../../base/popover';
10
 import { Popover } from '../../../base/popover';
11
+import { connect } from '../../../base/redux';
12
+import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
9
 import { ConnectionStatsTable } from '../../../connection-stats';
13
 import { ConnectionStatsTable } from '../../../connection-stats';
14
+import { saveLogs } from '../../actions';
10
 import AbstractConnectionIndicator, {
15
 import AbstractConnectionIndicator, {
11
     INDICATOR_DISPLAY_THRESHOLD,
16
     INDICATOR_DISPLAY_THRESHOLD,
12
     type Props as AbstractProps,
17
     type Props as AbstractProps,
62
      */
67
      */
63
     alwaysVisible: boolean,
68
     alwaysVisible: boolean,
64
 
69
 
70
+    /**
71
+     * The audio SSRC of this client.
72
+     */
73
+    audioSsrc: number,
74
+
65
     /**
75
     /**
66
      * The current condition of the user's connection, matching one of the
76
      * The current condition of the user's connection, matching one of the
67
      * enumerated values in the library.
77
      * enumerated values in the library.
68
      */
78
      */
69
     connectionStatus: string,
79
     connectionStatus: string,
70
 
80
 
81
+    /**
82
+     * The Redux dispatch function.
83
+     */
84
+    dispatch: Dispatch<any>,
85
+
71
     /**
86
     /**
72
      * Whether or not clicking the indicator should display a popover for more
87
      * Whether or not clicking the indicator should display a popover for more
73
      * details.
88
      * details.
93
     /**
108
     /**
94
      * Invoked to obtain translated strings.
109
      * Invoked to obtain translated strings.
95
      */
110
      */
96
-    t: Function
111
+    t: Function,
112
+
113
+    /**
114
+     * The video SSRC of this client.
115
+     */
116
+    videoSsrc: number,
117
+
118
+    /**
119
+     * Invoked to save the conference logs.
120
+     */
121
+    _onSaveLogs: Function
97
 };
122
 };
98
 
123
 
99
 /**
124
 /**
353
 
378
 
354
         return (
379
         return (
355
             <ConnectionStatsTable
380
             <ConnectionStatsTable
381
+                audioSsrc = { this.props.audioSsrc }
356
                 bandwidth = { bandwidth }
382
                 bandwidth = { bandwidth }
357
                 bitrate = { bitrate }
383
                 bitrate = { bitrate }
358
                 bridgeCount = { bridgeCount }
384
                 bridgeCount = { bridgeCount }
362
                 framerate = { framerate }
388
                 framerate = { framerate }
363
                 isLocalVideo = { this.props.isLocalVideo }
389
                 isLocalVideo = { this.props.isLocalVideo }
364
                 maxEnabledResolution = { maxEnabledResolution }
390
                 maxEnabledResolution = { maxEnabledResolution }
391
+                onSaveLogs = { this.props._onSaveLogs }
365
                 onShowMore = { this._onToggleShowMore }
392
                 onShowMore = { this._onToggleShowMore }
366
                 packetLoss = { packetLoss }
393
                 packetLoss = { packetLoss }
394
+                participantId = { this.props.participantId }
367
                 region = { region }
395
                 region = { region }
368
                 resolution = { resolution }
396
                 resolution = { resolution }
369
                 serverRegion = { serverRegion }
397
                 serverRegion = { serverRegion }
370
                 shouldShowMore = { this.state.showMoreStats }
398
                 shouldShowMore = { this.state.showMoreStats }
371
-                transport = { transport } />
399
+                transport = { transport }
400
+                videoSsrc = { this.props.videoSsrc } />
372
         );
401
         );
373
     }
402
     }
374
 }
403
 }
375
 
404
 
376
-export default translate(ConnectionIndicator);
405
+
406
+/**
407
+ * Maps redux actions to the props of the component.
408
+ *
409
+ * @param {Function} dispatch - The redux action {@code dispatch} function.
410
+ * @returns {{
411
+ *     _onSaveLogs: Function,
412
+ * }}
413
+ * @private
414
+ */
415
+export function _mapDispatchToProps(dispatch: Dispatch<any>) {
416
+    return {
417
+        /**
418
+         * Saves the conference logs.
419
+         *
420
+         * @returns {Function}
421
+         */
422
+        _onSaveLogs() {
423
+            dispatch(saveLogs());
424
+        }
425
+    };
426
+}
427
+
428
+
429
+/**
430
+ * Maps part of the Redux state to the props of this component.
431
+ *
432
+ * @param {Object} state - The Redux state.
433
+ * @param {Props} ownProps - The own props of the component.
434
+ * @returns {Props}
435
+ */
436
+export function _mapStateToProps(state: Object, ownProps: Props) {
437
+
438
+    const firstVideoTrack = getTrackByMediaTypeAndParticipant(
439
+        state['features/base/tracks'], MEDIA_TYPE.VIDEO, ownProps.participantId);
440
+    const firstAudioTrack = getTrackByMediaTypeAndParticipant(
441
+        state['features/base/tracks'], MEDIA_TYPE.AUDIO, ownProps.participantId);
442
+
443
+    return {
444
+        audioSsrc: firstAudioTrack
445
+            ? state['features/base/conference'].conference.getSsrcByTrack(firstAudioTrack.jitsiTrack) : undefined,
446
+        videoSsrc: firstVideoTrack
447
+            ? state['features/base/conference'].conference.getSsrcByTrack(firstVideoTrack.jitsiTrack) : undefined
448
+    };
449
+}
450
+export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ConnectionIndicator));

+ 113
- 6
react/features/connection-stats/components/ConnectionStatsTable.js 查看文件

10
  */
10
  */
11
 type Props = {
11
 type Props = {
12
 
12
 
13
+    /**
14
+     * The audio SSRC of this client.
15
+     */
16
+    audioSsrc: number,
17
+
13
     /**
18
     /**
14
      * Statistics related to bandwidth.
19
      * Statistics related to bandwidth.
15
      * {{
20
      * {{
49
      */
54
      */
50
     e2eRtt: number,
55
     e2eRtt: number,
51
 
56
 
57
+    /**
58
+     * The endpoint id of this client.
59
+     */
60
+    participantId: string,
61
+
52
     /**
62
     /**
53
      * Statistics related to frame rates for each ssrc.
63
      * Statistics related to frame rates for each ssrc.
54
      * {{
64
      * {{
68
      */
78
      */
69
     maxEnabledResolution: number,
79
     maxEnabledResolution: number,
70
 
80
 
81
+    /**
82
+     * Callback to invoke when the user clicks on the download logs link.
83
+     */
84
+    onSaveLogs: Function,
85
+
71
     /**
86
     /**
72
      * Callback to invoke when the show additional stats link is clicked.
87
      * Callback to invoke when the show additional stats link is clicked.
73
      */
88
      */
114
      */
129
      */
115
     t: Function,
130
     t: Function,
116
 
131
 
132
+    /**
133
+     * The video SSRC of this client.
134
+     */
135
+    videoSsrc: number,
136
+
117
     /**
137
     /**
118
      * Statistics related to transports.
138
      * Statistics related to transports.
119
      */
139
      */
138
         return (
158
         return (
139
             <div className = 'connection-info'>
159
             <div className = 'connection-info'>
140
                 { this._renderStatistics() }
160
                 { this._renderStatistics() }
141
-                { isLocalVideo ? this._renderShowMoreLink() : null }
142
-                { isLocalVideo && this.props.shouldShowMore
143
-                    ? this._renderAdditionalStats() : null }
161
+                <div className = 'connection-actions'>
162
+                    { isLocalVideo ? this._renderSaveLogs() : null}
163
+                    { this._renderShowMoreLink() }
164
+                </div>
165
+                { this.props.shouldShowMore ? this._renderAdditionalStats() : null }
144
             </div>
166
             </div>
145
         );
167
         );
146
     }
168
     }
153
      * @returns {ReactElement}
175
      * @returns {ReactElement}
154
      */
176
      */
155
     _renderAdditionalStats() {
177
     _renderAdditionalStats() {
178
+        const { isLocalVideo } = this.props;
179
+
156
         return (
180
         return (
157
             <table className = 'connection-info__container'>
181
             <table className = 'connection-info__container'>
158
                 <tbody>
182
                 <tbody>
159
-                    { this._renderBandwidth() }
160
-                    { this._renderTransport() }
161
-                    { this._renderRegion() }
183
+                    { isLocalVideo ? this._renderBandwidth() : null }
184
+                    { isLocalVideo ? this._renderTransport() : null }
185
+                    { isLocalVideo ? this._renderRegion() : null }
186
+                    { this._renderAudioSsrc() }
187
+                    { this._renderVideoSsrc() }
188
+                    { this._renderParticipantId() }
162
                 </tbody>
189
                 </tbody>
163
             </table>
190
             </table>
164
         );
191
         );
224
         );
251
         );
225
     }
252
     }
226
 
253
 
254
+    /**
255
+     * Creates a table row as a ReactElement for displaying the audio ssrc.
256
+     * This will typically be something like "Audio SSRC: 12345".
257
+     *
258
+     * @returns {JSX.Element}
259
+     * @private
260
+     */
261
+    _renderAudioSsrc() {
262
+        const { audioSsrc, t } = this.props;
263
+
264
+        return (
265
+            <tr>
266
+                <td>
267
+                    <span>{ t('connectionindicator.audio_ssrc') }</span>
268
+                </td>
269
+                <td>{ audioSsrc || 'N/A' }</td>
270
+            </tr>
271
+        );
272
+    }
273
+
274
+    /**
275
+     * Creates a table row as a ReactElement for displaying the video ssrc.
276
+     * This will typically be something like "Video SSRC: 12345".
277
+     *
278
+     * @returns {JSX.Element}
279
+     * @private
280
+     */
281
+    _renderVideoSsrc() {
282
+        const { videoSsrc, t } = this.props;
283
+
284
+        return (
285
+            <tr>
286
+                <td>
287
+                    <span>{ t('connectionindicator.video_ssrc') }</span>
288
+                </td>
289
+                <td>{ videoSsrc || 'N/A' }</td>
290
+            </tr>
291
+        );
292
+    }
293
+
294
+    /**
295
+     * Creates a table row as a ReactElement for displaying the endpoint id.
296
+     * This will typically be something like "Endpoint id: 1e8fbg".
297
+     *
298
+     * @returns {JSX.Element}
299
+     * @private
300
+     */
301
+    _renderParticipantId() {
302
+        const { participantId, t } = this.props;
303
+
304
+        return (
305
+            <tr>
306
+                <td>
307
+                    <span>{ t('connectionindicator.participant_id') }</span>
308
+                </td>
309
+                <td>{ participantId || 'N/A' }</td>
310
+            </tr>
311
+        );
312
+    }
313
+
227
     /**
314
     /**
228
      * Creates a a table row as a ReactElement for displaying codec, if present.
315
      * Creates a a table row as a ReactElement for displaying codec, if present.
229
      * This will typically be something like "Codecs (A/V): Opus, vp8".
316
      * This will typically be something like "Codecs (A/V): Opus, vp8".
455
         );
542
         );
456
     }
543
     }
457
 
544
 
545
+    /**
546
+     * Creates a ReactElement for display a link to save the logs.
547
+     *
548
+     * @private
549
+     * @returns {ReactElement}
550
+     */
551
+    _renderSaveLogs() {
552
+        return (
553
+            <span>
554
+                <a
555
+                    className = 'savelogs link'
556
+                    onClick = { this.props.onSaveLogs } >
557
+                    { this.props.t('connectionindicator.savelogs') }
558
+                </a>
559
+                <span> | </span>
560
+            </span>
561
+        );
562
+    }
563
+
564
+
458
     /**
565
     /**
459
      * Creates a ReactElement for display a link to toggle showing additional
566
      * Creates a ReactElement for display a link to toggle showing additional
460
      * statistics.
567
      * statistics.

正在加载...
取消
保存