Browse Source

Merge pull request #818 from saghul/refactor-permissions

Refactor permissions
tags/v0.0.2
Saúl Ibarra Corretgé 7 years ago
parent
commit
f86e86bba5
No account linked to committer's email address

+ 0
- 6
JitsiConference.js View File

@@ -131,10 +131,6 @@ export default function JitsiConference(options) {
131 131
         audio: false,
132 132
         video: false
133 133
     };
134
-    this.availableDevices = {
135
-        audio: undefined,
136
-        video: undefined
137
-    };
138 134
     this.isMutedByFocus = false;
139 135
 
140 136
     // Flag indicates if the 'onCallEnded' method was ever called on this
@@ -256,8 +252,6 @@ JitsiConference.prototype._init = function(options = {}) {
256 252
     this.room.addListener(
257 253
         XMPPEvents.CONNECTION_ESTABLISHED, this._onIceConnectionEstablished);
258 254
 
259
-    this.room.updateDeviceAvailability(RTC.getDeviceAvailability());
260
-
261 255
     this._updateProperties = this._updateProperties.bind(this);
262 256
     this.room.addListener(XMPPEvents.CONFERENCE_PROPERTIES_CHANGED,
263 257
         this._updateProperties);

+ 0
- 50
JitsiConferenceEventManager.js View File

@@ -391,52 +391,6 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
391 391
         }
392 392
     });
393 393
 
394
-    chatRoom.addPresenceListener('devices', (data, from) => {
395
-        let isAudioAvailable = false;
396
-        let isVideoAvailable = false;
397
-
398
-        data.children.forEach(config => {
399
-            if (config.tagName === 'audio') {
400
-                isAudioAvailable = config.value === 'true';
401
-            }
402
-            if (config.tagName === 'video') {
403
-                isVideoAvailable = config.value === 'true';
404
-            }
405
-        });
406
-
407
-        let availableDevices;
408
-
409
-        if (conference.myUserId() === from) {
410
-            availableDevices = conference.availableDevices;
411
-        } else {
412
-            const participant = conference.getParticipantById(from);
413
-
414
-            if (!participant) {
415
-                return;
416
-            }
417
-
418
-            availableDevices = participant._availableDevices;
419
-        }
420
-
421
-        let updated = false;
422
-
423
-        if (availableDevices.audio !== isAudioAvailable) {
424
-            updated = true;
425
-            availableDevices.audio = isAudioAvailable;
426
-        }
427
-
428
-        if (availableDevices.video !== isVideoAvailable) {
429
-            updated = true;
430
-            availableDevices.video = isVideoAvailable;
431
-        }
432
-
433
-        if (updated) {
434
-            conference.eventEmitter.emit(
435
-                JitsiConferenceEvents.AVAILABLE_DEVICES_CHANGED,
436
-                from, availableDevices);
437
-        }
438
-    });
439
-
440 394
     if (conference.statistics) {
441 395
         // FIXME ICE related events should end up in RTCEvents eventually
442 396
         chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
@@ -494,10 +448,6 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function() {
494 448
         conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);
495 449
     });
496 450
 
497
-    rtc.addListener(
498
-        RTCEvents.AVAILABLE_DEVICES_CHANGED,
499
-        devices => conference.room.updateDeviceAvailability(devices));
500
-
501 451
     rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,
