Explorar el Código

feat(prejoin): Add precall connection quality indicator

* Adds a dropdown indicator which displays the status of the internet connection.
* It uses the same data as `https://network.callstats.io`.
* The algorithm for the strings displayed to the user is also the one used on `network.callstas.io`.
master
Vlad Piersec hace 4 años
padre
commit
453c07cb17

+ 4
- 1
conference.js Ver fichero

121
 import {
121
 import {
122
     initPrejoin,
122
     initPrejoin,
123
     isPrejoinPageEnabled,
123
     isPrejoinPageEnabled,
124
-    isPrejoinPageVisible
124
+    isPrejoinPageVisible,
125
+    makePrecallTest
125
 } from './react/features/prejoin';
126
 } from './react/features/prejoin';
126
 import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
127
 import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
127
 import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
128
 import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
767
                 return c;
768
                 return c;
768
             });
769
             });
769
 
770
 
771
+            APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
772
+
770
             const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
773
             const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
771
             const tracks = await tryCreateLocalTracks;
774
             const tracks = await tryCreateLocalTracks;
772
 
775
 

+ 60
- 0
css/_connection-status.scss Ver fichero

1
+.con-status {
2
+    position: absolute;
3
+    top: 40px;
4
+    width: 100%;
5
+    z-index: $toolbarZ + 3;
6
+
7
+    &-container {
8
+        background: rgba(28, 32, 37, .5);
9
+        border-radius: 3px;
10
+        color: #fff;
11
+        font-size: 13px;
12
+        line-height: 20px;
13
+        margin: 0 auto;
14
+        width: 304px;
15
+    }
16
+
17
+    &-header {
18
+        align-items: center;
19
+        display: flex;
20
+        justify-content: space-between;
21
+        padding: 8px;
22
+    }
23
+
24
+    &-circle {
25
+        border-radius: 50%;
26
+        display: inline-block;
27
+        padding: 4px;
28
+    }
29
+
30
+    &--good {
31
+        background: #31B76A;
32
+    }
33
+
34
+    &--poor {
35
+        background: #E12D2D;
36
+    }
37
+
38
+    &--non-optimal {
39
+        background: #E39623;
40
+    }
41
+
42
+    &-arrow {
43
+        &--up {
44
+            transform: rotate(180deg);
45
+        }
46
+
47
+        &>svg {
48
+            cursor: pointer;
49
+        }
50
+    }
51
+
52
+    &-text {
53
+        text-align: center;
54
+    }
55
+
56
+    &-details {
57
+        border-top: 1px solid #5E6D7A;
58
+        padding: 16px;
59
+    }
60
+}

+ 1
- 0
css/main.scss Ver fichero

102
 @import 'premeeting-screens';
102
 @import 'premeeting-screens';
103
 @import 'e2ee';
103
 @import 'e2ee';
104
 @import 'responsive';
104
 @import 'responsive';
105
+@import 'connection-status';
105
 
106
 
106
 /* Modules END */
107
 /* Modules END */

+ 19
- 0
lang/main.json Ver fichero

511
         "callMeAtNumber": "Call me at this number:",
511
         "callMeAtNumber": "Call me at this number:",
512
         "configuringDevices": "Configuring devices...",
512
         "configuringDevices": "Configuring devices...",
513
         "connectedWithAudioQ": "You’re connected with audio?",
513
         "connectedWithAudioQ": "You’re connected with audio?",
514
+        "connection": {
515
+           "good": "Your internet connection looks good!",
516
+           "nonOptimal": "Your internet connection is not optimal",
517
+           "poor": "You have a poor internet connection"
518
+        },
519
+        "connectionDetails": {
520
+          "audioClipping": "We expect your audio to be clipped.",
521
+          "audioHighQuality": "We expect your audio to have excellent quality.",
522
+          "audioLowNoVideo": "We expect your audio quality to be low and no video.",
523
+          "goodQuality": "Awesome! Your media quality is going to be great.",
524
+          "noMediaConnectivity": "We could not find a way to establish media connectivity for this test. This is typically caused by a firewall or NAT.",
525
+          "noVideo": "We expect that your video will be terrible.",
526
+          "undetectable": "If you still can not make calls in browser, we recommend that you make sure your speakers, microphone and camera are properly set up, that you have granted your browser rights to use your microphone and camera, and that your browser version is up-to-date. If you still have trouble calling, you should contact the web application developer.",
527
+          "veryPoorConnection": "We expect your call quality to be really terrible.",
528
+          "videoFreezing": "We expect your video to freeze, turn black, and be pixelated.",
529
+          "videoHighQuality": "We expect your video to have good quality.",
530
+          "videoLowQuality": "We expect your video to have low quality in terms of frame rate and resolution.",
531
+          "videoTearing": "We expect your video to be pixelated or have visual artefacts."
532
+        },
514
         "copyAndShare": "Copy & share meeting link",
