Bladeren bron

fix: Add GUM timeout & improve device permissions

master
Hristo Terezov 4 jaren geleden
bovenliggende
commit
a6c6cd6c56

+ 34
- 5
conference.js Bestand weergeven

504
 
504
 
505
         let tryCreateLocalTracks;
505
         let tryCreateLocalTracks;
506
 
506
 
507
+        const timeout = browser.isElectron() ? 15000 : 60000;
508
+
507
         // FIXME is there any simpler way to rewrite this spaghetti below ?
509
         // FIXME is there any simpler way to rewrite this spaghetti below ?
508
         if (options.startScreenSharing) {
510
         if (options.startScreenSharing) {
509
             tryCreateLocalTracks = this._createDesktopTrack()
511
             tryCreateLocalTracks = this._createDesktopTrack()
512
                         return [ desktopStream ];
514
                         return [ desktopStream ];
513
                     }
515
                     }
514
 
516
 
515
-                    return createLocalTracksF({ devices: [ 'audio' ] }, true)
517
+                    return createLocalTracksF({
518
+                        devices: [ 'audio' ],
519
+                        timeout
520
+                    }, true)
516
                         .then(([ audioStream ]) =>
521
                         .then(([ audioStream ]) =>
517
                             [ desktopStream, audioStream ])
522
                             [ desktopStream, audioStream ])
518
                         .catch(error => {
523
                         .catch(error => {
526
                     errors.screenSharingError = error;
531
                     errors.screenSharingError = error;
527
 
532
 
528
                     return requestedAudio
533
                     return requestedAudio
529
-                        ? createLocalTracksF({ devices: [ 'audio' ] }, true)
534
+                        ? createLocalTracksF({
535
+                            devices: [ 'audio' ],
536
+                            timeout
537
+                        }, true)
530
                         : [];
538
                         : [];
531
                 })
539
                 })