502 452
         (from, payload) => {
503 453
             const participant = conference.getParticipantById(from);

+ 0
- 5
JitsiConferenceEvents.js View File

@@ -7,11 +7,6 @@
7 7
  */
8 8
 export const AUTH_STATUS_CHANGED = 'conference.auth_status_changed';
9 9
 
10
-/**
11
- * Indicates that available devices changed.
12
- */
13
-export const AVAILABLE_DEVICES_CHANGED = 'conference.availableDevicesChanged';
14
-
15 10
 /**
16 11
  * A participant avatar has changed.
17 12
  */

+ 133
- 59
JitsiMediaDevices.js View File

@@ -8,32 +8,88 @@ import Statistics from './modules/statistics/statistics';
8 8
 
9 9
 import * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';
10 10
 
11
-const eventEmitter = new EventEmitter();
12
-
13 11
 /**
14
- * Gathers data and sends it to statistics.
15
- * @param deviceID the device id to log
16
- * @param devices list of devices
12
+ * Media devices utilities for Jitsi.
17 13
  */
18
-function logOutputDevice(deviceID, devices) {
19
-    const device
20
-        = devices.find(
21
-            d => d.kind === 'audiooutput' && d.deviceId === deviceID);
22
-
23
-    if (device) {
24
-        Statistics.sendActiveDeviceListEvent(
25
-            RTC.getEventDataForActiveDevice(device));
14
+class JitsiMediaDevices {
15
+    /**
16
+     * Initializes a {@code JitsiMediaDevices} object. There will be a single
17
+     * instance of this class.
18
+     */
19
+    constructor() {
20
+        this._eventEmitter = new EventEmitter();
21
+        this._grantedPermissions = {};
22
+
23
+        RTC.addListener(
24
+            RTCEvents.DEVICE_LIST_CHANGED,
25
+            devices =>
26
+                this._eventEmitter.emit(
27
+                    JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
28
+                    devices));
29
+        RTC.addListener(
30
+            RTCEvents.DEVICE_LIST_AVAILABLE,
31
+            devices =>
32
+                this._logOutputDevice(
33
+                    this.getAudioOutputDevice(),
34
+                    devices));
35
+        RTC.addListener(
36
+            RTCEvents.GRANTED_PERMISSIONS,
37
+            grantedPermissions =>
38
+                this._handleGrantedPermissions(grantedPermissions));
39
+
40
+        // Test if the W3C Permissions API is implemented and the 'camera' and
41
+        // 'microphone' permissions are implemented. (Testing for at least one
42
+        // of them seems sufficient).
43
+        this._permissionsApiSupported = new Promise(resolve => {
44
+            if (!navigator.permissions) {
45
+                resolve(false);
46
+
47
+                return;
48
+            }
49
+
50
+            navigator.permissions.query({ name: 'camera ' })
51
+                .then(() => resolve(true), () => resolve(false));
52
+        });
53
+    }
54
+
55
+    /**
56
+     * Updated the local granted permissions cache. A permissions might be
57
+     * granted, denied, or undefined. This is represented by having its media
58
+     * type key set to {@code true} or {@code false} respectively.
59
+     *
60
+     * @param {Object} grantedPermissions - Array with the permissions
61
+     * which were granted.
62
+     */
63
+    _handleGrantedPermissions(grantedPermissions) {
64
+        this._grantedPermissions = {
65
+            ...this._grantedPermissions,
66
+            ...grantedPermissions
67
+        };
68
+    }
69
+
70
+    /**
71
+     * Gathers data and sends it to statistics.
72
+     * @param deviceID the device id to log
73
+     * @param devices list of devices
74
+     */
75
+    _logOutputDevice(deviceID, devices) {
76
+        const device
77
+            = devices.find(
78
+                d => d.kind === 'audiooutput' && d.deviceId === deviceID);
79
+
80
+        if (device) {
81
+            Statistics.sendActiveDeviceListEvent(
82
+                RTC.getEventDataForActiveDevice(device));
83
+        }
26 84
     }
27
-}
28 85
 
29
-const JitsiMediaDevices = {
30 86
     /**
31 87
      * Executes callback with list of media devices connected.
32 88
      * @param {function} callback
33 89
      */
34 90
     enumerateDevices(callback) {
35 91
         RTC.enumerateDevices(callback);
36
-    },
92
+    }
37 93
 
38 94
     /**
39 95
      * Checks if its possible to enumerate available cameras/micropones.
@@ -43,7 +99,7 @@ const JitsiMediaDevices = {
43 99
      */
44 100
     isDeviceListAvailable() {
45 101
         return RTC.isDeviceListAvailable();
46
-    },
102
+    }
47 103
 
48 104
     /**
49 105
      * Returns true if changing the input (camera / microphone) or output
@@ -54,26 +110,58 @@ const JitsiMediaDevices = {
54 110
      */
55 111
     isDeviceChangeAvailable(deviceType) {
56 112
         return RTC.isDeviceChangeAvailable(deviceType);
57
-    },
113
+    }
58 114
 
59 115
     /**
60
-     * Returns true if user granted permission to media devices.
116
+     * Checks if the permission for the given device was granted.
117
+     *
61 118
      * @param {'audio'|'video'} [type] - type of devices to check,
62 119
      *      undefined stands for both 'audio' and 'video' together
63
-     * @returns {boolean}
120
+     * @returns {Promise<boolean>}
64 121
      */
65 122
     isDevicePermissionGranted(type) {
66
-        const permissions = RTC.getDeviceAvailability();
67
-
68
-        switch (type) {
69
-        case MediaType.VIDEO:
70
-            return permissions.video === true;
71
-        case MediaType.AUDIO:
72
-            return permissions.audio === true;
73
-        default:
74
-            return permissions.video === true && permissions.audio === true;
75
-        }
76
-    },
123
+        return new Promise(resolve => {
124
+            // Shortcut: first check if we already know the permission was
125
+            // granted.
126
+            if (type in this._grantedPermissions) {
127
+                resolve(this._grantedPermissions[type]);
128
+
129
+                return;
130
+            }
131
+
132
+            // Check using the Permissions API.
133
+            this._permissionsApiSupported.then(supported => {
134
+                if (!supported) {
135
+                    resolve(false);
136
+
137
+                    return;
138
+                }
139
+
140
+                const promises = [];
141
+
142
+                switch (type) {
143
+                case MediaType.VIDEO:
144
+                    promises.push(
145
+                        navigator.permissions.query({ name: 'camera' }));
146
+                    break;
147
+                case MediaType.AUDIO:
148
+                    promises.push(
149
+                        navigator.permissions.query({ name: 'microphone' }));
150
+                    break;
151
+                default:
152
+                    promises.push(
153
+                        navigator.permissions.query({ name: 'camera' }));
154
+                    promises.push(
155
+                        navigator.permissions.query({ name: 'microphone' }));
156
+                }
157
+
158
+                Promise.all(promises).then(
159
+                    r => resolve(r.every(Boolean)),
160
+                    () => resolve(false)
161
+                );
162
+            });
163
+        });
164
+    }
77 165
 