533
         "copyAndShare": "Copy & share meeting link",
515
         "dialInMeeting": "Dial into the meeting",
534
         "dialInMeeting": "Dial into the meeting",
516
         "dialInPin": "Dial into the meeting and enter PIN code:",
535
         "dialInPin": "Dial into the meeting and enter PIN code:",

+ 3
- 0
react/features/base/icons/svg/index.js Ver fichero

98
 export { default as IconVolumeEmpty } from './volume-empty.svg';
98
 export { default as IconVolumeEmpty } from './volume-empty.svg';
99
 export { default as IconVolumeOff } from './volume-off.svg';
99
 export { default as IconVolumeOff } from './volume-off.svg';
100
 export { default as IconWarning } from './warning.svg';
100
 export { default as IconWarning } from './warning.svg';
101
+export { default as IconWifi1Bar } from './wifi-1.svg';
102
+export { default as IconWifi2Bars } from './wifi-2.svg';
103
+export { default as IconWifi3Bars } from './wifi-3.svg';
101
 export { default as IconYahoo } from './yahoo.svg';
104
 export { default as IconYahoo } from './yahoo.svg';

+ 5
- 0
react/features/base/icons/svg/wifi-1.svg Ver fichero

1
+<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path opacity="0.4" d="M13.0913 6.59847C12.4227 5.88894 11.629 5.32611 10.7554 4.94212C9.88182 4.55812 8.94553 4.36048 7.99997 4.36048C7.05442 4.36048 6.11813 4.55812 5.24456 4.94212C4.371 5.32611 3.57726 5.88894 2.90869 6.59847L4.36305 8.14176C4.84061 7.63486 5.4076 7.23276 6.03163 6.95842C6.65566 6.68408 7.32451 6.54288 7.99997 6.54288C8.67544 6.54288 9.34429 6.68408 9.96832 6.95842C10.5923 7.23276 11.1593 7.63486 11.6369 8.14176L13.0913 6.59847Z" fill="white"/>
3
+<path opacity="0.4" d="M16 3.51081C13.8766 1.26261 10.9996 0 8 0C5.00044 0 2.12337 1.26261 0 3.51081L1.45436 5.0541C3.19156 3.21432 5.54565 2.18105 8 2.18105C10.4543 2.18105 12.8084 3.21432 14.5456 5.0541L16 3.51081Z" fill="white"/>
4
+<path d="M5.94287 9.81713L7.99996 12L10.057 9.81713C9.78693 9.53041 9.46623 9.30297 9.11328 9.1478C8.76032 8.99263 8.38201 8.91276 7.99996 8.91276C7.6179 8.91276 7.23959 8.99263 6.88663 9.1478C6.53368 9.30297 6.21298 9.53041 5.94287 9.81713Z" />
5
+</svg>

+ 5
- 0
react/features/base/icons/svg/wifi-2.svg Ver fichero

1
+<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M13.0913 6.59847C12.4227 5.88894 11.629 5.32611 10.7554 4.94211C9.88182 4.55811 8.94553 4.36047 7.99997 4.36047C7.05442 4.36047 6.11813 4.55811 5.24456 4.94211C4.371 5.32611 3.57726 5.88894 2.90869 6.59847L4.36305 8.14176C4.84061 7.63486 5.4076 7.23276 6.03163 6.95842C6.65566 6.68408 7.32451 6.54288 7.99997 6.54288C8.67544 6.54288 9.34429 6.68408 9.96832 6.95842C10.5923 7.23276 11.1593 7.63486 11.6369 8.14176L13.0913 6.59847Z" fill="white"/>
3
+<path opacity="0.4" d="M16 3.51081C13.8766 1.26261 10.9996 0 8 0C5.00044 0 2.12337 1.26261 0 3.51081L1.45436 5.0541C3.19156 3.21432 5.54565 2.18105 8 2.18105C10.4543 2.18105 12.8084 3.21432 14.5456 5.0541L16 3.51081Z" fill="white"/>
4
+<path d="M5.94287 9.81713L7.99996 12L10.057 9.81713C9.78693 9.53042 9.46623 9.30298 9.11328 9.14781C8.76032 8.99263 8.38201 8.91277 7.99996 8.91277C7.6179 8.91277 7.23959 8.99263 6.88663 9.14781C6.53368 9.30298 6.21298 9.53042 5.94287 9.81713Z" />
5
+</svg>

