瀏覽代碼

fix(gum) add event handling for SLOW_GET_USER_MEDIA

Show an overlay with a spinner when slow gUM is fired
j8
Tudor-Ovidiu Avram 4 年之前
父節點
當前提交
f50fd7b7bd

+ 28
- 13
conference.js 查看文件

115
     submitFeedback
115
     submitFeedback
116
 } from './react/features/feedback';
116
 } from './react/features/feedback';
117
 import { showNotification } from './react/features/notifications';
117
 import { showNotification } from './react/features/notifications';
118
-import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
118
+import { mediaPermissionPromptVisibilityChanged, toggleSlowGUMOverlay } from './react/features/overlay';
119
 import { suspendDetected } from './react/features/power-monitor';
119
 import { suspendDetected } from './react/features/power-monitor';
120
 import {
120
 import {
121
     initPrejoin,
121
     initPrejoin,
502
             );
502
             );
503
         }
503
         }
504
 
504
 
505
+        JitsiMeetJS.mediaDevices.addEventListener(
506
+            JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA,
507
+            () => APP.store.dispatch(toggleSlowGUMOverlay(true))
508
+        );
509
+
505
         let tryCreateLocalTracks;
510
         let tryCreateLocalTracks;
506
 
511
 
507
         // On Electron there is no permission prompt for granting permissions. That's why we don't need to
512
         // On Electron there is no permission prompt for granting permissions. That's why we don't need to
519
 
524
 
520
                     return createLocalTracksF({
525
                     return createLocalTracksF({
521
                         devices: [ 'audio' ],
526
                         devices: [ 'audio' ],
522
-                        timeout
523
-                    }, true)
527
+                        timeout,
528
+                        firePermissionPromptIsShownEvent: true,
529
+                        fireSlowPromiseEvent: true
530
+                    })
524
                         .then(([ audioStream ]) =>
531
                         .then(([ audioStream ]) =>
525
                             [ desktopStream, audioStream ])
532
                             [ desktopStream, audioStream ])
526
                         .catch(error => {
533
                         .catch(error => {
536
                     return requestedAudio
543
                     return requestedAudio
537
                         ? createLocalTracksF({
544
                         ? createLocalTracksF({
538
                             devices: [ 'audio' ],
545
                             devices: [ 'audio' ],
539
-                            timeout
540
-                        }, true)
546
+                            timeout,
547
+                            firePermissionPromptIsShownEvent: true,
548
+                            fireSlowPromiseEvent: true
549
+                        })
541
                         : [];
550
                         : [];
542
                 })
551
                 })