78 166
     /**
79 167
      * Returns true if it is possible to be simultaneously capturing audio
@@ -83,7 +171,7 @@ const JitsiMediaDevices = {
83 171
      */
84 172
     isMultipleAudioInputSupported() {
85 173
         return !browser.isFirefox();
86
-    },
174
+    }
87 175
 
88 176
     /**
89 177
      * Returns currently used audio output device id, 'default' stands
@@ -92,7 +180,7 @@ const JitsiMediaDevices = {
92 180
      */
93 181
     getAudioOutputDevice() {
94 182
         return RTC.getAudioOutputDevice();
95
-    },
183
+    }
96 184
 
97 185
     /**
98 186
      * Sets current audio output device.
@@ -103,18 +191,18 @@ const JitsiMediaDevices = {
103 191
      *      otherwise
104 192
      */
105 193
     setAudioOutputDevice(deviceId) {
106
-
107 194
         const availableDevices = RTC.getCurrentlyAvailableMediaDevices();
108 195
 
109 196
         if (availableDevices && availableDevices.length > 0) {
110 197
             // if we have devices info report device to stats
111 198
             // normally this will not happen on startup as this method is called
112 199
             // too early. This will happen only on user selection of new device
113
-            logOutputDevice(deviceId, RTC.getCurrentlyAvailableMediaDevices());
200
+            this._logOutputDevice(
201
+                deviceId, RTC.getCurrentlyAvailableMediaDevices());
114 202
         }
115 203
 
116 204
         return RTC.setAudioOutputDevice(deviceId);
117
-    },
205
+    }
118 206
 
119 207
     /**
120 208
      * Adds an event handler.
@@ -122,8 +210,8 @@ const JitsiMediaDevices = {
122 210
      * @param {function} handler - event handler
123 211
      */