+ 5
- 0
react/features/base/icons/svg/wifi-3.svg Ver fichero

1
+<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M13.0913 6.59847C12.4227 5.88894 11.629 5.32611 10.7554 4.94211C9.88182 4.55811 8.94553 4.36047 7.99997 4.36047C7.05442 4.36047 6.11813 4.55811 5.24456 4.94211C4.371 5.32611 3.57726 5.88894 2.90869 6.59847L4.36305 8.14176C4.84061 7.63486 5.4076 7.23276 6.03163 6.95842C6.65566 6.68408 7.32451 6.54288 7.99997 6.54288C8.67544 6.54288 9.34429 6.68408 9.96832 6.95842C10.5923 7.23276 11.1593 7.63486 11.6369 8.14176L13.0913 6.59847Z" fill="white"/>
3
+<path d="M16 3.51081C13.8766 1.26261 10.9996 0 8 0C5.00044 0 2.12337 1.26261 0 3.51081L1.45436 5.0541C3.19156 3.21432 5.54565 2.18105 8 2.18105C10.4543 2.18105 12.8084 3.21432 14.5456 5.0541L16 3.51081Z" fill="white"/>
4
+<path d="M5.94287 9.81713L7.99996 12L10.057 9.81713C9.78693 9.53042 9.46623 9.30298 9.11328 9.14781C8.76032 8.99263 8.38201 8.91277 7.99996 8.91277C7.6179 8.91277 7.23959 8.99263 6.88663 9.14781C6.53368 9.30298 6.21298 9.53042 5.94287 9.81713Z" />
5
+</svg>

+ 104
- 0
react/features/base/premeeting/components/web/ConnectionStatus.js Ver fichero

1
+// @flow
2
+
3
+import React, { useState } from 'react';
4
+
5
+import { translate } from '../../../i18n';
6
+import { Icon, IconArrowDownSmall, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons';
7
+import { connect } from '../../../redux';
8
+import { CONNECTION_TYPE } from '../../constants';
9
+import { getConnectionData } from '../../functions';
10
+
11
+type Props = {
12
+
13
+    /**
14
+     * List of strings with details about the connection.
15
+     */
16
+    connectionDetails: string[],
17
+
18
+    /**
19
+     * The type of the connection. Can be: 'none', 'poor', 'nonOptimal' or 'good'.
20
+     */
21
+    connectionType: string,
22
+
23
+    /**
24
+     * Used for translation.
25
+     */
26
+    t: Function
27
+}
28
+
29
+const CONNECTION_TYPE_MAP = {
30
+    [CONNECTION_TYPE.POOR]: {
31
+        connectionClass: 'con-status--poor',
32
+        icon: IconWifi1Bar,
33
+        connectionText: 'prejoin.connection.poor'
34
+    },
35
+    [CONNECTION_TYPE.NON_OPTIMAL]: {
36
+        connectionClass: 'con-status--non-optimal',
37
+        icon: IconWifi2Bars,
38
+        connectionText: 'prejoin.connection.nonOptimal'
39
+    },
40
+    [CONNECTION_TYPE.GOOD]: {
41
+        connectionClass: 'con-status--good',
42
+        icon: IconWifi3Bars,
43
+        connectionText: 'prejoin.connection.good'
44
+    }
45
+};
46
+
47
+/**
48
+ * Component displaying information related to the connection & audio/video quality.
49
+ *
50
+ * @param {Props} props - The props of the component.
51
+ * @returns {ReactElement}
52
+ */
53
+function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
54
+    if (connectionType === CONNECTION_TYPE.NONE) {
55
+        return null;
56
+    }
57
+
58
+    const { connectionClass, icon, connectionText } = CONNECTION_TYPE_MAP[connectionType];
59
+    const [ showDetails, toggleDetails ] = useState(false);
60
+    const arrowClassName = showDetails
61
+        ? 'con-status-arrow con-status-arrow--up'
62
+        : 'con-status-arrow';
63
+    const detailsText = connectionDetails.map(t).join(' ');
64
+
65
+    return (
66
+        <div className = 'con-status'>
67
+            <div className = 'con-status-container'>
68
+                <div className = 'con-status-header'>
69
+                    <div className = { `con-status-circle ${connectionClass}` }>
70
+                        <Icon
71
+                            size = { 16 }
72
+                            src = { icon } />
73
+                    </div>
74
+                    <span className = 'con-status-text'>{t(connectionText)}</span>
75
+                    <Icon
76
+                        className = { arrowClassName }
77
+                        // eslint-disable-next-line react/jsx-no-bind
78
+                        onClick = { () => toggleDetails(!showDetails) }
79
+                        size = { 24 }
80
+                        src = { IconArrowDownSmall } />
81
+                </div>
82
+                { showDetails
83
+                  && <div className = 'con-status-details'>{detailsText}</div> }
84
+            </div>
85
+        </div>
86
+    );
87
+}
88
+
89
+/**
90
+ * Maps (parts of) the redux state to the React {@code Component} props.
91
+ *
92
+ * @param {Object} state - The redux state.
93
+ * @returns {Object}
94
+ */
95
+function mapStateToProps(state): Object {
96
+    const { connectionDetails, connectionType } = getConnectionData(state);
97
+
98
+    return {
99
+        connectionDetails,
100
+        connectionType
101
+    };
102
+}
103
+
104
+export default translate(connect(mapStateToProps)(ConnectionStatus));