543
                 .catch(error => {
552
                 .catch(error => {
551
         } else {
560
         } else {
552
             tryCreateLocalTracks = createLocalTracksF({
561
             tryCreateLocalTracks = createLocalTracksF({
553
                 devices: initialDevices,
562
                 devices: initialDevices,
554
-                timeout
555
-            }, true)
563
+                timeout,
564
+                firePermissionPromptIsShownEvent: true,
565
+                fireSlowPromiseEvent: true
566
+            })
556
                 .catch(err => {
567
                 .catch(err => {
557
                     if (requestedAudio && requestedVideo) {
568
                     if (requestedAudio && requestedVideo) {
558
 
569
 
574
                         return (
585
                         return (
575
                             createLocalTracksF({
586
                             createLocalTracksF({
576
                                 devices: [ 'audio' ],
587
                                 devices: [ 'audio' ],
577
-                                timeout
578
-                            }, true));
588
+                                timeout,
589
+                                firePermissionPromptIsShownEvent: true,
590
+                                fireSlowPromiseEvent: true
591
+                            }));
579
                     } else if (requestedAudio && !requestedVideo) {
592
                     } else if (requestedAudio && !requestedVideo) {
580
                         errors.audioOnlyError = err;
593
                         errors.audioOnlyError = err;
581
 
594
 
598
                     return requestedVideo
611
                     return requestedVideo
599
                         ? createLocalTracksF({
612
                         ? createLocalTracksF({
600
                             devices: [ 'video' ],
613
                             devices: [ 'video' ],
601
-                            timeout
602
-                        }, true)
614
+                            firePermissionPromptIsShownEvent: true,
615
+                            fireSlowPromiseEvent: true
616
+                        })
603
                         : [];
617
                         : [];
604
                 })
618
                 })
605
                 .catch(err => {
619
                 .catch(err => {
619
         // the user inputs their credentials, but the dialog would be
633
         // the user inputs their credentials, but the dialog would be
620
         // overshadowed by the overlay.
634
         // overshadowed by the overlay.
621
         tryCreateLocalTracks.then(tracks => {
635
         tryCreateLocalTracks.then(tracks => {
636
+            APP.store.dispatch(toggleSlowGUMOverlay(false));
622
             APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
637
             APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
623
 
638
 
624
             return tracks;
639
             return tracks;
882
                 showUI && APP.store.dispatch(notifyMicError(error));
897
                 showUI && APP.store.dispatch(notifyMicError(error));
883
             };
898
             };
884
 
899
 
885
-            createLocalTracksF({ devices: [ 'audio' ] }, false)
900
+            createLocalTracksF({ devices: [ 'audio' ] })
886
                 .then(([ audioTrack ]) => audioTrack)
901
                 .then(([ audioTrack ]) => audioTrack)
887
                 .catch(error => {
902
                 .catch(error => {
888
                     maybeShowErrorDialog(error);
903
                     maybeShowErrorDialog(error);
996
             //
1011
             //
997
             // FIXME when local track creation is moved to react/redux
1012
             // FIXME when local track creation is moved to react/redux
998
             // it should take care of the use case described above
1013
             // it should take care of the use case described above
999
-            createLocalTracksF({ devices: [ 'video' ] }, false)
1014
+            createLocalTracksF({ devices: [ 'video' ] })
1000
                 .then(([ videoTrack ]) => videoTrack)
1015
                 .then(([ videoTrack ]) => videoTrack)
1001
                 .catch(error => {
1016
                 .catch(error => {
1002
                     // FIXME should send some feedback to the API on error ?
1017
                     // FIXME should send some feedback to the API on error ?

+ 8
- 0
css/overlay/_overlay.scss 查看文件

33
         bottom: 24px;
33
         bottom: 24px;
34
         width: 100%;
34
         width: 100%;
35
     }
35
     }
36
+
37
+    &__spinner-container {
38
+        display: flex;
39
+        width: 100%;
40
+        height: 100%;
41
+        justify-content: center;
42
+        align-items: center;
43
+    }
36
 }
44
 }

+ 2
- 2
package-lock.json 查看文件

10343
       }
10343
       }
10344
     },
10344
     },
10345
     "lib-jitsi-meet": {
10345
     "lib-jitsi-meet": {
10346
-      "version": "github:jitsi/lib-jitsi-meet#7f919faaccb268ef307d619992260919a6535e95",
10347
-      "from": "github:jitsi/lib-jitsi-meet#7f919faaccb268ef307d619992260919a6535e95",
10346
+      "version": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
10347
+      "from": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
10348
       "requires": {
10348
       "requires": {
10349
         "@jitsi/js-utils": "1.0.2",
10349
         "@jitsi/js-utils": "1.0.2",
10350
         "@jitsi/sdp-interop": "1.0.3",
10350
         "@jitsi/sdp-interop": "1.0.3",

+ 1
- 1
package.json 查看文件

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#7f919faaccb268ef307d619992260919a6535e95",
59
+    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
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",

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

127
                             options.facingMode || CAMERA_FACING_MODE.USER,
127
                             options.facingMode || CAMERA_FACING_MODE.USER,
128
                         micDeviceId: options.micDeviceId
128
                         micDeviceId: options.micDeviceId
129
                     },
129
                     },
130
-                    /* firePermissionPromptIsShownEvent */ false,
131
                     store)
130
                     store)
132
                 .then(
131
                 .then(
133
                     localTracks => {
132
                     localTracks => {

+ 27
- 8
react/features/base/tracks/functions.js 查看文件

63
  * @param {string|null} [options.micDeviceId] - Microphone device id or
63
  * @param {string|null} [options.micDeviceId] - Microphone device id or
64
  * {@code undefined} to use app's settings.
64
  * {@code undefined} to use app's settings.
65
  * @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
65
  * @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
66
- * @param {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
66
+ * @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
67
  * should check for a {@code getUserMedia} permission prompt and fire a
67
  * should check for a {@code getUserMedia} permission prompt and fire a
68
  * corresponding event.
68
  * corresponding event.
69
+ * @param {boolean} [options.fireSlowPromiseEvent] - Whether lib-jitsi-meet
70
+ * should check for a slow {@code getUserMedia} request and fire a
71
+ * corresponding event.
69
  * @param {Object} store - The redux store in the context of which the function
72
  * @param {Object} store - The redux store in the context of which the function
70
  * is to execute and from which state such as {@code config} is to be retrieved.
73
  * is to execute and from which state such as {@code config} is to be retrieved.
71
  * @returns {Promise<JitsiLocalTrack[]>}
74
  * @returns {Promise<JitsiLocalTrack[]>}
72
  */
75
  */
73
-export function createLocalTracksF(options = {}, firePermissionPromptIsShownEvent, store) {
76
+export function createLocalTracksF(options = {}, store) {
74
     let { cameraDeviceId, micDeviceId } = options;
77
     let { cameraDeviceId, micDeviceId } = options;
75
-    const { desktopSharingSourceDevice, desktopSharingSources, timeout } = options;
78
+    const {
79
+        desktopSharingSourceDevice,
80
+        desktopSharingSources,
81
+        firePermissionPromptIsShownEvent,
82
+        fireSlowPromiseEvent,
83
+        timeout
84
+    } = options;
76
 
85
 
77
     if (typeof APP !== 'undefined') {
86
     if (typeof APP !== 'undefined') {
78
         // TODO The app's settings should go in the redux store and then the
87
         // TODO The app's settings should go in the redux store and then the
114
                     devices: options.devices.slice(0),
123
                     devices: options.devices.slice(0),
115
                     effects,
124
                     effects,
116
                     firefox_fake_device, // eslint-disable-line camelcase
125
                     firefox_fake_device, // eslint-disable-line camelcase
126
+                    firePermissionPromptIsShownEvent,
127
+                    fireSlowPromiseEvent,
117
                     micDeviceId,
128
                     micDeviceId,
118
                     resolution,
129
                     resolution,
119
                     timeout
130
                     timeout
120
-                },
121
-                firePermissionPromptIsShownEvent)
131
+                })
122
             .catch(err => {
132
             .catch(err => {
123
                 logger.error('Failed to create local tracks', options.devices, err);
133
                 logger.error('Failed to create local tracks', options.devices, err);
124
 
134
 
161
         // Resolve with no tracks
171
         // Resolve with no tracks
162
         tryCreateLocalTracks = Promise.resolve([]);
172
         tryCreateLocalTracks = Promise.resolve([]);
163
     } else {
173
     } else {
164
-        tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
174
+        tryCreateLocalTracks = createLocalTracksF({
175
+            devices: initialDevices,
176
+            firePermissionPromptIsShownEvent: true
177
+        })
165
                 .catch(err => {
178
                 .catch(err => {
166
                     if (requestedAudio && requestedVideo) {
179
                     if (requestedAudio && requestedVideo) {
167
 
180
 
169
                         errors.audioAndVideoError = err;
182
                         errors.audioAndVideoError = err;
170
 
183
 
171
                         return (
184
                         return (
172
-                            createLocalTracksF({ devices: [ 'audio' ] }, true));
185
+                            createLocalTracksF({
186
+                                devices: [ 'audio' ],
187
+                                firePermissionPromptIsShownEvent: true
188
+                            }));
173
                     } else if (requestedAudio && !requestedVideo) {
189
                     } else if (requestedAudio && !requestedVideo) {
174
                         errors.audioOnlyError = err;
190
                         errors.audioOnlyError = err;
175
 
191
 
190
 
206
 
191
                     // Try video only...
207
                     // Try video only...
192
                     return requestedVideo
208
                     return requestedVideo
193
-                        ? createLocalTracksF({ devices: [ 'video' ] }, true)
209
+                        ? createLocalTracksF({
210
+                            devices: [ 'video' ],
211
+                            firePermissionPromptIsShownEvent: true
212
+                        })
194
                         : [];
213
                         : [];
195
                 })
214
                 })
196
                 .catch(err => {
215
                 .catch(err => {

+ 11
- 0
react/features/overlay/actionTypes.js 查看文件

14
 export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
14
 export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
15
     = 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
15
     = 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
16
 
16
 
17
+/**
18
+ * The type of the Redux action which signals that the overlay for slow gUM is visible or not.
19
+ *
20
+ * {
21
+ *     type: TOGGLE_SLOW_GUM_OVERLAY,
22
+ *     isVisible: {boolean},
23
+ * }
24
+ * @public
25
+ */
26
+export const TOGGLE_SLOW_GUM_OVERLAY = 'TOGGLE_SLOW_GUM_OVERLAY';
27
+
17
 /**
28
 /**
18
  * Adjust the state of the fatal error which shows/hides the reload screen. See
29
  * Adjust the state of the fatal error which shows/hides the reload screen. See
19
  * action methods's description for more info about each of the fields.
30
  * action methods's description for more info about each of the fields.

+ 20
- 1
react/features/overlay/actions.js 查看文件

2
 
2
 
3
 import {
3
 import {
4
     MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
4
     MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
5
-    SET_FATAL_ERROR
5
+    SET_FATAL_ERROR,
6
+    TOGGLE_SLOW_GUM_OVERLAY
6
 } from './actionTypes';
7
 } from './actionTypes';
7
 
8
 
8
 /**
9
 /**
26
     };
27
     };
27
 }
28
 }
28
 
29
 
30
+/**
31
+ * Signals that the prompt for media permission is visible or not.
32
+ *
33
+ * @param {boolean} isVisible - If the value is true - the prompt for media
34
+ * permission is visible otherwise the value is false/undefined.
35
+ * @public
36
+ * @returns {{
37
+*     type: SLOW_GET_USER_MEDIA_OVERLAY,
38
+*     isVisible: {boolean}
39
+* }}
40
+*/
41
+export function toggleSlowGUMOverlay(isVisible: boolean) {
42
+    return {
43
+        type: TOGGLE_SLOW_GUM_OVERLAY,
44
+        isVisible
45
+    };
46
+}
47
+
29
 /**
48
 /**
30
  * The action indicates that an unrecoverable error has occurred and the reload
49
  * The action indicates that an unrecoverable error has occurred and the reload
31
  * screen will be displayed or hidden.
50
  * screen will be displayed or hidden.

+ 33
- 0
react/features/overlay/components/web/AbstractSlowGUMOverlay.js 查看文件

1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+/**
6
+ * The type of the React {@code Component} props of
7
+ * {@link AbstractSlowGUMOverlay}.
8
+ */
9
+type Props = {
10
+
11
+    /**
12
+     * The function to translate human-readable text.
13
+     */
14
+    t: Function
15
+};
16
+
17
+/**
18
+ * Implements a React {@link Component} for slow gUM overlay. Shown when
19
+ * a slow gUM promise resolution is detected
20
+ */
21
+export default class AbstractSlowGUMOverlay extends Component<Props> {
22
+    /**
23
+     * Determines whether this overlay needs to be rendered (according to a
24
+     * specific redux state). Called by {@link OverlayContainer}.
25
+     *
26
+     * @param {Object} state - The redux state.
27
+     * @returns {boolean} - If this overlay needs to be rendered, {@code true};
28
+     * {@code false}, otherwise.
29
+     */
30
+    static needsRender(state: Object) {
31
+        return state['features/overlay'].isSlowGUMOverlayVisible;
32
+    }
33
+}

+ 36
- 0
react/features/overlay/components/web/SlowGUMOverlay.js 查看文件

1
+// @flow
2
+import Spinner from '@atlaskit/spinner';
3
+import React from 'react';
4
+
5
+import { translate } from '../../../base/i18n';
6
+
7
+import AbstractSlowGUMOverlay from './AbstractSlowGUMOverlay';
8
+import OverlayFrame from './OverlayFrame';
9
+
10
+/**
11
+ * Implements a React {@link Component} for slow gUM overlay. Shown when
12
+ * a slow gUM promise resolution is detected
13
+ */
14
+class SlowGUMOverlay extends AbstractSlowGUMOverlay {
15
+    /**
16
+     * Implements React's {@link Component#render()}.
17
+     *
18
+     * @inheritdoc
19
+     * @returns {ReactElement}
20
+     */
21
+    render() {
22
+        // const { t } = this.props;
23
+
24
+        return (
25
+            <OverlayFrame>
26
+                <div className = { 'overlay__spinner-container' }>
27
+                    <Spinner
28
+                        invertColor = { true }
29
+                        size = { 'large' } />
30
+                </div>
31
+            </OverlayFrame>
32
+        );
33
+    }
34
+}
35
+
36
+export default translate(SlowGUMOverlay);

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

5
 export { default as PageReloadOverlay } from './PageReloadOverlay';
5
 export { default as PageReloadOverlay } from './PageReloadOverlay';
6
 export { default as SuspendedOverlay } from './SuspendedOverlay';
6
 export { default as SuspendedOverlay } from './SuspendedOverlay';
7
 export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';
7
 export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';
8
+export { default as SlowGUMOverlay } from './SlowGUMOverlay';

+ 3
- 1
react/features/overlay/overlays.web.js 查看文件

2
 
2
 
3
 import {
3
 import {
4
     PageReloadOverlay,
4
     PageReloadOverlay,
5
+    SlowGUMOverlay,
5
     SuspendedOverlay,
6
     SuspendedOverlay,
6
     UserMediaPermissionsOverlay
7
     UserMediaPermissionsOverlay
7
 } from './components/web';
8
 } from './components/web';
17
     return [
18
     return [
18
         PageReloadOverlay,
19
         PageReloadOverlay,
19
         SuspendedOverlay,
20
         SuspendedOverlay,
20
-        UserMediaPermissionsOverlay
21
+        UserMediaPermissionsOverlay,
22
+        SlowGUMOverlay
21
     ];
23
     ];
22
 }
24
 }

+ 22
- 1
react/features/overlay/reducer.js 查看文件

5
 
5
 
6
 import {
6
 import {
7
     MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
7
     MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
8
-    SET_FATAL_ERROR
8
+    SET_FATAL_ERROR,
9
+    TOGGLE_SLOW_GUM_OVERLAY
9
 } from './actionTypes';
10
 } from './actionTypes';
10
 
11
 
11
 /**
12
 /**
28
     case SET_FATAL_ERROR:
29
     case SET_FATAL_ERROR:
29
         return _setFatalError(state, action);
30
         return _setFatalError(state, action);
30
 
31
 
32
+    case TOGGLE_SLOW_GUM_OVERLAY:
33
+        return _toggleSlowGUMOverlay(state, action);
31
     }
34
     }
32
 
35
 
33
     return state;
36
     return state;
52
     });
55
     });
53
 }
56
 }
54
 
57
 
58
+/**
59
+ * Reduces a specific redux action TOGGLE_SLOW_GUM_OVERLAY of
60
+ * the feature overlay.
61
+ *
62
+ * @param {Object} state - The redux state of the feature overlay.
63
+ * @param {Action} action - The redux action to reduce.
64
+ * @private
65
+ * @returns {Object} The new state of the feature overlay after the reduction of
66
+ * the specified action.
67
+ */
68
+function _toggleSlowGUMOverlay(
69
+        state,
70
+        { isVisible }) {
71
+    return assign(state, {
72
+        isSlowGUMOverlayVisible: isVisible
73
+    });
74
+}
75
+
55
 /**
76
 /**
56
  * Sets the {@code LoadConfigOverlay} overlay visible or not.
77
  * Sets the {@code LoadConfigOverlay} overlay visible or not.
57
  *
78
  *

Loading…
取消
儲存