532
                 .catch(error => {
540
                 .catch(error => {
538
             // Resolve with no tracks
546
             // Resolve with no tracks
539
             tryCreateLocalTracks = Promise.resolve([]);
547
             tryCreateLocalTracks = Promise.resolve([]);
540
         } else {
548
         } else {
541
-            tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
549
+            tryCreateLocalTracks = createLocalTracksF({
550
+                devices: initialDevices,
551
+                timeout
552
+            }, true)
542
                 .catch(err => {
553
                 .catch(err => {
543
                     if (requestedAudio && requestedVideo) {
554
                     if (requestedAudio && requestedVideo) {
544
 
555
 
545
                         // Try audio only...
556
                         // Try audio only...
546
                         errors.audioAndVideoError = err;
557
                         errors.audioAndVideoError = err;
547
 
558
 
559
+                        if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
560
+                            // In this case we expect that the permission prompt is still visible. There is no point of
561
+                            // executing GUM with different source. Also at the time of writting the following
562
+                            // inconsistency have been noticed in some browsers - if the permissions prompt is visible
563
+                            // and another GUM is executed the prompt does not change its content but if the user
564
+                            // clicks allow the user action isassociated with the latest GUM call.
565
+                            errors.audioOnlyError = err;
566
+                            errors.videoOnlyError = err;
567
+
568
+                            return [];
569
+                        }
570
+
548
                         return (
571
                         return (
549
-                            createLocalTracksF({ devices: [ 'audio' ] }, true));
572
+                            createLocalTracksF({
573
+                                devices: [ 'audio' ],
574
+                                timeout
575
+                            }, true));
550
                     } else if (requestedAudio && !requestedVideo) {
576
                     } else if (requestedAudio && !requestedVideo) {
551
                         errors.audioOnlyError = err;
577
                         errors.audioOnlyError = err;
552
 
578
 
567
 
593
 
568
                     // Try video only...
594
                     // Try video only...
569
                     return requestedVideo
595
                     return requestedVideo
570
-                        ? createLocalTracksF({ devices: [ 'video' ] }, true)
596
+                        ? createLocalTracksF({
597
+                            devices: [ 'video' ],
598
+                            timeout
599
+                        }, true)
571
                         : [];
600
                         : [];
572
                 })
601
                 })
573
                 .catch(err => {
602
                 .catch(err => {

+ 3
- 1
lang/main.json Bestand weergeven

180
         "cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to reload the application.",
180
         "cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to reload the application.",
181
         "cameraNotSendingDataTitle": "Unable to access camera",
181
         "cameraNotSendingDataTitle": "Unable to access camera",
182
         "cameraPermissionDeniedError": "You have not granted permission to use your camera. You can still join the conference but others won't see you. Use the camera button in the address bar to fix this.",
182
         "cameraPermissionDeniedError": "You have not granted permission to use your camera. You can still join the conference but others won't see you. Use the camera button in the address bar to fix this.",
183
+        "cameraTimeoutError": "Could not start video source. Timeout occured!",
183
         "cameraUnknownError": "Cannot use camera for an unknown reason.",
184
         "cameraUnknownError": "Cannot use camera for an unknown reason.",
184
         "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
185
         "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
185
         "Cancel": "Cancel",
186
         "Cancel": "Cancel",
233
         "micNotSendingData": "Go to your computer's settings to unmute your mic and adjust its level",
234
         "micNotSendingData": "Go to your computer's settings to unmute your mic and adjust its level",
234
         "micNotSendingDataTitle": "Your mic is muted by your system settings",
235
         "micNotSendingDataTitle": "Your mic is muted by your system settings",
235
         "micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
236
         "micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
237
+        "micTimeoutError": "Could not start audio source. Timeout occured!",
236
         "micUnknownError": "Cannot use microphone for an unknown reason.",
238
         "micUnknownError": "Cannot use microphone for an unknown reason.",
237
         "muteEveryoneElseDialog": "Once muted, you won't be able to unmute them, but they can unmute themselves at any time.",
239
         "muteEveryoneElseDialog": "Once muted, you won't be able to unmute them, but they can unmute themselves at any time.",
238
         "muteEveryoneElseTitle": "Mute everyone except {{whom}}?",
240
         "muteEveryoneElseTitle": "Mute everyone except {{whom}}?",
810
         "androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
812
         "androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
811
         "chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
813
         "chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
812
         "edgeGrantPermissions": "Select <b><i>Yes</i></b> when your browser asks for permissions.",
814
         "edgeGrantPermissions": "Select <b><i>Yes</i></b> when your browser asks for permissions.",
813
-        "electronGrantPermissions": "Please grant permissions to use your camera and microphone",
815
+        "electronGrantPermissions": "Trying to access your camera and microphone",
814
         "firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
816
         "firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
815
         "iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
817
         "iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
816
         "nwjsGrantPermissions": "Please grant permissions to use your camera and microphone",
818
         "nwjsGrantPermissions": "Please grant permissions to use your camera and microphone",

+ 10
- 0
react/features/base/devices/actionTypes.js Bestand weergeven

84
  * }
84
  * }
85
  */
85
  */
86
 export const CHECK_AND_NOTIFY_FOR_NEW_DEVICE = 'CHECK_AND_NOTIFY_FOR_NEW_DEVICE';
86
 export const CHECK_AND_NOTIFY_FOR_NEW_DEVICE = 'CHECK_AND_NOTIFY_FOR_NEW_DEVICE';
87
+
88
+/**
89
+ * The type of Redux action which signals that the device permissions have changed.
90
+ *
91
+ * {
92
+ *     type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE
93
+ *     permissions: Object
94
+ * }
95
+ */
96
+export const DEVICE_PERMISSIONS_CHANGED = 'DEVICE_PERMISSIONS_CHANGED';

+ 17
- 0
react/features/base/devices/actions.js Bestand weergeven

7
 import {
7
 import {
8
     ADD_PENDING_DEVICE_REQUEST,
8
     ADD_PENDING_DEVICE_REQUEST,
9
     CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
9
     CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
10
+    DEVICE_PERMISSIONS_CHANGED,
10
     NOTIFY_CAMERA_ERROR,
11
     NOTIFY_CAMERA_ERROR,
11
     NOTIFY_MIC_ERROR,
12
     NOTIFY_MIC_ERROR,
12
     REMOVE_PENDING_DEVICE_REQUESTS,
13
     REMOVE_PENDING_DEVICE_REQUESTS,
320
         oldDevices
321
         oldDevices
321
     };
322
     };
322
 }
323
 }
324
+
325
+/**
326
+ * Signals that the device permissions have changed.
327
+ *
328
+ * @param {Object} permissions - Object with the permissions.
329
+ * @returns {{
330
+ *      type: DEVICE_PERMISSIONS_CHANGED,
331
+ *      permissions: Object
332
+ * }}
333
+ */
334
+export function devicePermissionsChanged(permissions) {
335
+    return {
336
+        type: DEVICE_PERMISSIONS_CHANGED,
337
+        permissions
338
+    };
339
+}

+ 43
- 3
react/features/base/devices/middleware.js Bestand weergeven

5
 import { showNotification, showWarningNotification } from '../../notifications';
5
 import { showNotification, showWarningNotification } from '../../notifications';
6
 import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
6
 import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
7
 import { isPrejoinPageVisible } from '../../prejoin/functions';
7
 import { isPrejoinPageVisible } from '../../prejoin/functions';
8
-import { JitsiTrackErrors } from '../lib-jitsi-meet';
8
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
9
+import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
9
 import { MiddlewareRegistry } from '../redux';
10
 import { MiddlewareRegistry } from '../redux';
10
 import { updateSettings } from '../settings';
11
 import { updateSettings } from '../settings';
11
 
12
 
18
     UPDATE_DEVICE_LIST
19
     UPDATE_DEVICE_LIST
19
 } from './actionTypes';
20
 } from './actionTypes';
20
 import {
21
 import {
22
+    devicePermissionsChanged,
21
     removePendingDeviceRequests,
23
     removePendingDeviceRequests,
22
     setAudioInputDevice,
24
     setAudioInputDevice,
23
     setVideoInputDevice
25
     setVideoInputDevice
35
         [JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.micConstraintFailedError',
37
         [JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.micConstraintFailedError',
36
         [JitsiTrackErrors.GENERAL]: 'dialog.micUnknownError',
38
         [JitsiTrackErrors.GENERAL]: 'dialog.micUnknownError',
37
         [JitsiTrackErrors.NOT_FOUND]: 'dialog.micNotFoundError',
39
         [JitsiTrackErrors.NOT_FOUND]: 'dialog.micNotFoundError',
38
-        [JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError'
40
+        [JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError',
41
+        [JitsiTrackErrors.TIMEOUT]: 'dialog.micTimeoutError'
39
     },
42
     },
40
     camera: {
43
     camera: {
41
         [JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.cameraConstraintFailedError',
44
         [JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.cameraConstraintFailedError',
42
         [JitsiTrackErrors.GENERAL]: 'dialog.cameraUnknownError',
45
         [JitsiTrackErrors.GENERAL]: 'dialog.cameraUnknownError',
43
         [JitsiTrackErrors.NOT_FOUND]: 'dialog.cameraNotFoundError',
46
         [JitsiTrackErrors.NOT_FOUND]: 'dialog.cameraNotFoundError',
44
         [JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.cameraPermissionDeniedError',
47
         [JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.cameraPermissionDeniedError',
45
-        [JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError'
48
+        [JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError',
49
+        [JitsiTrackErrors.TIMEOUT]: 'dialog.cameraTimeoutError'
46
     }
50
     }
47
 };
51
 };
48
 
52
 
53
+
54
+/**
55
+ * A listener for device permissions changed reported from lib-jitsi-meet.
56
+ */
57
+let permissionsListener;
58
+
49
 /**
59
 /**
50
  * Logs the current device list.
60
  * Logs the current device list.
51
  *
61
  *
73
 // eslint-disable-next-line no-unused-vars
83
 // eslint-disable-next-line no-unused-vars
74
 MiddlewareRegistry.register(store => next => action => {
84
 MiddlewareRegistry.register(store => next => action => {
75
     switch (action.type) {
85
     switch (action.type) {
86
+    case APP_WILL_MOUNT: {
87
+        const _permissionsListener = permissions => {
88
+            store.dispatch(devicePermissionsChanged(permissions));
89
+        };
90
+        const { mediaDevices } = JitsiMeetJS;
91
+
92
+        permissionsListener = _permissionsListener;
93
+        mediaDevices.addEventListener(JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, permissionsListener);
94
+        Promise.all([
95
+            mediaDevices.isDevicePermissionGranted('audio'),
96
+            mediaDevices.isDevicePermissionGranted('video')
97
+        ])
98
+        .then(results => {
99
+            _permissionsListener({
100
+                audio: results[0],
101
+                video: results[1]
102
+            });
103
+        })
104
+        .catch(() => {
105
+            // Ignore errors.
106
+        });
107
+        break;
108
+    }
109
+    case APP_WILL_UNMOUNT:
110
+        if (typeof permissionsListener === 'function') {
111
+            JitsiMeetJS.mediaDevices.removeEventListener(
112
+                JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, permissionsListener);
113
+            permissionsListener = undefined;
114
+        }
115
+        break;
76
     case NOTIFY_CAMERA_ERROR: {
116
     case NOTIFY_CAMERA_ERROR: {
77
         if (typeof APP !== 'object' || !action.error) {
117
         if (typeof APP !== 'object' || !action.error) {
78
             break;
118
             break;

+ 12
- 1
react/features/base/devices/reducer.js Bestand weergeven

2
 
2
 
3
 import {
3
 import {
4
     ADD_PENDING_DEVICE_REQUEST,
4
     ADD_PENDING_DEVICE_REQUEST,
5
+    DEVICE_PERMISSIONS_CHANGED,
5
     REMOVE_PENDING_DEVICE_REQUESTS,
6
     REMOVE_PENDING_DEVICE_REQUESTS,
6
     SET_AUDIO_INPUT_DEVICE,
7
     SET_AUDIO_INPUT_DEVICE,
7
     SET_VIDEO_INPUT_DEVICE,
8
     SET_VIDEO_INPUT_DEVICE,
16
         audioOutput: [],
17
         audioOutput: [],
17
         videoInput: []
18
         videoInput: []
18
     },
19
     },
19
-    pendingRequests: []
20
+    pendingRequests: [],
21
+    permissions: {
22
+        audio: false,
23
+        video: false
24
+    }
20
 };
25
 };
21
 
26
 
22
 /**
27
 /**
68
 
73
 
69
             return state;
74
             return state;
70
         }
75
         }
76
+        case DEVICE_PERMISSIONS_CHANGED: {
77
+            return {
78
+                ...state,
79
+                permissions: action.permissions
80
+            };
81
+        }
71
         default:
82
         default:
72
             return state;
83
             return state;
73
         }
84
         }

+ 4
- 2
react/features/base/lib-jitsi-meet/functions.any.js Bestand weergeven

13
  * @param {string} type - The media type of track being created. Expected values
13
  * @param {string} type - The media type of track being created. Expected values
14
  * are "video" or "audio".
14
  * are "video" or "audio".
15
  * @param {string} deviceId - The id of the target media source.
15
  * @param {string} deviceId - The id of the target media source.
16
+ * @param {number} [timeout] - A timeout for the JitsiMeetJS.createLocalTracks function call.
16
  * @returns {Promise<JitsiLocalTrack>}
17
  * @returns {Promise<JitsiLocalTrack>}
17
  */
18
  */
18
-export function createLocalTrack(type: string, deviceId: string) {
19
+export function createLocalTrack(type: string, deviceId: string, timeout: ?number) {
19
     return (
20
     return (
20
         JitsiMeetJS.createLocalTracks({
21
         JitsiMeetJS.createLocalTracks({
21
             cameraDeviceId: deviceId,
22
             cameraDeviceId: deviceId,
24
             // eslint-disable-next-line camelcase
25
             // eslint-disable-next-line camelcase
25
             firefox_fake_device:
26
             firefox_fake_device:
26
                 window.config && window.config.firefox_fake_device,
27
                 window.config && window.config.firefox_fake_device,
27
-            micDeviceId: deviceId
28
+            micDeviceId: deviceId,
29
+            timeout
28
         })
30
         })
29
             .then(([ jitsiLocalTrack ]) => jitsiLocalTrack));
31
             .then(([ jitsiLocalTrack ]) => jitsiLocalTrack));
30
 }
32
 }

+ 6
- 4
react/features/base/tracks/functions.js Bestand weergeven

62
  * and/or 'video'.
62
  * and/or 'video'.
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 {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
66
  * @param {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
66
  * should check for a {@code getUserMedia} permission prompt and fire a
67
  * should check for a {@code getUserMedia} permission prompt and fire a
67
  * corresponding event.
68
  * corresponding event.
71
  */
72
  */
72
 export function createLocalTracksF(options = {}, firePermissionPromptIsShownEvent, store) {
73
 export function createLocalTracksF(options = {}, firePermissionPromptIsShownEvent, store) {
73
     let { cameraDeviceId, micDeviceId } = options;
74
     let { cameraDeviceId, micDeviceId } = options;
75
+    const { desktopSharingSourceDevice, desktopSharingSources, timeout } = options;
74
 
76
 
75
     if (typeof APP !== 'undefined') {
77
     if (typeof APP !== 'undefined') {
76
         // TODO The app's settings should go in the redux store and then the
78
         // TODO The app's settings should go in the redux store and then the
105
                     cameraDeviceId,
107
                     cameraDeviceId,
106
                     constraints,
108
                     constraints,
107
                     desktopSharingFrameRate,
109
                     desktopSharingFrameRate,
108
-                    desktopSharingSourceDevice:
109
-                        options.desktopSharingSourceDevice,
110
-                    desktopSharingSources: options.desktopSharingSources,
110
+                    desktopSharingSourceDevice,
111
+                    desktopSharingSources,
111
 
112
 
112
                     // Copy array to avoid mutations inside library.
113
                     // Copy array to avoid mutations inside library.
113
                     devices: options.devices.slice(0),
114
                     devices: options.devices.slice(0),
114
                     effects,
115
                     effects,
115
                     firefox_fake_device, // eslint-disable-line camelcase
116
                     firefox_fake_device, // eslint-disable-line camelcase
116
                     micDeviceId,
117
                     micDeviceId,
117
-                    resolution
118
+                    resolution,
119
+                    timeout
118
                 },
120
                 },
119
                 firePermissionPromptIsShownEvent)
121
                 firePermissionPromptIsShownEvent)
120
             .catch(err => {
122
             .catch(err => {

+ 14
- 36
react/features/device-selection/components/DeviceSelection.js Bestand weergeven

6
     type Props as AbstractDialogTabProps
6
     type Props as AbstractDialogTabProps
7
 } from '../../base/dialog/components/web/AbstractDialogTab';
7
 } from '../../base/dialog/components/web/AbstractDialogTab';
8
 import { translate } from '../../base/i18n/functions';
8
 import { translate } from '../../base/i18n/functions';
9
-import JitsiMeetJS from '../../base/lib-jitsi-meet/_';
10
 import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
9
 import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
11
 import logger from '../logger';
10
 import logger from '../logger';
12
 
11
 
41
      */
40
      */
42
     disableDeviceChange: boolean,
41
     disableDeviceChange: boolean,
43
 
42
 
43
+    /**
44
+     * Whether or not the audio permission was granted.
45
+     */
46
+    hasAudioPermission: boolean,
47
+
48
+    /**
49
+     * Whether or not the audio permission was granted.
50
+     */
51
+    hasVideoPermission: boolean,
52
+
44
     /**
53
     /**
45
      * If true, the audio meter will not display. Necessary for browsers or
54
      * If true, the audio meter will not display. Necessary for browsers or
46
      * configurations that do not support local stats to prevent a
55
      * configurations that do not support local stats to prevent a
87
  */
96
  */
88
 type State = {
97
 type State = {
89
 
98
 
90
-    /**
91
-     * Whether or not the audio permission was granted.
92
-     */
93
-    hasAudioPermission: boolean,
94
-
95
-    /**
96
-     * Whether or not the audio permission was granted.
97
-     */
98
-    hasVideoPermission: boolean,
99
-
100
     /**
99
     /**
101
      * The JitsiTrack to use for previewing audio input.
100
      * The JitsiTrack to use for previewing audio input.
102
      */
101
      */
141
         super(props);
140
         super(props);
142
 
141
 
143
         this.state = {
142
         this.state = {
144
-            hasAudioPermission: false,
145
-            hasVideoPermission: false,
146
             previewAudioTrack: null,
143
             previewAudioTrack: null,
147
             previewVideoTrack: null,
144
             previewVideoTrack: null,
148
             previewVideoTrackError: null
145
             previewVideoTrackError: null
170
      * video input previews.
167
      * video input previews.
171
      *
168
      *
172
      * @param {Object} prevProps - Previous props this component received.
169
      * @param {Object} prevProps - Previous props this component received.
173
-     * @param {Object} prevState - Previous state this component had.
174
      * @returns {void}
170
      * @returns {void}
175
      */
171
      */
176
-    componentDidUpdate(prevProps, prevState) {
177
-        const { previewAudioTrack, previewVideoTrack } = prevState;
178
-
179
-        if ((!previewAudioTrack && this.state.previewAudioTrack)
180
-                || (!previewVideoTrack && this.state.previewVideoTrack)) {
181
-            Promise.all([
182
-                JitsiMeetJS.mediaDevices.isDevicePermissionGranted('audio'),
183
-                JitsiMeetJS.mediaDevices.isDevicePermissionGranted('video')
184
-            ]).then(r => {
185
-                const [ hasAudioPermission, hasVideoPermission ] = r;
186
-
187
-                this.setState({
188
-                    hasAudioPermission,
189
-                    hasVideoPermission
190
-                });
191
-            });
192
-        }
193
-
172
+    componentDidUpdate(prevProps) {
194
         if (prevProps.selectedAudioInputId
173
         if (prevProps.selectedAudioInputId
195
             !== this.props.selectedAudioInputId) {
174
             !== this.props.selectedAudioInputId) {
196
             this._createAudioInputTrack(this.props.selectedAudioInputId);
175
             this._createAudioInputTrack(this.props.selectedAudioInputId);
258
      */
237
      */
259
     _createAudioInputTrack(deviceId) {
238
     _createAudioInputTrack(deviceId) {
260
         return this._disposeAudioInputPreview()
239
         return this._disposeAudioInputPreview()
261
-            .then(() => createLocalTrack('audio', deviceId))
240
+            .then(() => createLocalTrack('audio', deviceId, 5000))
262
             .then(jitsiLocalTrack => {
241
             .then(jitsiLocalTrack => {
263
                 if (this._unMounted) {
242
                 if (this._unMounted) {
264
                     jitsiLocalTrack.dispose();
243
                     jitsiLocalTrack.dispose();
286
      */
265
      */
287
     _createVideoInputTrack(deviceId) {
266
     _createVideoInputTrack(deviceId) {
288
         return this._disposeVideoInputPreview()
267
         return this._disposeVideoInputPreview()
289
-            .then(() => createLocalTrack('video', deviceId))
268
+            .then(() => createLocalTrack('video', deviceId, 5000))
290
             .then(jitsiLocalTrack => {
269
             .then(jitsiLocalTrack => {
291
                 if (!jitsiLocalTrack) {
270
                 if (!jitsiLocalTrack) {
292
                     return Promise.reject();
271
                     return Promise.reject();
360
      * @returns {Array<ReactElement>} DeviceSelector instances.
339
      * @returns {Array<ReactElement>} DeviceSelector instances.
361
      */
340
      */
362
     _renderSelectors() {
341
     _renderSelectors() {
363
-        const { availableDevices } = this.props;
364
-        const { hasAudioPermission, hasVideoPermission } = this.state;
342
+        const { availableDevices, hasAudioPermission, hasVideoPermission } = this.props;
365
 
343
 
366
         const configurations = [
344
         const configurations = [
367
             {
345
             {

+ 3
- 0
react/features/device-selection/functions.js Bestand weergeven

32
     const state = toState(stateful);
32
     const state = toState(stateful);
33
     const settings = state['features/base/settings'];
33
     const settings = state['features/base/settings'];
34
     const { conference } = state['features/base/conference'];
34
     const { conference } = state['features/base/conference'];
35
+    const { permissions } = state['features/base/devices'];
35
     let disableAudioInputChange = !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
36
     let disableAudioInputChange = !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
36
     let selectedAudioInputId = settings.micDeviceId;
37
     let selectedAudioInputId = settings.micDeviceId;
37
     let selectedAudioOutputId = getAudioOutputDeviceId();
38
     let selectedAudioOutputId = getAudioOutputDeviceId();
55
         disableAudioInputChange,
56
         disableAudioInputChange,
56
         disableDeviceChange:
57
         disableDeviceChange:
57
             !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
58
             !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
59
+        hasAudioPermission: permissions.audio,
60
+        hasVideoPermission: permissions.video,
58
         hideAudioInputPreview:
61
         hideAudioInputPreview:
59
             !JitsiMeetJS.isCollectingLocalStats(),
62
             !JitsiMeetJS.isCollectingLocalStats(),
60
         hideAudioOutputSelect: !JitsiMeetJS.mediaDevices
63
         hideAudioOutputSelect: !JitsiMeetJS.mediaDevices

+ 1
- 3
react/features/settings/components/web/audio/AudioSettingsContent.js Bestand weergeven

182
 
182
 
183
         this._disposeTracks(this.state.audioTracks);
183
         this._disposeTracks(this.state.audioTracks);
184
 
184
 
185
-        const audioTracks = await createLocalAudioTracks(
186
-            this.props.microphoneDevices
187
-        );
185
+        const audioTracks = await createLocalAudioTracks(this.props.microphoneDevices, 5000);
188
 
186
 
189
         if (this._componentWasUnmounted) {
187
         if (this._componentWasUnmounted) {
190
             this._disposeTracks(audioTracks);
188
             this._disposeTracks(audioTracks);

+ 1
- 3
react/features/settings/components/web/video/VideoSettingsContent.js Bestand weergeven

85
     async _setTracks() {
85
     async _setTracks() {
86
         this._disposeTracks(this.state.trackData);
86
         this._disposeTracks(this.state.trackData);
87
 
87
 
88
-        const trackData = await createLocalVideoTracks(
89
-            this.props.videoDeviceIds,
90
-        );
88
+        const trackData = await createLocalVideoTracks(this.props.videoDeviceIds, 5000);
91
 
89
 
92
         // In case the component gets unmounted before the tracks are created
90
         // In case the component gets unmounted before the tracks are created
93
         // avoid a leak by not setting the state
91
         // avoid a leak by not setting the state

+ 6
- 4
react/features/settings/functions.js Bestand weergeven

156
  * all the video jitsiTracks and appropriate errors for the given device ids.
156
  * all the video jitsiTracks and appropriate errors for the given device ids.
157
  *
157
  *
158
  * @param {string[]} ids - The list of the camera ids for wich to create tracks.
158
  * @param {string[]} ids - The list of the camera ids for wich to create tracks.
159
+ * @param {number} [timeout] - A timeout for the createLocalTrack function call.
159
  *
160
  *
160
  * @returns {Promise<Object[]>}
161
  * @returns {Promise<Object[]>}
161
  */
162
  */
162
-export function createLocalVideoTracks(ids: string[]) {
163
-    return Promise.all(ids.map(deviceId => createLocalTrack('video', deviceId)
163
+export function createLocalVideoTracks(ids: string[], timeout: ?number) {
164
+    return Promise.all(ids.map(deviceId => createLocalTrack('video', deviceId, timeout)
164
                    .then(jitsiTrack => {
165
                    .then(jitsiTrack => {
165
                        return {
166
                        return {
166
                            jitsiTrack,
167
                            jitsiTrack,
182
  * the audio track and the corresponding audio device information.
183
  * the audio track and the corresponding audio device information.
183
  *
184
  *
184
  * @param {Object[]} devices - A list of microphone devices.
185
  * @param {Object[]} devices - A list of microphone devices.
186
+ * @param {number} [timeout] - A timeout for the createLocalTrack function call.
185
  * @returns {Promise<{
187
  * @returns {Promise<{
186
  *   deviceId: string,
188
  *   deviceId: string,
187
  *   hasError: boolean,
189
  *   hasError: boolean,
189
  *   label: string
191
  *   label: string
190
  * }[]>}
192
  * }[]>}
191
  */
193
  */
192
-export function createLocalAudioTracks(devices: Object[]) {
194
+export function createLocalAudioTracks(devices: Object[], timeout: ?number) {
193
     return Promise.all(
195
     return Promise.all(
194
         devices.map(async ({ deviceId, label }) => {
196
         devices.map(async ({ deviceId, label }) => {
195
             let jitsiTrack = null;
197
             let jitsiTrack = null;
196
             let hasError = false;
198
             let hasError = false;
197
 
199
 
198
             try {
200
             try {
199
-                jitsiTrack = await createLocalTrack('audio', deviceId);
201
+                jitsiTrack = await createLocalTrack('audio', deviceId, timeout);
200
             } catch (err) {
202
             } catch (err) {
201
                 hasError = true;
203
                 hasError = true;
202
             }
204
             }

+ 11
- 79
react/features/toolbox/components/web/AudioSettingsButton.js Bestand weergeven

7
 import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
7
 import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
8
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
9
 import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
9
 import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
10
-import { getMediaPermissionPromptVisibility } from '../../../overlay';
11
 import { AudioSettingsPopup, toggleAudioSettings } from '../../../settings';
10
 import { AudioSettingsPopup, toggleAudioSettings } from '../../../settings';
12
 import { isAudioSettingsButtonDisabled } from '../../functions';
11
 import { isAudioSettingsButtonDisabled } from '../../functions';
13
 import AudioMuteButton from '../AudioMuteButton';
12
 import AudioMuteButton from '../AudioMuteButton';
15
 type Props = {
14
 type Props = {
16
 
15
 
17
     /**
16
     /**
18
-     * Click handler for the small icon. Opens audio options.
17
+     * Indicates whether audio permissions have been granted or denied.
19
      */
18
      */
20
-    onAudioOptionsClick: Function,
19
+    hasPermissions: boolean,
21
 
20
 
22
     /**
21
     /**
23
-     * Whether the permission prompt is visible or not.
24
-     * Useful for enabling the button on permission grant.
22
+     * Click handler for the small icon. Opens audio options.
25
      */
23
      */
26
-    permissionPromptVisibility: boolean,
24
+    onAudioOptionsClick: Function,
27
 
25
 
28
     /**
26
     /**
29
      * If the button should be disabled.
27
      * If the button should be disabled.
34
      * Flag controlling the visibility of the button.
32
      * Flag controlling the visibility of the button.
35
      * AudioSettings popup is disabled on mobile browsers.
33
      * AudioSettings popup is disabled on mobile browsers.
36
      */
34
      */
37
-    visible: boolean,
35
+    visible: boolean
38
 };
36
 };
39
 
37
 
40
-type State = {
41
-
42
-    /**
43
-     * If there are permissions for audio devices.
44
-     */
45
-    hasPermissions: boolean,
46
-}
47
-
48
 /**
38
 /**
49
  * Button used for audio & audio settings.
39
  * Button used for audio & audio settings.
50
  *
40
  *
51
  * @returns {ReactElement}
41
  * @returns {ReactElement}
52
  */
42
  */
53
-class AudioSettingsButton extends Component<Props, State> {
54
-    _isMounted: boolean;
55
-
56
-    /**
57
-     * Initializes a new {@code AudioSettingsButton} instance.
58
-     *
59
-     * @param {Object} props - The read-only properties with which the new
60
-     * instance is to be initialized.
61
-     */
62
-    constructor(props) {
63
-        super(props);
64
-
65
-        this._isMounted = true;
66
-        this.state = {
67
-            hasPermissions: false
68
-        };
69
-    }
70
-
71
-    /**
72
-     * Updates device permissions.
73
-     *
74
-     * @returns {Promise<void>}
75
-     */
76
-    async _updatePermissions() {
77
-        const hasPermissions = await JitsiMeetJS.mediaDevices.isDevicePermissionGranted(
78
-            'audio',
79
-        );
80
-
81
-        this._isMounted && this.setState({
82
-            hasPermissions
83
-        });
84
-    }
85
-
86
-    /**
87
-     * Implements React's {@link Component#componentDidMount}.
88
-     *
89
-     * @inheritdoc
90
-     */
91
-    componentDidMount() {
92
-        this._updatePermissions();
93
-    }
94
-
95
-    /**
96
-     * Implements React's {@link Component#componentDidUpdate}.
97
-     *
98
-     * @inheritdoc
99
-     */
100
-    componentDidUpdate(prevProps) {
101
-        if (this.props.permissionPromptVisibility !== prevProps.permissionPromptVisibility) {
102
-            this._updatePermissions();
103
-        }
104
-    }
105
-
106
-    /**
107
-     * Implements React's {@link Component#componentWillUnmount}.
108
-     *
109
-     * @inheritdoc
110
-     */
111
-    componentWillUnmount() {
112
-        this._isMounted = false;
113
-    }
43
+class AudioSettingsButton extends Component<Props> {
114
 
44
 
115
     /**
45
     /**
116
      * Implements React's {@link Component#render}.
46
      * Implements React's {@link Component#render}.
118
      * @inheritdoc
48
      * @inheritdoc
119
      */
49
      */
120
     render() {
50
     render() {
121
-        const { isDisabled, onAudioOptionsClick, visible } = this.props;
122
-        const settingsDisabled = !this.state.hasPermissions
51
+        const { hasPermissions, isDisabled, onAudioOptionsClick, visible } = this.props;
52
+        const settingsDisabled = !hasPermissions
123
             || isDisabled
53
             || isDisabled
124
             || !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
54
             || !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
125
 
55
 
143
  * @returns {Object}
73
  * @returns {Object}
144
  */
74
  */
145
 function mapStateToProps(state) {
75
 function mapStateToProps(state) {
76
+    const { permissions = {} } = state['features/base/devices'];
77
+
146
     return {
78
     return {
79
+        hasPermissions: permissions.audio,
147
         isDisabled: isAudioSettingsButtonDisabled(state),
80
         isDisabled: isAudioSettingsButtonDisabled(state),
148
-        permissionPromptVisibility: getMediaPermissionPromptVisibility(state),
149
         visible: !isMobileBrowser()
81
         visible: !isMobileBrowser()
150
     };
82
     };
151
 }
83
 }

+ 9
- 78
react/features/toolbox/components/web/VideoSettingsButton.js Bestand weergeven

4
 
4
 
5
 import { isMobileBrowser } from '../../../base/environment/utils';
5
 import { isMobileBrowser } from '../../../base/environment/utils';
6
 import { IconArrowDown } from '../../../base/icons';
6
 import { IconArrowDown } from '../../../base/icons';
7
-import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
8
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
9
 import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
8
 import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
10
 import { getLocalJitsiVideoTrack } from '../../../base/tracks';
9
 import { getLocalJitsiVideoTrack } from '../../../base/tracks';
11
-import { getMediaPermissionPromptVisibility } from '../../../overlay';
12
 import { toggleVideoSettings, VideoSettingsPopup } from '../../../settings';
10
 import { toggleVideoSettings, VideoSettingsPopup } from '../../../settings';
13
 import { isVideoSettingsButtonDisabled } from '../../functions';
11
 import { isVideoSettingsButtonDisabled } from '../../functions';
14
 import VideoMuteButton from '../VideoMuteButton';
12
 import VideoMuteButton from '../VideoMuteButton';
21
     onVideoOptionsClick: Function,
19
     onVideoOptionsClick: Function,
22
 
20
 
23
     /**
21
     /**
24
-     * Whether the permission prompt is visible or not.
25
-     * Useful for enabling the button on initial permission grant.
22
+     * Indicates whether video permissions have been granted or denied.
26
      */
23
      */
27
-    permissionPromptVisibility: boolean,
24
+    hasPermissions: boolean,
28
 
25
 
29
     /**
26
     /**
30
      * Whether there is a video track or not.
27
      * Whether there is a video track or not.
42
      * as mobile devices do not support capture of more than one
39
      * as mobile devices do not support capture of more than one
43
      * camera at a time.
40
      * camera at a time.
44
      */
41
      */
45
-    visible: boolean,
46
-};
47
-
48
-type State = {
49
-
50
-    /**
51
-     * Whether the app has video permissions or not.
52
-     */
53
-    hasPermissions: boolean,
42
+    visible: boolean
54
 };
43
 };
55
 
44
 
56
 /**
45
 /**
58
  *
47
  *
59
  * @returns {ReactElement}
48
  * @returns {ReactElement}
60
  */
49
  */
61
-class VideoSettingsButton extends Component<Props, State> {
62
-    _isMounted: boolean;
63
-
64
-    /**
65
-     * Initializes a new {@code VideoSettingsButton} instance.
66
-     *
67
-     * @param {Object} props - The read-only properties with which the new
68
-     * instance is to be initialized.
69
-     */
70
-    constructor(props) {
71
-        super(props);
72
-
73
-        this._isMounted = true;
74
-        this.state = {
75
-            hasPermissions: false
76
-        };
77
-    }
50
+class VideoSettingsButton extends Component<Props> {
78
 
51
 
79
     /**
52
     /**
80
      * Returns true if the settings icon is disabled.
53
      * Returns true if the settings icon is disabled.
82
      * @returns {boolean}
55
      * @returns {boolean}
83
      */
56
      */
84
     _isIconDisabled() {
57
     _isIconDisabled() {
85
-        const { hasVideoTrack, isDisabled } = this.props;
58
+        const { hasPermissions, hasVideoTrack, isDisabled } = this.props;
86
 
59
 
87
-        return (!this.state.hasPermissions || isDisabled) && !hasVideoTrack;
88
-    }
89
-
90
-    /**
91
-     * Updates device permissions.
92
-     *
93
-     * @returns {Promise<void>}
94
-     */
95
-    async _updatePermissions() {
96
-        const hasPermissions = await JitsiMeetJS.mediaDevices.isDevicePermissionGranted(
97
-            'video',
98
-        );
99
-
100
-        this._isMounted && this.setState({
101
-            hasPermissions
102
-        });
103
-    }
104
-
105
-    /**
106
-     * Implements React's {@link Component#componentDidMount}.
107
-     *
108
-     * @inheritdoc
109
-     */
110
-    componentDidMount() {
111
-        this._updatePermissions();
112
-    }
113
-
114
-    /**
115
-     * Implements React's {@link Component#componentDidUpdate}.
116
-     *
117
-     * @inheritdoc
118
-     */
119
-    componentDidUpdate(prevProps) {
120
-        if (this.props.permissionPromptVisibility !== prevProps.permissionPromptVisibility) {
121
-            this._updatePermissions();
122
-        }
123
-    }
124
-
125
-    /**
126
-     * Implements React's {@link Component#componentWillUnmount}.
127
-     *
128
-     * @inheritdoc
129
-     */
130
-    componentWillUnmount() {
131
-        this._isMounted = false;
60
+        return (!hasPermissions || isDisabled) && !hasVideoTrack;
132
     }
61
     }
133
 
62
 
134
     /**
63
     /**
159
  * @returns {Object}
88
  * @returns {Object}
160
  */
89
  */
161
 function mapStateToProps(state) {
90
 function mapStateToProps(state) {
91
+    const { permissions = {} } = state['features/base/devices'];
92
+
162
     return {
93
     return {
94
+        hasPermissions: permissions.video,
163
         hasVideoTrack: Boolean(getLocalJitsiVideoTrack(state)),
95
         hasVideoTrack: Boolean(getLocalJitsiVideoTrack(state)),
164
         isDisabled: isVideoSettingsButtonDisabled(state),
96
         isDisabled: isVideoSettingsButtonDisabled(state),
165
-        permissionPromptVisibility: getMediaPermissionPromptVisibility(state),
166
         visible: !isMobileBrowser()
97
         visible: !isMobileBrowser()
167
     };
98
     };
168
 }
99
 }

Laden…
Annuleren
Opslaan