+ 2
- 0
react/features/base/premeeting/components/web/PreMeetingScreen.js Ver fichero

4
 
4
 
5
 import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox/components/web';
5
 import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox/components/web';
6
 
6
 
7
+import ConnectionStatus from './ConnectionStatus';
7
 import CopyMeetingUrl from './CopyMeetingUrl';
8
 import CopyMeetingUrl from './CopyMeetingUrl';
8
 import Preview from './Preview';
9
 import Preview from './Preview';
9
 
10
 
82
             <div
83
             <div
83
                 className = 'premeeting-screen'
84
                 className = 'premeeting-screen'
84
                 id = 'lobby-screen'>
85
                 id = 'lobby-screen'>
86
+                <ConnectionStatus />
85
                 <Preview
87
                 <Preview
86
                     name = { name }
88
                     name = { name }
87
                     showAvatar = { showAvatar }
89
                     showAvatar = { showAvatar }

+ 8
- 0
react/features/base/premeeting/constants.js Ver fichero

1
+// @flow
2
+
3
+export const CONNECTION_TYPE = {
4
+    GOOD: 'good',
5
+    NON_OPTIMAL: 'nonOptimal',
6
+    NONE: 'none',
7
+    POOR: 'poor'
8
+};

+ 142
- 0
react/features/base/premeeting/functions.js Ver fichero

