瀏覽代碼

[React] Call/ring overlay

Rewrite the non-React RingOverlay into the React Component CallOverlay
in a way which makes it easier to port to React Native.
master
Lyubo Marinov 8 年之前
父節點
當前提交
d437f3db03

+ 2
- 3
conference.js 查看文件

386
     _onConferenceFailed(err, ...params) {
386
     _onConferenceFailed(err, ...params) {
387
         APP.store.dispatch(conferenceFailed(room, err, ...params));
387
         APP.store.dispatch(conferenceFailed(room, err, ...params));
388
         logger.error('CONFERENCE FAILED:', err, ...params);
388
         logger.error('CONFERENCE FAILED:', err, ...params);
389
-        APP.UI.hideRingOverlay();
390
-        switch (err) {
391
 
389
 
390
+        switch (err) {
392
         case ConferenceErrors.CONNECTION_ERROR:
391
         case ConferenceErrors.CONNECTION_ERROR:
393
             {
392
             {
394
                 let [msg] = params;
393
                 let [msg] = params;
2027
      */
2026
      */
2028
     hangup(requestFeedback = false) {
2027
     hangup(requestFeedback = false) {
2029
         eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
2028
         eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
2030
-        APP.UI.hideRingOverlay();
2029
+
2031
         let requestFeedbackPromise = requestFeedback
2030
         let requestFeedbackPromise = requestFeedback
2032
                 ? APP.UI.requestFeedbackOnHangup()
2031
                 ? APP.UI.requestFeedbackOnHangup()
2033
                 // false - because the thank you dialog shouldn't be displayed
2032
                 // false - because the thank you dialog shouldn't be displayed

+ 9
- 3
interface_config.js 查看文件

70
     ENABLE_FEEDBACK_ANIMATION: false,
70
     ENABLE_FEEDBACK_ANIMATION: false,
71
     DISABLE_FOCUS_INDICATOR: false,
71
     DISABLE_FOCUS_INDICATOR: false,
72
     DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
72
     DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
73
-    // disables the ringing sound when the RingOverlay is shown.
73
+
74
+    /**
75
+     * Whether the ringing sound in the call/ring overlay is disabled. If
76
+     * {@code undefined}, defaults to {@code false}.
77
+     *
78
+     * @type {boolean}
79
+     */
74
     DISABLE_RINGING: false,
80
     DISABLE_RINGING: false,
75
     AUDIO_LEVEL_PRIMARY_COLOR: "rgba(255,255,255,0.4)",
81
     AUDIO_LEVEL_PRIMARY_COLOR: "rgba(255,255,255,0.4)",
76
     AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
82
     AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
82
 
88
 
83
     /**
89
     /**
84
      * Whether the mobile app Jitsi Meet is to be promoted to participants
90
      * Whether the mobile app Jitsi Meet is to be promoted to participants
85
-     * attempting to join a conference in a mobile Web browser. If undefined,
86
-     * default to true.
91
+     * attempting to join a conference in a mobile Web browser. If
92
+     * {@code undefined}, defaults to {@code true}.
87
      *
93
      *
88
      * @type {boolean}
94
      * @type {boolean}
89
      */
95
      */

+ 6
- 33
modules/UI/UI.js 查看文件

19
 import SettingsMenu from "./side_pannels/settings/SettingsMenu";
19
 import SettingsMenu from "./side_pannels/settings/SettingsMenu";
20
 import Profile from "./side_pannels/profile/Profile";
20
 import Profile from "./side_pannels/profile/Profile";
21
 import Settings from "./../settings/Settings";
21
 import Settings from "./../settings/Settings";
22
-import RingOverlay from "./ring_overlay/RingOverlay";
23
 import UIErrors from './UIErrors';
22
 import UIErrors from './UIErrors';
24
 import { debounce } from "../util/helpers";
23
 import { debounce } from "../util/helpers";
25
 
24
 
26
-
27
-import {
28
-    updateDeviceList
29
-} from '../../react/features/base/devices';
30
-import {
31
-    setAudioMuted,
32
-    setVideoMuted
33
-} from '../../react/features/base/media';
25
+import { updateDeviceList } from '../../react/features/base/devices';
26
+import { setAudioMuted, setVideoMuted } from '../../react/features/base/media';
34
 import {
27
 import {
35
     openDeviceSelectionDialog
28
     openDeviceSelectionDialog
36
 } from '../../react/features/device-selection';
29
 } from '../../react/features/device-selection';
368
             "closeHtml": '<button type="button" tabIndex="-1">&times;</button>'
361
             "closeHtml": '<button type="button" tabIndex="-1">&times;</button>'
369
         };
362
         };
370
     }
363
     }
371
-
372
-    const { callee } = APP.store.getState()['features/jwt'];
373
-
374
-    callee && UI.showRingOverlay();
375
 };
364
 };
376
 
365
 
377
 /**
366
 /**
492
 UI.addUser = function (user) {
481
 UI.addUser = function (user) {
493
     var id = user.getId();
482
     var id = user.getId();
494
     var displayName = user.getDisplayName();
483
     var displayName = user.getDisplayName();
495
-    UI.hideRingOverlay();
484
+
496
     if (UI.ContactList)
485
     if (UI.ContactList)
497
         UI.ContactList.addContact(id);
486
         UI.ContactList.addContact(id);
498
 
487
 
1371
 UI.setMicrophoneButtonEnabled
1360
 UI.setMicrophoneButtonEnabled
1372
     = enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
1361
     = enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
1373
 
1362
 
1374
-UI.showRingOverlay = function () {
1375
-    const { callee } = APP.store.getState()['features/jwt'];
1376
-
1377
-    callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
1378
-
1379
-    Filmstrip.toggleFilmstrip(false, false);
1380
-};
1381
-
1382
-UI.hideRingOverlay
1383
-    = () => RingOverlay.hide() && Filmstrip.toggleFilmstrip(true, false);
1384
-
1385
 /**
1363
 /**
1386
  * Indicates if any the "top" overlays are currently visible. The check includes
1364
  * Indicates if any the "top" overlays are currently visible. The check includes
1387
  * the call/ring overlay, the suspended overlay, the GUM permissions overlay,
1365
  * the call/ring overlay, the suspended overlay, the GUM permissions overlay,
1390
  * @returns {*|boolean} {true} if an overlay is visible; {false}, otherwise
1368
  * @returns {*|boolean} {true} if an overlay is visible; {false}, otherwise
1391
  */
1369
  */
1392
 UI.isOverlayVisible = function () {
1370
 UI.isOverlayVisible = function () {
1393
-    return this.isRingOverlayVisible() || this.overlayVisible;
1371
+    return (
1372
+        this.overlayVisible
1373
+            || APP.store.getState()['features/jwt'].callOverlayVisible);
1394
 };
1374
 };
1395
 
1375
 
1396
-/**
1397
- * Indicates if the ring overlay is currently visible.
1398
- *
1399
- * @returns {*|boolean} {true} if the ring overlay is visible, {false} otherwise
1400
- */
1401
-UI.isRingOverlayVisible = () => RingOverlay.isVisible();
1402
-
1403
 /**
1376
 /**
1404
  * Handles user's features changes.
1377
  * Handles user's features changes.
1405
  */
1378
  */

+ 0
- 178
modules/UI/ring_overlay/RingOverlay.js 查看文件

1
-/* global $, APP */
2
-
3
-import UIEvents from "../../../service/UI/UIEvents";
4
-
5
-/**
6
- * Store the current ring overlay instance.
7
- * Note: We want to have only 1 instance at a time.
8
- */
9
-let overlay = null;
10
-
11
-/**
12
- * Handler for UIEvents.LARGE_VIDEO_AVATAR_VISIBLE event.
13
- * @param {boolean} shown indicates whether the avatar on the large video is
14
- *  currently displayed or not.
15
- */
16
-function onAvatarVisible(shown) {
17
-    overlay._changeBackground(shown);
18
-}
19
-
20
-/**
21
- * Shows ring overlay
22
- */
23
-class RingOverlay {
24
-    /**
25
-     *
26
-     * @param callee The callee (Object) as defined by the JWT support.
27
-     * @param {boolean} disableRinging if true the ringing sound wont be played.
28
-     */
29
-    constructor(callee, disableRinging) {
30
-        this._containerId = 'ringOverlay';
31
-        this._audioContainerId = 'ringOverlayRinging';
32
-        this.isRinging = true;
33
-        this.callee = callee;
34
-        this.disableRinging = disableRinging;
35
-        this.render();
36
-        if (!disableRinging)
37
-            this._initAudio();
38
-        this._timeout
39
-            = setTimeout(
40
-                    () => {
41
-                        this.destroy();
42
-                        this.render();
43
-                    },
44
-                    30000);
45
-    }
46
-
47
-    /**
48
-     * Initializes the audio element and setups the interval for playing it.
49
-     */
50
-    _initAudio() {
51
-        this.audio = document.getElementById(this._audioContainerId);
52
-        this.audio.play();
53
-        this.interval = setInterval(() => this.audio.play(), 5000);
54
-    }
55
-
56
-    /**
57
-     * Chagnes the background of the ring overlay.
58
-     * @param {boolean} solid - if true the new background will be the solid
59
-     * one, otherwise the background will be default one.
60
-     * NOTE: The method just toggles solidBG css class.
61
-     */
62
-    _changeBackground(solid) {
63
-        const container = $("#" + this._containerId);
64
-
65
-        if (solid) {
66
-            container.addClass("solidBG");
67
-        } else {
68
-            container.removeClass("solidBG");
69
-        }
70
-    }
71
-
72
-    /**
73
-     * Builds and appends the ring overlay to the html document
74
-     */
75
-    _getHtmlStr(callee) {
76
-        let callingLabel = this.isRinging ? "<p>Calling...</p>" : "";
77
-        let callerStateLabel =  this.isRinging ? "" : " isn't available";
78
-        let audioHTML = this.disableRinging ? ""
79
-            : "<audio id=\"" + this._audioContainerId
80
-                + "\" src=\"./sounds/ring.ogg\" />";
81
-
82
-        return `
83
-            <div id="${this._containerId}" class='ringing' >
84
-                <div class='ringing__content'>
85
-                    ${callingLabel}
86
-                    <img class='ringing__avatar' src="${callee.avatarUrl}" />
87
-                    <div class="ringing__caller-info">
88
-                        <p>${callee.name}${callerStateLabel}</p>
89
-                    </div>
90
-                </div>
91
-                ${audioHTML}
92
-            </div>`;
93
-    }
94
-
95
-    /**
96
-     *
97
-     */
98
-    render() {
99
-        this.htmlStr = this._getHtmlStr(this.callee);
100
-        this._attach();
101
-    }
102
-
103
-    /**
104
-     * Destroys and clears all the objects (html elements and audio interval)
105
-     * related to the ring overlay.
106
-     */
107
-    destroy() {
108
-        this.isRinging = false;
109
-        this._stopAudio();
110
-        this._detach();
111
-    }
112
-
113
-    _attach() {
114
-        $("body").append(this.htmlStr);
115
-    }
116
-
117
-    _detach() {
118
-        $(`#${this._containerId}`).remove();
119
-    }
120
-
121
-    /**
122
-     * Stops the ringing and clears related timers.
123
-     */
124
-    _stopAudio() {
125
-        if (this.interval) {
126
-            clearInterval(this.interval);
127
-        }
128
-        if (this._timeout) {
129
-            clearTimeout(this._timeout);
130
-        }
131
-    }
132
-}
133
-
134
-export default {
135
-    /**
136
-     * Shows the ring overlay for the passed callee.
137
-     *
138
-     * @param {Object} callee - The callee. Object containing data about
139
-     * callee.
140
-     * @param {boolean} disableRinging - If true the ringing sound won't be
141
-     * played.
142
-     * @returns {void}
143
-     */
144
-    show(callee, disableRinging = false) {
145
-        if (overlay) {
146
-            this.hide();
147
-        }
148
-
149
-        overlay = new RingOverlay(callee, disableRinging);
150
-        APP.UI.addListener(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
151
-            onAvatarVisible);
152
-    },
153
-
154
-    /**
155
-     * Hides the ring overlay. Destroys all the elements related to the ring
156
-     * overlay.
157
-     */
158
-    hide() {
159
-        if (!overlay) {
160
-            return false;
161
-        }
162
-        overlay.destroy();
163
-        overlay = null;
164
-        APP.UI.removeListener(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
165
-            onAvatarVisible);
166
-        return true;
167
-    },
168
-
169
-    /**
170
-     * Checks whether or not the ring overlay is currently displayed.
171
-     *
172
-     * @returns {boolean} true if the ring overlay is currently displayed or
173
-     * false otherwise.
174
-     */
175
-    isVisible() {
176
-        return overlay !== null;
177
-    }
178
-};

+ 0
- 1
react/features/base/connection/actions.web.js 查看文件

77
             }
77
             }
