Quellcode durchsuchen

fix(GUM): improve permissions logic.

dev1
Hristo Terezov vor 4 Jahren
Ursprung
Commit
84357ce1a8
4 geänderte Dateien mit 123 neuen und 39 gelöschten Zeilen
  1. 102
    34
      JitsiMediaDevices.js
  2. 7
    0
      JitsiMediaDevicesEvents.js
  3. 12
    3
      modules/RTC/RTCUtils.js
  4. 2
    2
      service/RTC/RTCEvents.js

+ 102
- 34
JitsiMediaDevices.js Datei anzeigen

@@ -21,7 +21,7 @@ class JitsiMediaDevices {
21 21
      */
22 22
     constructor() {
23 23
         this._eventEmitter = new EventEmitter();
24
-        this._grantedPermissions = {};
24
+        this._permissions = {};
25 25
 
26 26
         RTC.addListener(
27 27
             RTCEvents.DEVICE_LIST_CHANGED,
@@ -35,14 +35,18 @@ class JitsiMediaDevices {
35 35
                 this._logOutputDevice(
36 36
                     this.getAudioOutputDevice(),
37 37
                     devices));
38
+
39
+        // We would still want to update the permissions cache in case the permissions API is not supported.
38 40
         RTC.addListener(
39
-            RTCEvents.GRANTED_PERMISSIONS,
40
-            grantedPermissions =>
41
-                this._handleGrantedPermissions(grantedPermissions));
41
+            RTCEvents.PERMISSIONS_CHANGED,
42
+            permissions => this._handlePermissionsChange(permissions));
42 43
 
43
-        // Test if the W3C Permissions API is implemented and the 'camera' and
44
-        // 'microphone' permissions are implemented. (Testing for at least one
45
-        // of them seems sufficient).
44
+        // Test if the W3C Permissions API is implemented and the 'camera' and 'microphone' permissions are
45
+        // implemented. If supported add onchange listeners.
46
+        //
47
+        // NOTE: We don't cache the result for the query because this can potentialy lead to outdated result returned
48
+        // from isDevicePermissionGranted method ( for the time period before the first GUM has been resolved) if the
49
+        //  onchange handler is not working.
46 50
         this._permissionsApiSupported = new Promise(resolve => {
47 51
             if (!navigator.permissions) {
48 52
                 resolve(false);
@@ -50,24 +54,94 @@ class JitsiMediaDevices {
50 54
                 return;
51 55
             }
52 56
 
53
-            navigator.permissions.query({ name: VIDEO_PERMISSION_NAME })
54
-                .then(() => resolve(true), () => resolve(false));
57
+            const self = this;
58
+
59
+            const promises = [];
60
+
61
+            promises.push(navigator.permissions.query({ name: VIDEO_PERMISSION_NAME })
62
+                .then(status => {
63
+                    status.onchange = function() {
64
+                        try {
65
+                            self._handlePermissionsChange({
66
+                                [MediaType.VIDEO]: self._parsePermissionState(this)
67
+                            });
68
+                        } catch (error) {
69
+                            // Nothing to do.
70
+                        }
71
+                    };
72
+
73
+                    return true;
74
+                })
75
+                .catch(() => false));
76
+
77
+            promises.push(navigator.permissions.query({ name: AUDIO_PERMISSION_NAME })
78
+                .then(status => {
79
+                    status.onchange = function() {
80
+                        try {
81
+                            self._handlePermissionsChange({
82
+                                [MediaType.AUDIO]: self._parsePermissionState(this)
83
+                            });
84
+                        } catch (error) {
85
+                            // Nothing to do.
86
+                        }
87
+                    };
88
+
89
+                    return true;
90
+                })
91
+                .catch(() => false));
92
+
93
+            Promise.all(promises).then(results => resolve(results.every(supported => supported)));
94
+
55 95
         });
56 96
     }
57 97
 
98
+
58 99
     /**
59
-     * Updated the local granted permissions cache. A permissions might be
100
+     * Parses a PermissionState object and returns true for granted and false otherwise.
101
+     *
102
+     * @param {PermissionState} permissionStatus - The PermissionState object retrieved from the Permissions API.
103
+     * @returns {boolean} - True for granted and false for denied.
104
+     * @throws {TypeError}
105
+     */
106
+    _parsePermissionState(permissionStatus = {}) {
107
+        // The status attribute is deprecated, and state
108
+        // should be used instead, but check both for now
109
+        // for backwards compatibility.
110
+        const status = permissionStatus.state || permissionStatus.status;
111
+
112
+        if (typeof status !== 'string') {
113
+            throw new TypeError();
114
+        }
115
+
116
+        return status === PERMISSION_GRANTED_STATUS;
117
+    }
118
+
119
+    /**
120
+     * Updates the local granted/denied permissions cache. A permissions might be
60 121
      * granted, denied, or undefined. This is represented by having its media
61 122
      * type key set to {@code true} or {@code false} respectively.
62 123
      *
63
-     * @param {Object} grantedPermissions - Array with the permissions
64
-     * which were granted.
124
+     * @param {Object} permissions - Object with the permissions.
65 125
      */
66
-    _handleGrantedPermissions(grantedPermissions) {
67
-        this._grantedPermissions = {
68
-            ...this._grantedPermissions,
69
-            ...grantedPermissions
70
-        };
126
+    _handlePermissionsChange(permissions) {
127
+        const hasPermissionsChanged
128
+            = [ MediaType.AUDIO, MediaType.VIDEO ]
129
+                .some(type => type in permissions && permissions[type] !== this._permissions[type]);
130
+
131
+        if (hasPermissionsChanged) {
132
+            this._permissions = {
133
+                ...this._permissions,
134
+                ...permissions
135
+            };
136
+            this._eventEmitter.emit(JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, this._permissions);
137
+
138
+            if (this._permissions[MediaType.AUDIO] || this._permissions[MediaType.VIDEO]) {
139
+                // Triggering device list update when the permissiions are granted in order to update
140
+                // the labels the devices.
141
+                // eslint-disable-next-line no-empty-function
142
+                this.enumerateDevices(() => {});
143
+            }
144
+        }
71 145
     }
72 146
 
73 147
     /**
@@ -121,13 +195,17 @@ class JitsiMediaDevices {
121 195
      * @param {'audio'|'video'} [type] - type of devices to check,
122 196
      *      undefined stands for both 'audio' and 'video' together
123 197
      * @returns {Promise<boolean>}
198
+     *
199
+     * NOTE: We don't cache the result from the query because this can potentialy lead to outdated result returned
200
+     * from this method ( for the time period before the first GUM has been resolved) if the onchange handler is not
201
+     * working.
124 202
      */
125 203
     isDevicePermissionGranted(type) {
126 204
         return new Promise(resolve => {
127 205
             // Shortcut: first check if we already know the permission was
128 206
             // granted.
129
-            if (type in this._grantedPermissions) {
130
-                resolve(this._grantedPermissions[type]);
207
+            if (type in this._permissions) {
208
+                resolve(this._permissions[type]);
131 209
 
132 210
                 return;
133 211
             }
@@ -135,14 +213,6 @@ class JitsiMediaDevices {
135 213
             // Check using the Permissions API.
136 214
             this._permissionsApiSupported.then(supported => {
137 215
                 if (!supported) {
138
-                    // Workaround on Safari for audio input device
139
-                    // selection to work. Safari doesn't support the
140
-                    // permissions query.
141
-                    if (browser.isSafari()) {
142
-                        resolve(true);
143
-
144
-                        return;
145
-                    }
146 216
                     resolve(false);
147 217
 
148 218
                     return;
@@ -176,13 +246,11 @@ class JitsiMediaDevices {
176 246
 
177 247
                 Promise.all(promises).then(
178 248
                     results => resolve(results.every(permissionStatus => {
179
-                        // The status attribute is deprecated, and state
180
-                        // should be used instead, but check both for now
181
-                        // for backwards compatibility.
182
-                        const grantStatus = permissionStatus.state
183
-                            || permissionStatus.status;
184
-
185
-                        return grantStatus === PERMISSION_GRANTED_STATUS;
249
+                        try {
250
+                            return this._parsePermissionState(permissionStatus);
251
+                        } catch {
252
+                            return false;
253
+                        }
186 254
                     })),
187 255
                     () => resolve(false)
188 256
                 );

+ 7
- 0
JitsiMediaDevicesEvents.js Datei anzeigen

@@ -12,6 +12,13 @@
12 12
  */
13 13
 export const DEVICE_LIST_CHANGED = 'mediaDevices.devicechange';
14 14
 
15
+/**
16
+ * Event emitted when the user granted/blocked a permission for the camera / mic.
17
+ * Used to keep track of the granted permissions on browsers which don't
18
+ * support the Permissions API.
19
+ */
20
+export const PERMISSIONS_CHANGED = 'rtc.permissions_changed';
21
+
15 22
 /**
16 23
  * Indicates that the environment is currently showing permission prompt to
17 24
  * access camera and/or microphone. The event provides the following

+ 12
- 3
modules/RTC/RTCUtils.js Datei anzeigen

@@ -565,7 +565,7 @@ function updateGrantedPermissions(um, stream) {
565 565
         grantedPermissions.audio = audioTracksReceived;
566 566
     }
567 567
 
568
-    eventEmitter.emit(RTCEvents.GRANTED_PERMISSIONS, grantedPermissions);
568
+    eventEmitter.emit(RTCEvents.PERMISSIONS_CHANGED, grantedPermissions);
569 569
 }
570 570
 
571 571
 /**
@@ -962,13 +962,22 @@ class RTCUtils extends Listenable {
962 962
                 })
963 963
                 .catch(error => {
964 964
                     logger.warn(`Failed to get access to local media. ${error} ${JSON.stringify(constraints)}`);
965
-                    updateGrantedPermissions(umDevices, undefined);
965
+                    const jitsiError = new JitsiTrackError(error, constraints, umDevices);
966
+
966 967
                     if (!timeoutExpired) {
967 968
                         if (typeof gumTimeout !== 'undefined') {
968 969
                             clearTimeout(gumTimeout);
969 970
                         }
970
-                        reject(new JitsiTrackError(error, constraints, umDevices));
971
+                        reject(error);
972
+                    }
973
+
974
+                    if (jitsiError.name === JitsiTrackErrors.PERMISSION_DENIED) {
975
+                        updateGrantedPermissions(umDevices, undefined);
971 976
                     }
977
+
978
+                    // else {
979
+                    // Probably the error is not caused by the lack of permissions and we don't need to update them.
980
+                    // }
972 981
                 });
973 982
         });
974 983
     }

+ 2
- 2
service/RTC/RTCEvents.js Datei anzeigen

@@ -14,11 +14,11 @@ const RTCEvents = {
14 14
     LASTN_ENDPOINT_CHANGED: 'rtc.lastn_endpoint_changed',
15 15
 
16 16
     /**
17
-     * Event emitted when the user granted a permission for the camera / mic.
17
+     * Event emitted when the user granted/blocked a permission for the camera / mic.
18 18
      * Used to keep track of the granted permissions on browsers which don't
19 19
      * support the Permissions API.
20 20
      */
21
-    GRANTED_PERMISSIONS: 'rtc.granted_permissions',
21
+    PERMISSIONS_CHANGED: 'rtc.permissions_changed',
22 22
 
23 23
     SENDER_VIDEO_CONSTRAINTS_CHANGED: 'rtc.sender_video_constraints_changed',
24 24
 

Laden…
Abbrechen
Speichern