1
+import { findIndex } from 'lodash';
2
+
3
+import { CONNECTION_TYPE } from './constants';
4
+
5
+const LOSS_AUDIO_THRESHOLDS = [ 0.33, 0.05 ];
6
+const LOSS_VIDEO_THRESHOLDS = [ 0.33, 0.1, 0.05 ];
7
+
8
+const THROUGHPUT_AUDIO_THRESHOLDS = [ 8, 20 ];
9
+const THROUGHPUT_VIDEO_THRESHOLDS = [ 60, 750 ];
10
+
11
+/**
12
+ * Returns the level based on a list of thresholds.
13
+ *
14
+ * @param {number[]} thresholds - The thresholds array.
15
+ * @param {number} value - The value against which the level is calculated.
16
+ * @param {boolean} descending - The order based on which the level is calculated.
17
+ *
18
+ * @returns {number}
19
+ */
20
+function _getLevel(thresholds, value, descending = true) {
21
+    let predicate;
22
+
23
+    if (descending) {
24
+        predicate = function(threshold) {
25
+            return value > threshold;
26
+        };
27
+    } else {
28
+        predicate = function(threshold) {
29
+            return value < threshold;
30
+        };
31
+    }
32
+
33
+    const i = findIndex(thresholds, predicate);
34
+
35
+    if (i === -1) {
36
+        return thresholds.length;
37
+    }
38
+
39
+    return i;
40
+}
41
+
42
+/**
43
+ * Returns the connection details from the test results.
44
+ *
45
+ * @param {{
46
+ *   fractionalLoss: number,
47
+ *   throughput: number
48
+ * }} testResults - The state of the app.
49
+ *
50
+ * @returns {{
51
+ *   connectionType: string,
52
+ *   connectionDetails: string[]
53
+ * }}
54
+ */
55
+function _getConnectionDataFromTestResults({ fractionalLoss: l, throughput: t }) {
56
+    const loss = {
57
+        audioQuality: _getLevel(LOSS_AUDIO_THRESHOLDS, l),
58
+        videoQuality: _getLevel(LOSS_VIDEO_THRESHOLDS, l)
59
+    };
60
+    const throughput = {
61
+        audioQuality: _getLevel(THROUGHPUT_AUDIO_THRESHOLDS, t, false),
62
+        videoQuality: _getLevel(THROUGHPUT_VIDEO_THRESHOLDS, t, false)
63
+    };
64
+    let connectionType = CONNECTION_TYPE.NONE;
65
+    const connectionDetails = [];
66
+
67
+    if (throughput.audioQuality === 0 || loss.audioQuality === 0) {
68
+        // Calls are impossible.
69
+        connectionType = CONNECTION_TYPE.POOR;
70
+        connectionDetails.push('prejoin.connectionDetails.veryPoorConnection');
71
+    } else if (
72
+        throughput.audioQuality === 2
73
+        && throughput.videoQuality === 2
74
+        && loss.audioQuality === 2
75
+        && loss.videoQuality === 3
76
+    ) {
77
+        // Ideal conditions for both audio and video. Show only one message.
78
+        connectionType = CONNECTION_TYPE.GOOD;
79
+        connectionDetails.push('prejoin.connectionDetails.goodQuality');
80
+    } else {
81
+        connectionType = CONNECTION_TYPE.NON_OPTIMAL;
82
+
83
+        if (throughput.audioQuality === 1) {
84
+            // Minimum requirements for a call are met.
85
+            connectionDetails.push('prejoin.connectionDetails.audioLowNoVideo');
86
+        } else {
87
+            // There are two paragraphs: one saying something about audio and the other about video.
88
+            if (loss.audioQuality === 1) {
89
+                connectionDetails.push('prejoin.connectionDetails.audioClipping');
90
+            } else {
91
+                connectionDetails.push('prejoin.connectionDetails.audioHighQuality');
92
+            }
93
+
94
+            if (throughput.videoQuality === 0 || loss.videoQuality === 0) {
95
+                connectionDetails.push('prejoin.connectionDetails.noVideo');
96
+            } else if (throughput.videoQuality === 1) {
97
+                connectionDetails.push('prejoin.connectionDetails.videoLowQuality');
98
+            } else if (loss.videoQuality === 1) {
99
+                connectionDetails.push('prejoin.connectionDetails.videoFreezing');
100
+            } else if (loss.videoQuality === 2) {
101
+                connectionDetails.push('prejoin.connectionDetails.videoTearing');
102
+            } else {
103
+                connectionDetails.push('prejoin.connectionDetails.videoHighQuality');
104
+            }
105
+        }
106
+        connectionDetails.push('prejoin.connectionDetails.undetectable');
107
+    }
108
+
109
+    return {
110
+        connectionType,
111
+        connectionDetails
112
+    };
113
+}
114
+
115
+/**
116
+ * Selector for determining the connection type & details.
117
+ *
118
+ * @param {Object} state - The state of the app.
119
+ * @returns {{
120
+ *   connectionType: string,
121
+ *   connectionDetails: string[]
122
+ * }}
123
+ */
124
+export function getConnectionData(state) {
125
+    const { precallTestResults } = state['features/prejoin'];
126
+
127
+    if (precallTestResults) {
128
+        if (precallTestResults.mediaConnectivity) {
129
+            return _getConnectionDataFromTestResults(precallTestResults);
130
+        }
131
+
132
+        return {
133
+            connectionType: CONNECTION_TYPE.POOR,
134
+            connectionDetails: [ 'prejoin.connectionDetails.noMediaConnectivity' ]
135
+        };
136
+    }
137
+
138
+    return {
139
+        connectionType: CONNECTION_TYPE.NONE,
140
+        connectionDetails: []
141
+    };
142
+}

+ 5
- 0
react/features/prejoin/actionTypes.js Ver fichero

39
  */
39
  */