124 212
     addEventListener(event, handler) {
125
-        eventEmitter.addListener(event, handler);
126
-    },
213
+        this._eventEmitter.addListener(event, handler);
214
+    }
127 215
 
128 216
     /**
129 217
      * Removes event handler.
@@ -131,16 +219,16 @@ const JitsiMediaDevices = {
131 219
      * @param {function} handler - event handler
132 220
      */
133 221
     removeEventListener(event, handler) {
134
-        eventEmitter.removeListener(event, handler);
135
-    },
222
+        this._eventEmitter.removeListener(event, handler);
223
+    }
136 224
 
137 225
     /**
138 226
      * Emits an event.
139 227
      * @param {string} event - event name
140 228
      */
141 229
     emitEvent(event, ...args) {
142
-        eventEmitter.emit(event, ...args);
143
-    },
230
+        this._eventEmitter.emit(event, ...args);
231
+    }
144 232
 
145 233
     /**
146 234
      * Returns whether or not the current browser can support capturing video,
@@ -154,20 +242,6 @@ const JitsiMediaDevices = {
154 242
         // JitsiMediaDevices.
155 243
         return browser.supportsVideo();
156 244
     }
157
-};
158
-
159
-
160
-RTC.addListener(
161
-    RTCEvents.DEVICE_LIST_CHANGED,
162
-    devices =>
163
-        eventEmitter.emit(
164
-            JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
165
-            devices));
166
-RTC.addListener(
167
-    RTCEvents.DEVICE_LIST_AVAILABLE,
168
-    devices =>
169
-        logOutputDevice(
170
-            JitsiMediaDevices.getAudioOutputDevice(),
171
-            devices));
172
-
173
-export default JitsiMediaDevices;
245
+}
246
+
247
+export default new JitsiMediaDevices();

+ 0
- 4
JitsiParticipant.js View File

@@ -34,10 +34,6 @@ export default class JitsiParticipant {
34 34
         this._tracks = [];
35 35
         this._role = 'none';
36 36
         this._status = status;
37
-        this._availableDevices = {
38
-            audio: undefined,
39
-            video: undefined
40
-        };
41 37
         this._hidden = hidden;
42 38
         this._statsID = statsID;
43 39
         this._connectionStatus = ParticipantConnectionStatus.ACTIVE;

+ 1
- 2
doc/API.md View File

@@ -88,7 +88,7 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
88 88
         - groupId - group identifier, two devices have the same group identifier if they belong to the same physical device; for example a monitor with both a built-in camera and microphone
89 89
     - ```setAudioOutputDevice(deviceId)``` - sets current audio output device. ```deviceId``` - id of 'audiooutput' device from ```JitsiMeetJS.enumerateDevices()```, '' is for default device.
90 90
     - ```getAudioOutputDevice()``` - returns currently used audio output device id, '' stands for default device.
91
-    - ```isDevicePermissionGranted(type)``` - returns true if user granted permission to media devices. ```type``` - 'audio', 'video' or ```undefined```. In case of ```undefined``` will check if both audio and video permissions were granted.
91
+    - ```isDevicePermissionGranted(type)``` - returns a Promise which resolves to true if user granted permission to media devices. ```type``` - 'audio', 'video' or ```undefined```. In case of ```undefined``` will check if both audio and video permissions were granted.
92 92
     - ```addEventListener(event, handler)``` - attaches an event handler.
93 93
     - ```removeEventListener(event, handler)``` - removes an event handler.
94 94
 
@@ -117,7 +117,6 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
117 117
         - KICKED - notifies that user has been kicked from the conference.
118 118
         - START_MUTED_POLICY_CHANGED - notifies that all new participants will join with muted audio/video stream (parameters - JS object with 2 properties - audio(boolean), video(boolean))
119 119
         - STARTED_MUTED - notifies that the local user has started muted
120
-        - AVAILABLE_DEVICES_CHANGED - notifies that available participant devices changed (camera or microphone was added or removed) (parameters - id(string), devices(JS object with 2 properties - audio(boolean), video(boolean)))
121 120
         - CONNECTION_STATS - New local connection statistics are received. (parameters - stats(object))
122 121
         - BEFORE_STATISTICS_DISPOSED - fired just before the statistics module is disposed and it's the last chance to submit some logs to the statistics service, before it gets disconnected
123 122
         - AUTH_STATUS_CHANGED - notifies that authentication is enabled or disabled, or local user authenticated (logged in). (parameters - isAuthEnabled(boolean), authIdentity(string))

+ 0
- 7
modules/RTC/RTC.js View File

@@ -416,13 +416,6 @@ export default class RTC extends Listenable {
416 416
         return RTCUtils.init(this.options);
417 417
     }
418 418
 
419
-    /**
420
-     *
421
-     */
422
-    static getDeviceAvailability() {
423
-        return RTCUtils.getDeviceAvailability();
424
-    }
425
-
426 419
     /* eslint-disable max-params */