78
         })
78
         })
79
             .catch(error => {
79
             .catch(error => {
80
-                APP.UI.hideRingOverlay();
81
                 APP.API.notifyConferenceLeft(APP.conference.roomName);
80
                 APP.API.notifyConferenceLeft(APP.conference.roomName);
82
                 logger.error(error);
81
                 logger.error(error);
83
 
82
 

+ 27
- 9
react/features/filmstrip/middleware.js 查看文件

1
-import UIEvents from '../../../service/UI/UIEvents';
1
+/* @flow */
2
 
2
 
3
 import { MiddlewareRegistry } from '../base/redux';
3
 import { MiddlewareRegistry } from '../base/redux';
4
+import { SET_CALL_OVERLAY_VISIBLE } from '../jwt';
5
+
6
+import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
7
+import UIEvents from '../../../service/UI/UIEvents';
4
 
8
 
5
 import {
9
 import {
6
     SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
10
     SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
10
 declare var APP: Object;
14
 declare var APP: Object;
11
 
15
 
12
 // eslint-disable-next-line no-unused-vars
16
 // eslint-disable-next-line no-unused-vars
13
-MiddlewareRegistry.register(store => next => action => {
14
-    const result = next(action);
15
-
17
+MiddlewareRegistry.register(({ getState }) => next => action => {
16
     switch (action.type) {
18
     switch (action.type) {
17
-    case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
18
-    case SET_FILMSTRIP_VISIBILITY: {
19
-        if (typeof APP !== 'undefined') {
20
-            APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
19
+    case SET_CALL_OVERLAY_VISIBLE:
20
+        if (typeof APP === 'undefined') {
21
+            const oldValue
22
+                = Boolean(getState()['features/jwt'].callOverlayVisible);
23
+            const result = next(action);
24
+            const newValue
25
+                = Boolean(getState()['features/jwt'].callOverlayVisible);
21
 
26
 
27
+            oldValue === newValue
28
+                || Filmstrip.toggleFilmstrip(!newValue, false);
29
+
30
+            return result;
22
         }
31
         }
23
         break;
32
         break;
33
+
34
+    case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
35
+    case SET_FILMSTRIP_VISIBILITY: {
36
+        const result = next(action);
37
+
38
+        typeof APP === 'undefined'
39
+            || APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
40
+
41
+        return result;
24
     }
42
     }
25
     }
43
     }
26
 
44
 
27
-    return result;
45
+    return next(action);
28
 });
46
 });

+ 10
- 0
react/features/jwt/actionTypes.js 查看文件

1
+/**
2
+ * The type of redux action which sets the visibility of {@code CallOverlay}.
3
+ *
4
+ * {
5
+ *     type: SET_CALL_OVERLAY_VISIBLE,
6
+ *     callOverlayVisible: boolean
7
+ * }
8
+ */
9
+export const SET_CALL_OVERLAY_VISIBLE = Symbol('SET_CALL_OVERLAY_VISIBLE');
10
+
1
 /**
11
 /**
2
  * The type of redux action which stores a specific JSON Web Token (JWT) into
12
  * The type of redux action which stores a specific JSON Web Token (JWT) into
3
  * the redux store.
13
  * the redux store.

+ 22
- 1
react/features/jwt/actions.js 查看文件

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
-import { SET_JWT } from './actionTypes';
3
+import { SET_CALL_OVERLAY_VISIBLE, SET_JWT } from './actionTypes';
4
+
5
+/**
6
+ * Sets the visibility of {@code CallOverlay}.
7
+ *
8
+ * @param {boolean|undefined} callOverlayVisible - If {@code CallOverlay} is to
9
+ * be displayed/visible, then {@code true}; otherwise, {@code false} or
10
+ * {@code undefined}.
11
+ * @returns {{
12
+ *     type: SET_CALL_OVERLAY_VISIBLE,
13
+ *     callOverlayVisible: (boolean|undefined)
14
+ * }}
15
+ */
16
+export function setCallOverlayVisible(callOverlayVisible: boolean) {
17
+    return (dispatch: Dispatch<*>, getState: Function) => {
18
+        getState()['features/jwt'].callOverlayVisible === callOverlayVisible
19
+            || dispatch({
20
+                type: SET_CALL_OVERLAY_VISIBLE,
21
+                callOverlayVisible
22
+            });
23
+    };
24
+}
4
 
25
 
5
 /**
26
 /**
6
  * Stores a specific JSON Web Token (JWT) into the redux store.
27
  * Stores a specific JSON Web Token (JWT) into the redux store.

+ 288
- 0
react/features/jwt/components/CallOverlay.js 查看文件

1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { Audio } from '../../base/media';
7
+import { Avatar } from '../../base/participants';
8
+import { Container, Text } from '../../base/react';
9
+import UIEvents from '../../../../service/UI/UIEvents';
10
+
11
+declare var $: Object;
12
+declare var APP: Object;
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * Implements a React {@link Component} which depicts the establishment of a
17
+ * call with a specific remote callee.
18
+ *
19
+ * @extends Component
20
+ */
21
+class CallOverlay extends Component {
22
+    /**
23
+     * The (reference to the) {@link Audio} which plays/renders the audio
24
+     * depicting the ringing phase of the call establishment represented by this
25
+     * {@code CallOverlay}.
26
+     */
27
+    _audio: ?Audio
28
+
29
+    _onLargeVideoAvatarVisible: Function
30
+
31
+    _playAudioInterval: ?number
32
+
33
+    _ringingTimeout: ?number
34
+
35
+    _setAudio: Function
36
+
37
+    state: {
38
+
39
+        /**
40
+         * The CSS class (name), if any, to add to this {@code CallOverlay}.
41
+         *
42
+         * @type {string}
43
+         */
44
+        className: ?string,
45
+
46
+        /**
47
+         * The indicator which determines whether this {@code CallOverlay}
48
+         * should play/render audio to indicate the ringing phase of the
49
+         * call establishment between the local participant and the
50
+         * associated remote callee.
51
+         *
52
+         * @type {boolean}
53
+         */
54
+        renderAudio: boolean,
55
+
56
+        /**
57
+         * The indicator which determines whether this {@code CallOverlay}
58
+         * is depicting the ringing phase of the call establishment between
59
+         * the local participant and the associated remote callee or the
60
+         * phase afterwards when the callee has not answered the call for a
61
+         * period of time and, consequently, is considered unavailable.
62
+         *
63
+         * @type {boolean}
64
+         */
65
+        ringing: boolean
66
+    }
67
+
68
+    /**
69
+     * {@code CallOverlay} component's property types.
70
+     *
71
+     * @static
72
+     */
73
+    static propTypes = {
74
+        _callee: React.PropTypes.object
75
+    };
76
+
77
+    /**
78
+     * Initializes a new {@code CallOverlay} instance.
79
+     *
80
+     * @param {Object} props - The read-only React {@link Component} props with
81
+     * which the new instance is to be initialized.
82
+     */
83
+    constructor(props) {
84
+        super(props);
85
+
86
+        this.state = {
87
+            className: undefined,
88
+            renderAudio:
89
+                typeof interfaceConfig !== 'object'
90
+                    || !interfaceConfig.DISABLE_RINGING,
91
+            ringing: true
92
+        };
93
+
94
+        this._onLargeVideoAvatarVisible
95
+            = this._onLargeVideoAvatarVisible.bind(this);
96
+        this._setAudio = this._setAudio.bind(this);
97
+
98
+        if (typeof APP === 'object') {
99
+            APP.UI.addListener(
100
+                UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
101
+                this._onLargeVideoAvatarVisible);
102
+        }
103
+    }
104
+
105
+    /**
106
+     * Sets up timeouts such as the timeout to end the ringing phase of the call
107
+     * establishment depicted by this {@code CallOverlay}.
108
+     *
109
+     * @inheritdoc
110
+     */
111
+    componentDidMount() {
112
+        // Set up the timeout to end the ringing phase of the call establishment
113
+        // depicted by this CallOverlay.
114
+        if (this.state.ringing && !this._ringingTimeout) {
115
+            this._ringingTimeout
116
+                = setTimeout(
117
+                    () => {
118
+                        this._pauseAudio();
119
+
120
+                        this._ringingTimeout = undefined;
121
+                        this.setState({
122
+                            ringing: false
123
+                        });
124
+                    },
125
+                    30000);
126
+        }
127
+
128
+        this._playAudio();
129
+    }
130
+
131
+    /**
132
+     * Cleans up before this {@code Calleverlay} is unmounted and destroyed.
133
+     *
134
+     * @inheritdoc
135
+     */
136
+    componentWillUnmount() {
137
+        this._pauseAudio();
138
+
139
+        if (this._ringingTimeout) {
140
+            clearTimeout(this._ringingTimeout);
141
+            this._ringingTimeout = undefined;
142
+        }
143
+
144
+        if (typeof APP === 'object') {
145
+            APP.UI.removeListener(
146
+                UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
147
+                this._onLargeVideoAvatarVisible);
148
+        }
149
+    }
150
+
151
+    /**
152
+     * Implements React's {@link Component#render()}.
153
+     *
154
+     * @inheritdoc
155
+     * @returns {ReactElement}
156
+     */
157
+    render() {
158
+        const { className, ringing } = this.state;
159
+        const { avatarUrl, name } = this.props._callee;
160
+
161
+        return (
162
+            <Container
163
+                className = { `ringing ${className || ''}` }
164
+                id = 'ringOverlay'>
165
+                <Container className = 'ringing__content'>
166
+                    { ringing ? <Text>Calling...</Text> : null }
167
+                    <Avatar
168
+                        className = 'ringing__avatar'
169
+                        uri = { avatarUrl } />
170
+                    <Container className = 'ringing__caller-info'>
171
+                        <Text>
172
+                            { name }
173
+                            { ringing ? null : ' isn\'t available' }
174
+                        </Text>
175
+                    </Container>
176
+                </Container>
177
+                { this._renderAudio() }
178
+            </Container>
179
+        );
180
+    }
181
+
182
+    /**
183
+     * Notifies this {@code CallOverlay} that the visibility of the
184
+     * participant's avatar in the large video has changed.
185
+     *
186
+     * @param {boolean} largeVideoAvatarVisible - If the avatar in the large
187
+     * video (i.e. of the participant on the stage) is visible, then
188
+     * {@code true}; otherwise, {@code false}.
189
+     * @private
190
+     * @returns {void}
191
+     */
192
+    _onLargeVideoAvatarVisible(largeVideoAvatarVisible: boolean) {
193
+        this.setState({
194
+            className: largeVideoAvatarVisible ? 'solidBG' : undefined
195
+        });
196
+    }
197
+
198
+    /**
199
+     * Stops the playback of the audio which represents the ringing phase of the
200
+     * call establishment depicted by this {@code CallOverlay}.
201
+     *
202
+     * @private
203
+     * @returns {void}
204
+     */
205
+    _pauseAudio() {
206
+        const audio = this._audio;
207
+
208
+        if (audio) {
209
+            audio.pause();
210
+        }
211
+        if (this._playAudioInterval) {
212
+            clearInterval(this._playAudioInterval);
213
+            this._playAudioInterval = undefined;
214
+        }
215
+    }
216
+
217
+    /**
218
+     * Starts the playback of the audio which represents the ringing phase of
219
+     * the call establishment depicted by this {@code CallOverlay}.
220
+     *
221
+     * @private
222
+     * @returns {void}
223
+     */
224
+    _playAudio() {
225
+        if (this._audio) {
226
+            this._audio.play();
227
+            if (!this._playAudioInterval) {
228
+                this._playAudioInterval
229
+                    = setInterval(() => this._playAudio(), 5000);
230
+            }
231
+        }
232
+    }
233
+
234
+    /**
235
+     * Renders an audio element to represent the ringing phase of the call
236
+     * establishment represented by this {@code CallOverlay}.
237
+     *
238
+     * @private
239
+     * @returns {ReactElement}
240
+     */
241
+    _renderAudio() {
242
+        if (this.state.renderAudio && this.state.ringing) {
243
+            return (
244
+                <Audio
245
+                    ref = { this._setAudio }
246
+                    src = './sounds/ring.ogg' />
247
+            );
248
+        }
249
+
250
+        return null;
251
+    }
252
+
253
+    /**
254
+     * Sets the (reference to the) {@link Audio} which renders the ringing phase
255
+     * of the call establishment represented by this {@code CallOverlay}.
256
+     *
257
+     * @param {Audio} audio - The (reference to the) {@code Audio} which
258
+     * plays/renders the audio depicting the ringing phase of the call
259
+     * establishment represented by this {@code CallOverlay}.
260
+     * @private
261
+     * @returns {void}
262
+     */
263
+    _setAudio(audio) {
264
+        this._audio = audio;
265
+    }
266
+}
267
+
268
+/**
269
+ * Maps (parts of) the redux state to {@code CallOverlay}'s props.
270
+ *
271
+ * @param {Object} state - The redux state.
272
+ * @private
273
+ * @returns {{
274
+ *     _callee: Object
275
+ * }}
276
+ */
277
+function _mapStateToProps(state) {
278
+    return {
279
+        /**
280
+         *
281
+         * @private
282
+         * @type {Object}
283
+         */
284
+        _callee: state['features/jwt'].callee
285
+    };
286
+}
287
+
288
+export default connect(_mapStateToProps)(CallOverlay);

+ 1
- 0
react/features/jwt/components/index.js 查看文件

1
+export { default as CallOverlay } from './CallOverlay';

+ 1
- 0
react/features/jwt/index.js 查看文件

1
 export * from './actions';
1
 export * from './actions';
2
+export * from './components';
2
 export * from './functions';
3
 export * from './functions';
3
 
4
 
4
 import './middleware';
5
 import './middleware';

+ 100
- 13
react/features/jwt/middleware.js 查看文件

1
 import jwtDecode from 'jwt-decode';
1
 import jwtDecode from 'jwt-decode';
2
 
2
 
3
+import {
4
+    CONFERENCE_FAILED,
5
+    CONFERENCE_LEFT,
6
+    CONFERENCE_WILL_LEAVE,
7
+    SET_ROOM
8
+} from '../base/conference';
3
 import { SET_CONFIG } from '../base/config';
9
 import { SET_CONFIG } from '../base/config';
4
 import { SET_LOCATION_URL } from '../base/connection';
10
 import { SET_LOCATION_URL } from '../base/connection';
11
+import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
12
+import { PARTICIPANT_JOINED } from '../base/participants';
5
 import { MiddlewareRegistry } from '../base/redux';
13
 import { MiddlewareRegistry } from '../base/redux';
6
 
14
 
7
-import { setJWT } from './actions';
15
+import { setCallOverlayVisible, setJWT } from './actions';
8
 import { SET_JWT } from './actionTypes';
16
 import { SET_JWT } from './actionTypes';
9
 import { parseJWTFromURLParams } from './functions';
17
 import { parseJWTFromURLParams } from './functions';
10
 
18
 
11
 /**
19
 /**
12
  * Middleware to parse token data upon setting a new room URL.
20
  * Middleware to parse token data upon setting a new room URL.
13
  *
21
  *
14
- * @param {Store} store - The Redux store.
22
+ * @param {Store} store - The redux store.
15
  * @private
23
  * @private
16
  * @returns {Function}
24
  * @returns {Function}
17
  */
25
  */
18
 MiddlewareRegistry.register(store => next => action => {
26
 MiddlewareRegistry.register(store => next => action => {
19
     switch (action.type) {
27
     switch (action.type) {
28
+    case CONFERENCE_FAILED:
29
+    case CONFERENCE_LEFT:
30
+    case CONFERENCE_WILL_LEAVE:
31
+    case LIB_INIT_ERROR:
32
+    case PARTICIPANT_JOINED:
33
+    case SET_ROOM:
34
+        return _maybeSetCallOverlayVisible(store, next, action);
35
+
20
     case SET_CONFIG:
36
     case SET_CONFIG:
21
     case SET_LOCATION_URL:
37
     case SET_LOCATION_URL:
22
         // XXX The JSON Web Token (JWT) is not the only piece of state that we
38
         // XXX The JSON Web Token (JWT) is not the only piece of state that we
33
     return next(action);
49
     return next(action);
34
 });
50
 });
35
 
51
 
52
+/**
53
+ * Notifies the feature jwt that a specific {@code action} is being dispatched
54
+ * within a specific redux {@code store} which may have an effect on the
55
+ * visiblity of (the) {@code CallOverlay}.
56
+ *
57
+ * @param {Store} store - The redux store in which the specified {@code action}
58
+ * is being dispatched.
59
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
60
+ * specified {@code action} to the specified {@code store}.
61
+ * @param {Action} action - The redux action which is being dispatched in the
62
+ * specified {@code store}.
63
+ * @private
64
+ * @returns {Object} The new state that is the result of the reduction of the
65
+ * specified {@code action}.
66
+ */
67
+function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
68
+    const result = next(action);
69
+
70
+    const state = getState();
71
+    const stateFeaturesJWT = state['features/jwt'];
72
+    let callOverlayVisible;
73
+
74
+    if (stateFeaturesJWT.callee) {
75
+        const { conference, leaving, room } = state['features/base/conference'];
76
+
77
+        // XXX The CallOverlay is to be displayed/visible as soon as
78
+        // possible including even before the conference is joined.
79
+        if (room && (!conference || conference !== leaving)) {
80
+            switch (action.type) {
81
+            case CONFERENCE_FAILED:
82
+            case CONFERENCE_LEFT:
83
+            case CONFERENCE_WILL_LEAVE:
84
+            case LIB_INIT_ERROR:
85
+                // Because the CallOverlay is to be displayed/visible as soon as
86
+                // possible even before the connection is established and/or the
87
+                // conference is joined, it is very complicated to figure out
88
+                // based on the current state alone. In order to reduce the
89
+                // risks of displaying the CallOverly at inappropirate times, do
90
+                // not even attempt to figure out based on the current state.
91
+                // The (redux) actions listed above are also the equivalents of
92
+                // the execution ponints at which APP.UI.hideRingOverlay() used
93
+                // to be invoked.
94
+                break;
95
+
96
+            default: {
97
+                // The CallOverlay it to no longer be displayed/visible as soon
98
+                // as another participant joins.
99
+                const participants = state['features/base/participants'];
100
+
101
+                callOverlayVisible
102
+                    = Boolean(
103
+                            participants
104
+                                && participants.length === 1
105
+                                && participants[0].local);
106
+
107
+                // However, the CallDialog is not to be displayed/visible again
108
+                // after all remote participants leave.
109
+                if (callOverlayVisible
110
+                        && stateFeaturesJWT.callOverlayVisible === false) {
111
+                    callOverlayVisible = false;
112
+                }
113
+                break;
114
+            }
115
+            }
116
+        }
117
+    }
118
+    dispatch(setCallOverlayVisible(callOverlayVisible));
119
+
120
+    return result;
121
+}
122
+
36
 /**
123
 /**
37
  * Notifies the feature jwt that the action {@link SET_CONFIG} or
124
  * Notifies the feature jwt that the action {@link SET_CONFIG} or
38
- * {@link SET_LOCATION_URL} is being dispatched within a specific Redux
125
+ * {@link SET_LOCATION_URL} is being dispatched within a specific redux
39
  * {@code store}.
126
  * {@code store}.
40
  *
127
  *
41
- * @param {Store} store - The Redux store in which the specified {@code action}
128
+ * @param {Store} store - The redux store in which the specified {@code action}
42
  * is being dispatched.
129
  * is being dispatched.
43
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
130
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
44
  * specified {@code action} to the specified {@code store}.
131
  * specified {@code action} to the specified {@code store}.
45
- * @param {Action} action - The Redux action {@code SET_CONFIG} or
132
+ * @param {Action} action - The redux action {@code SET_CONFIG} or
46
  * {@code SET_LOCATION_URL} which is being dispatched in the specified
133
  * {@code SET_LOCATION_URL} which is being dispatched in the specified
47
  * {@code store}.
134
  * {@code store}.
48
  * @private
135
  * @private
65
 
152
 
66
 /**
153
 /**
67
  * Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
154
  * Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
68
- * within a specific Redux {@code store}.
155
+ * within a specific redux {@code store}.
69
  *
156
  *
70
- * @param {Store} store - The Redux store in which the specified {@code action}
157
+ * @param {Store} store - The redux store in which the specified {@code action}
71
  * is being dispatched.
158
  * is being dispatched.
72
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
159
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
73
  * specified {@code action} to the specified {@code store}.
160
  * specified {@code action} to the specified {@code store}.
74
- * @param {Action} action - The Redux action {@code SET_JWT} which is being
161
+ * @param {Action} action - The redux action {@code SET_JWT} which is being
75
  * dispatched in the specified {@code store}.
162
  * dispatched in the specified {@code store}.
76
  * @private
163
  * @private
77
  * @returns {Object} The new state that is the result of the reduction of the
164
  * @returns {Object} The new state that is the result of the reduction of the
78
  * specified {@code action}.
165
  * specified {@code action}.
79
  */
166
  */
80
-function _setJWT({ getState }, next, action) {
167
+function _setJWT(store, next, action) {
81
     // eslint-disable-next-line no-unused-vars
168
     // eslint-disable-next-line no-unused-vars
82
     const { jwt, type, ...actionPayload } = action;
169
     const { jwt, type, ...actionPayload } = action;
83
 
170
 
84
     if (jwt && !Object.keys(actionPayload).length) {
171
     if (jwt && !Object.keys(actionPayload).length) {
85
         const {
172
         const {
86
             enableUserRolesBasedOnToken
173
             enableUserRolesBasedOnToken
87
-        } = getState()['features/base/config'];
174
+        } = store.getState()['features/base/config'];
88
 
175
 
89
         action.isGuest = !enableUserRolesBasedOnToken;
176
         action.isGuest = !enableUserRolesBasedOnToken;
90
 
177
 
103
         }
190
         }
104
     }
191
     }
105
 
192
 
106
-    return next(action);
193
+    return _maybeSetCallOverlayVisible(store, next, action);
107
 }
194
 }

+ 13
- 2
react/features/jwt/reducer.js 查看文件

1
-import { equals, ReducerRegistry } from '../base/redux';
1
+import { equals, set, ReducerRegistry } from '../base/redux';
2
 
2
 
3
-import { SET_JWT } from './actionTypes';
3
+import { SET_CALL_OVERLAY_VISIBLE, SET_JWT } from './actionTypes';
4
 
4
 
5
 /**
5
 /**
6
  * The initial redux state of the feature jwt.
6
  * The initial redux state of the feature jwt.
11
  * }}
11
  * }}
12
  */
12
  */
13
 const _INITIAL_STATE = {
13
 const _INITIAL_STATE = {
14
+    /**
15
+     * The indicator which determines whether (the) {@code CallOverlay} is
16
+     * visible.
17
+     *
18
+     * @type {boolean|undefined}
19
+     */
20
+    callOverlayVisible: undefined,
21
+
14
     /**
22
     /**
15
      * The indicator which determines whether the local participant is a guest
23
      * The indicator which determines whether the local participant is a guest
16
      * in the conference.
24
      * in the conference.
31
  */
39
  */
32
 ReducerRegistry.register('features/jwt', (state = _INITIAL_STATE, action) => {
40
 ReducerRegistry.register('features/jwt', (state = _INITIAL_STATE, action) => {
33
     switch (action.type) {
41
     switch (action.type) {
42
+    case SET_CALL_OVERLAY_VISIBLE:
43
+        return set(state, 'callOverlayVisible', action.callOverlayVisible);
44
+
34
     case SET_JWT: {
45
     case SET_JWT: {
35
         // eslint-disable-next-line no-unused-vars
46
         // eslint-disable-next-line no-unused-vars
36
         const { type, ...payload } = action;
47
         const { type, ...payload } = action;

+ 37
- 14
react/features/overlay/components/OverlayContainer.js 查看文件

1
 import React, { Component } from 'react';
1
 import React, { Component } from 'react';
2
 import { connect } from 'react-redux';
2
 import { connect } from 'react-redux';
3
 
3
 
4
+import { CallOverlay } from '../../jwt';
5
+
4
 import PageReloadFilmstripOnlyOverlay from './PageReloadFilmstripOnlyOverlay';
6
 import PageReloadFilmstripOnlyOverlay from './PageReloadFilmstripOnlyOverlay';
5
 import PageReloadOverlay from './PageReloadOverlay';
7
 import PageReloadOverlay from './PageReloadOverlay';
6
 import SuspendedFilmstripOnlyOverlay from './SuspendedFilmstripOnlyOverlay';
8
 import SuspendedFilmstripOnlyOverlay from './SuspendedFilmstripOnlyOverlay';
33
          */
35
          */
34
         _browser: React.PropTypes.string,
36
         _browser: React.PropTypes.string,
35
 
37
 
38
+        /**
39
+         * The indicator which determines whether the {@link CallOverlay} is
40
+         * displayed/visible.
41
+         *
42
+         * @private
43
+         * @type {boolean}
44
+         */
45
+        _callOverlayVisible: React.PropTypes.bool,
46
+
36
         /**
47
         /**
37
          * The indicator which determines whether the status of the
48
          * The indicator which determines whether the status of the
38
          * JitsiConnection object has been "established" or not.
49
          * JitsiConnection object has been "established" or not.
172
             props = {
183
             props = {
173
                 browser: this.props._browser
184
                 browser: this.props._browser
174
             };
185
             };
186
+        } else if (this.props._callOverlayVisible) {
187
+            overlayComponent = CallOverlay;
175
         }
188
         }
176
 
189
 
177
         return (
190
         return (
182
 }
195
 }
183
 
196
 
184
 /**
197
 /**
185
- * Maps (parts of) the Redux state to the associated OverlayContainer's props.
198
+ * Maps (parts of) the redux state to the associated OverlayContainer's props.
186
  *
199
  *
187
- * @param {Object} state - The Redux state.
200
+ * @param {Object} state - The redux state.
188
  * @returns {{
201
  * @returns {{
189
  *     _browser: string,
202
  *     _browser: string,
190
- *     _connectionEstablished: bool,
191
- *     _haveToReload: bool,
192
- *     _isNetworkFailure: bool,
193
- *     _isMediaPermissionPromptVisible: bool,
203
+ *     _callOverlayVisible: boolean,
204
+ *     _connectionEstablished: boolean,
205
+ *     _haveToReload: boolean,
206
+ *     _isNetworkFailure: boolean,
207
+ *     _isMediaPermissionPromptVisible: boolean,
194
  *     _reason: string,
208
  *     _reason: string,
195
- *     _suspendDetected: bool
209
+ *     _suspendDetected: boolean
196
  * }}
210
  * }}
197
  * @private
211
  * @private
198
  */
212
  */
203
         /**
217
         /**
204
          * The browser which is used currently.
218
          * The browser which is used currently.
205
          *
219
          *
206
-         * NOTE: Used by UserMediaPermissionsOverlay only.
220
+         * NOTE: Used by {@link UserMediaPermissionsOverlay} only.
207
          *
221
          *
208
          * @private
222
          * @private
209
          * @type {string}
223
          * @type {string}
210
          */
224
          */
211
         _browser: stateFeaturesOverlay.browser,
225
         _browser: stateFeaturesOverlay.browser,
212
 
226
 
227
+        /**
228
+         * The indicator which determines whether the {@link CallOverlay} is
229
+         * displayed/visible.
230
+         *
231
+         * @private
232
+         * @type {boolean}
233
+         */
234
+        _callOverlayVisible: Boolean(state['features/jwt'].callOverlayVisible),
235
+
213
         /**
236
         /**
214
          * The indicator which determines whether the status of the
237
          * The indicator which determines whether the status of the
215
          * JitsiConnection object has been "established" or not.
238
          * JitsiConnection object has been "established" or not.
216
          *
239
          *
217
-         * NOTE: Used by PageReloadOverlay only.
240
+         * NOTE: Used by {@link PageReloadOverlay} only.
218
          *
241
          *
219
          * @private
242
          * @private
220
          * @type {boolean}
243
          * @type {boolean}
225
          * The indicator which determines whether a critical error for reload
248
          * The indicator which determines whether a critical error for reload
226
          * has been received.
249
          * has been received.
227
          *
250
          *
228
-         * NOTE: Used by PageReloadOverlay only.
251
+         * NOTE: Used by {@link PageReloadOverlay} only.
229
          *
252
          *
230
          * @private
253
          * @private
231
          * @type {boolean}
254
          * @type {boolean}
236
          * The indicator which determines whether the GUM permissions prompt is
259
          * The indicator which determines whether the GUM permissions prompt is
237
          * displayed or not.
260
          * displayed or not.
238
          *
261
          *
239
-         * NOTE: Used by UserMediaPermissionsOverlay only.
262
+         * NOTE: Used by {@link UserMediaPermissionsOverlay} only.
240
          *
263
          *
241
          * @private
264
          * @private
242
          * @type {boolean}
265
          * @type {boolean}
248
          * The indicator which determines whether the reload was caused by
271
          * The indicator which determines whether the reload was caused by
249
          * network failure.
272
          * network failure.
250
          *
273
          *
251
-         * NOTE: Used by PageReloadOverlay only.
274
+         * NOTE: Used by {@link PageReloadOverlay} only.
252
          *
275
          *
253
          * @private
276
          * @private
254
          * @type {boolean}
277
          * @type {boolean}
258
         /**
281
         /**
259
          * The reason for the error that will cause the reload.
282
          * The reason for the error that will cause the reload.
260
          *
283
          *
261
-         * NOTE: Used by PageReloadOverlay only.
284
+         * NOTE: Used by {@link PageReloadOverlay} only.
262
          *
285
          *
263
          * @private
286
          * @private
264
          * @type {string}
287
          * @type {string}
269
          * The indicator which determines whether the GUM permissions prompt is
292
          * The indicator which determines whether the GUM permissions prompt is
270
          * displayed or not.
293
          * displayed or not.
271
          *
294
          *
272
-         * NOTE: Used by SuspendedOverlay only.
295
+         * NOTE: Used by {@link SuspendedOverlay} only.
273
          *
296
          *
274
          * @private
297
          * @private
275
          * @type {string}
298
          * @type {string}

+ 1
- 1
react/features/toolbox/actions.web.js 查看文件

178
 
178
 
179
         if (!force
179
         if (!force
180
                 && (hovered
180
                 && (hovered
181
-                    || APP.UI.isRingOverlayVisible()
181
+                    || state['features/jwt'].callOverlayVisible
182
                     || SideContainerToggler.isVisible())) {
182
                     || SideContainerToggler.isVisible())) {
183
             dispatch(
183
             dispatch(
184
                 setToolboxTimeout(
184
                 setToolboxTimeout(

Loading…
取消
儲存