40
 export const SET_JOIN_BY_PHONE_DIALOG_VISIBLITY = 'SET_JOIN_BY_PHONE_DIALOG_VISIBLITY';
40
 export const SET_JOIN_BY_PHONE_DIALOG_VISIBLITY = 'SET_JOIN_BY_PHONE_DIALOG_VISIBLITY';
41
 
41
 
42
+/**
43
+ * Action type to set the precall test data.
44
+ */
45
+export const SET_PRECALL_TEST_RESULTS = 'SET_PRECALL_TEST_RESULTS';
46
+
42
 /**
47
 /**
43
  * Action type to disable the audio while on prejoin page.
48
  * Action type to disable the audio while on prejoin page.
44
  */
49
  */

+ 32
- 0
react/features/prejoin/actions.js Ver fichero

1
 // @flow
1
 // @flow
2
 
2
 
3
+declare var JitsiMeetJS: Object;
4
+
3
 import uuid from 'uuid';
5
 import uuid from 'uuid';
4
 
6
 
5
 import { getRoomName } from '../base/conference';
7
 import { getRoomName } from '../base/conference';
24
     SET_PREJOIN_DISPLAY_NAME_REQUIRED,
26
     SET_PREJOIN_DISPLAY_NAME_REQUIRED,
25
     SET_SKIP_PREJOIN,
27
     SET_SKIP_PREJOIN,
26
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
28
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
29
+    SET_PRECALL_TEST_RESULTS,
27
     SET_PREJOIN_DEVICE_ERRORS,
30
     SET_PREJOIN_DEVICE_ERRORS,
28
     SET_PREJOIN_PAGE_VISIBILITY
31
     SET_PREJOIN_PAGE_VISIBILITY
29
 } from './actionTypes';
32
 } from './actionTypes';
231
     };
234
     };
232
 }
235
 }
233
 
236
 
237
+/**
238
+ * Initializes the 'precallTest' and executes one test, storing the results.
239
+ *
240
+ * @param {Object} conferenceOptions - The conference options.
241
+ * @returns {Function}
242
+ */
243
+export function makePrecallTest(conferenceOptions: Object) {
244
+    return async function(dispatch: Function) {
245
+        await JitsiMeetJS.precallTest.init(conferenceOptions);
246
+
247
+        const results = await JitsiMeetJS.precallTest.execute();
248
+
249
+        dispatch(setPrecallTestResults(results));
250
+    };
251
+}
252
+
234
 /**
253
 /**
235
  * Opens an external page with all the dial in numbers.
254
  * Opens an external page with all the dial in numbers.
236
  *
255
  *
397
     };
416
     };
398
 }
417
 }
399
 
418
 
419
+/**
420
+ * Action used to set data from precall test.
421
+ *
422
+ * @param {Object} value - The precall test results.
423
+ * @returns {Object}
424
+ */
425
+export function setPrecallTestResults(value: Object) {
426
+    return {
427
+        type: SET_PRECALL_TEST_RESULTS,
428
+        value
429
+    };
430
+}
431
+
400
 /**
432
 /**
401
  * Action used to set the initial errors after creating the tracks.
433
  * Action used to set the initial errors after creating the tracks.
402
  *
434
  *

+ 7
- 0
react/features/prejoin/reducer.js Ver fichero

6
     SET_DIALOUT_NUMBER,
6
     SET_DIALOUT_NUMBER,
7
     SET_DIALOUT_STATUS,
7
     SET_DIALOUT_STATUS,
8
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
8
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
9
+    SET_PRECALL_TEST_RESULTS,
9
     SET_PREJOIN_DEVICE_ERRORS,
10
     SET_PREJOIN_DEVICE_ERRORS,
10
     SET_PREJOIN_DISPLAY_NAME_REQUIRED,
11
     SET_PREJOIN_DISPLAY_NAME_REQUIRED,
11
     SET_PREJOIN_PAGE_VISIBILITY,
12
     SET_PREJOIN_PAGE_VISIBILITY,
45
             };
46
             };
46
         }
47
         }
47
 
48
 
49
+        case SET_PRECALL_TEST_RESULTS:
50
+            return {
51
+                ...state,
52
+                precallTestResults: action.value
53
+            };
54
+
48
         case SET_PREJOIN_PAGE_VISIBILITY:
55
         case SET_PREJOIN_PAGE_VISIBILITY:
49
             return {
56
             return {
50
                 ...state,
57
                 ...state,

Loading…
Cancelar
Guardar