427 420
 
428 421
     /**

+ 10
- 45
modules/RTC/RTCUtils.js View File

@@ -65,14 +65,6 @@ const DEFAULT_CONSTRAINTS = {
65 65
     }
66 66
 };
67 67
 
68
-
69
-// TODO (brian): Move this devices hash, maybe to a model, so RTCUtils remains
70
-// stateless.
71
-const devices = {
72
-    audio: false,
73
-    video: false
74
-};
75
-
76 68
 /**
77 69
  * The default frame rate for Screen Sharing.
78 70
  */
@@ -496,23 +488,24 @@ function getTrackSSConstraints(options = {}) {
496 488
 }
497 489
 
498 490
 /**
499
- * Sets the availbale devices based on the options we requested and the
491
+ * Updates the granted permissions based on the options we requested and the
500 492
  * streams we received.
501 493
  * @param um the options we requested to getUserMedia.
502 494
  * @param stream the stream we received from calling getUserMedia.
503 495
  */
504
-function setAvailableDevices(um, stream) {
496
+function updateGrantedPermissions(um, stream) {
505 497
     const audioTracksReceived = stream && stream.getAudioTracks().length > 0;
506 498
     const videoTracksReceived = stream && stream.getVideoTracks().length > 0;
499
+    const grantedPermissions = {};
507 500
 
508 501
     if (um.indexOf('video') !== -1) {
509
-        devices.video = videoTracksReceived;
502
+        grantedPermissions.video = videoTracksReceived;
510 503
     }
511 504
     if (um.indexOf('audio') !== -1) {
512
-        devices.audio = audioTracksReceived;
505
+        grantedPermissions.audio = audioTracksReceived;
513 506
     }
514 507
 
515
-    eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, devices);
508
+    eventEmitter.emit(RTCEvents.GRANTED_PERMISSIONS, grantedPermissions);
516 509
 }
517 510
 
518 511
 /**
@@ -596,27 +589,6 @@ function onMediaDevicesListChanged(devicesReceived) {
596 589
 
597 590
     sendDeviceListToAnalytics(availableDevices);
598 591
 
599
-    const videoInputDevices
600
-        = availableDevices.filter(d => d.kind === 'videoinput');
601
-    const audioInputDevices
602
-        = availableDevices.filter(d => d.kind === 'audioinput');
603
-    const videoInputDevicesWithEmptyLabels
604
-        = videoInputDevices.filter(d => d.label === '');
605
-    const audioInputDevicesWithEmptyLabels
606
-        = audioInputDevices.filter(d => d.label === '');
607
-
608
-    if (videoInputDevices.length
609
-            && videoInputDevices.length
610
-                === videoInputDevicesWithEmptyLabels.length) {
611
-        devices.video = false;
612
-    }
613
-
614
-    if (audioInputDevices.length
615
-            && audioInputDevices.length
616
-                === audioInputDevicesWithEmptyLabels.length) {
617
-        devices.audio = false;
618
-    }
619
-
620 592
     eventEmitter.emit(RTCEvents.DEVICE_LIST_CHANGED, devicesReceived);
621 593
 }
622 594
 
@@ -1004,13 +976,13 @@ class RTCUtils extends Listenable {
1004 976
             navigator.mediaDevices.getUserMedia(constraints)
1005 977
                 .then(stream => {
1006 978
                     logger.log('onUserMediaSuccess');
1007
-                    setAvailableDevices(um, stream);
979
+                    updateGrantedPermissions(um, stream);
1008 980
                     resolve(stream);
1009 981
                 })
1010 982
                 .catch(error => {
1011 983
                     logger.warn('Failed to get access to local media. '
1012 984
                         + ` ${error} ${constraints} `);
1013
-                    setAvailableDevices(um, undefined);
985
+                    updateGrantedPermissions(um, undefined);
1014 986
                     reject(new JitsiTrackError(error, constraints, um));
1015 987
                 });
1016 988
         });
@@ -1029,13 +1001,13 @@ class RTCUtils extends Listenable {
1029 1001
             navigator.mediaDevices.getUserMedia(constraints)
1030 1002
                 .then(stream => {
1031 1003
                     logger.log('onUserMediaSuccess');
1032
-                    setAvailableDevices(umDevices, stream);
1004
+                    updateGrantedPermissions(umDevices, stream);
1033 1005
                     resolve(stream);
1034 1006
                 })
1035 1007
                 .catch(error => {
1036 1008
                     logger.warn('Failed to get access to local media. '
1037 1009
                         + ` ${error} ${constraints} `);
1038
-                    setAvailableDevices(umDevices, undefined);
1010
+                    updateGrantedPermissions(umDevices, undefined);
1039 1011
                     reject(new JitsiTrackError(error, constraints, umDevices));
1040 1012
                 });
1041 1013
         });
@@ -1421,13 +1393,6 @@ class RTCUtils extends Listenable {
1421 1393
             .then(() => mediaStreamsMetaData);
1422 1394
     }
1423 1395
 
1424
-    /**
1425
-     *
1426
-     */
1427
-    getDeviceAvailability() {
1428
-        return devices;
1429
-    }
1430
-
1431 1396
     /**
1432 1397
      * Checks whether it is possible to enumerate available cameras/microphones.
1433 1398
      *

+ 0
- 20
modules/xmpp/ChatRoom.js View File

@@ -211,26 +211,6 @@ export default class ChatRoom extends Listenable {
211 211
         }
212 212
     }
213 213
 
214
-    /**
215
-     *
216
-     * @param devices
217
-     */
218
-    updateDeviceAvailability(devices) {
219
-        this.presMap.nodes.push({
220
-            'tagName': 'devices',
221
-            'children': [
222
-                {
223
-                    'tagName': 'audio',
224
-                    'value': devices.audio
225
-                },
226
-                {
227
-                    'tagName': 'video',
228
-                    'value': devices.video
229
-                }
230
-            ]
231
-        });
232
-    }
233
-
234 214
     /**
235 215
      * Joins the chat room.
236 216
      * @param password

+ 7
- 1
service/RTC/RTCEvents.js View File

@@ -13,6 +13,13 @@ const RTCEvents = {
13 13
     DOMINANT_SPEAKER_CHANGED: 'rtc.dominant_speaker_changed',
14 14
     LASTN_ENDPOINT_CHANGED: 'rtc.lastn_endpoint_changed',
15 15
 
16
+    /**
17
+     * Event emitted when the user granted a permission for the camera / mic.
18
+     * Used to keep track of the granted permissions on browsers which don't
19
+     * support the Permissions API.
20
+     */
21
+    GRANTED_PERMISSIONS: 'rtc.granted_permissions',
22
+
16 23
     IS_SELECTED_CHANGED: 'rtc.is_selected_change',
17 24
 
18 25
     /**
@@ -30,7 +37,6 @@ const RTCEvents = {
30 37
      */
31 38
     LOCAL_TRACK_SSRC_UPDATED: 'rtc.local_track_ssrc_updated',
32 39
 
33
-    AVAILABLE_DEVICES_CHANGED: 'rtc.available_devices_changed',
34 40
     TRACK_ATTACHED: 'rtc.track_attached',
35 41
 
36 42
     /**

Loading…
Cancel
Save