ソースを参照

Merge pull request #701 from tsareg/gum_permission_dialog_guidance

Show overlay with guidance for gUM permission prompts
j8
yanas 9年前
コミット
1d393f5786

+ 139
- 100
conference.js ファイルの表示

@@ -56,6 +56,58 @@ function connect(roomName) {
56 56
     });
57 57
 }
58 58
 
59
+/**
60
+ * Creates local media tracks and connects to room. Will show error
61
+ * dialogs in case if accessing local microphone and/or camera failed. Will
62
+ * show guidance overlay for users on how to give access to camera and/or
63
+ * microphone,
64
+ * @param {string} roomName
65
+ * @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
66
+ */
67
+function createInitialLocalTracksAndConnect(roomName) {
68
+    let audioAndVideoError,
69
+        audioOnlyError;
70
+
71
+    JitsiMeetJS.mediaDevices.addEventListener(
72
+        JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
73
+        browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
74
+
75
+    // First try to retrieve both audio and video.
76
+    let tryCreateLocalTracks = createLocalTracks(
77
+            { devices: ['audio', 'video'] }, true)
78
+        .catch(err => {
79
+            // If failed then try to retrieve only audio.
80
+            audioAndVideoError = err;
81
+            return createLocalTracks({ devices: ['audio'] }, true);
82
+        })
83
+        .catch(err => {
84
+            // If audio failed too then just return empty array for tracks.
85
+            audioOnlyError = err;
86
+            return [];
87
+        });
88
+
89
+    return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
90
+        .then(([tracks, con]) => {
91
+            APP.UI.hideUserMediaPermissionsGuidanceOverlay();
92
+
93
+            if (audioAndVideoError) {
94
+                if (audioOnlyError) {
95
+                    // If both requests for 'audio' + 'video' and 'audio' only
96
+                    // failed, we assume that there is some problems with user's
97
+                    // microphone and show corresponding dialog.
98
+                    APP.UI.showDeviceErrorDialog(audioOnlyError, null);
99
+                } else {
100
+                    // If request for 'audio' + 'video' failed, but request for
101
+                    // 'audio' only was OK, we assume that we had problems with
102
+                    // camera and show corresponding dialog.
103
+                    APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
104
+                }
105
+            }
106
+
107
+            return [tracks, con];
108
+        });
109
+}
110
+
59 111
 /**
60 112
  * Share data to other users.
61 113
  * @param command the command
@@ -182,32 +234,42 @@ function hangup (requestFeedback = false) {
182 234
 
183 235
 /**
184 236
  * Create local tracks of specified types.
185
- * @param {string[]} devices - required track types ('audio', 'video' etc.)
186
- * @param {string|null} [cameraDeviceId] - camera device id, if undefined - one
187
- *      from settings will be used
188
- * @param {string|null} [micDeviceId] - microphone device id, if undefined - one
189
- *      from settings will be used
237
+ * @param {Object} options
238
+ * @param {string[]} options.devices - required track types
239
+ *      ('audio', 'video' etc.)
240
+ * @param {string|null} (options.cameraDeviceId) - camera device id, if
241
+ *      undefined - one from settings will be used
242
+ * @param {string|null} (options.micDeviceId) - microphone device id, if
243
+ *      undefined - one from settings will be used
244
+ * @param {boolean} (checkForPermissionPrompt) - if lib-jitsi-meet should check
245
+ *      for gUM permission prompt
190 246
  * @returns {Promise<JitsiLocalTrack[]>}
191 247
  */
192
-function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
193
-    return JitsiMeetJS.createLocalTracks({
194
-        // copy array to avoid mutations inside library
195
-        devices: devices.slice(0),
196
-        resolution: config.resolution,
197
-        cameraDeviceId: typeof cameraDeviceId === 'undefined'
198
-            || cameraDeviceId === null
248
+function createLocalTracks (options, checkForPermissionPrompt) {
249
+    options || (options = {});
250
+
251
+    return JitsiMeetJS
252
+        .createLocalTracks({
253
+            // copy array to avoid mutations inside library
254
+            devices: options.devices.slice(0),
255
+            resolution: config.resolution,
256
+            cameraDeviceId: typeof options.cameraDeviceId === 'undefined' ||
257
+                    options.cameraDeviceId === null
199 258
                 ? APP.settings.getCameraDeviceId()
200
-                : cameraDeviceId,
201
-        micDeviceId: typeof micDeviceId === 'undefined' || micDeviceId === null
202
-            ? APP.settings.getMicDeviceId()
203
-            : micDeviceId,
204
-        // adds any ff fake device settings if any
205
-        firefox_fake_device: config.firefox_fake_device
206
-    }).catch(function (err) {
207
-        console.error('failed to create local tracks', ...devices, err);
208
-        return Promise.reject(err);
209
-    });
210
-}
259
+                : options.cameraDeviceId,
260
+            micDeviceId: typeof options.micDeviceId === 'undefined' ||
261
+                    options.micDeviceId === null
262
+                ? APP.settings.getMicDeviceId()
263
+                : options.micDeviceId,
264
+            // adds any ff fake device settings if any
265
+            firefox_fake_device: config.firefox_fake_device
266
+        }, checkForPermissionPrompt)
267
+        .catch(function (err) {
268
+            console.error(
269
+                'failed to create local tracks', options.devices, err);
270
+            return Promise.reject(err);
271
+        });
272
+    }
211 273
 
212 274
 /**
213 275
  * Changes the email for the local user
@@ -406,7 +468,6 @@ export default {
406 468
      * @returns {Promise}
407 469
      */
408 470
     init(options) {
409
-        let self = this;
410 471
         this.roomName = options.roomName;
411 472
         JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
412 473
 
@@ -432,65 +493,35 @@ export default {
432 493
             };
433 494
         }
434 495
 
435
-        let audioAndVideoError, audioOnlyError;
436
-
437
-        return JitsiMeetJS.init(config).then(() => {
438
-            return Promise.all([
439
-                // try to retrieve audio and video
440
-                createLocalTracks(['audio', 'video'])
441
-                // if failed then try to retrieve only audio
442
-                    .catch(err => {
443
-                        audioAndVideoError = err;
444
-                        return createLocalTracks(['audio']);
445
-                    })
446
-                // if audio also failed then just return empty array
447
-                    .catch(err => {
448
-                        audioOnlyError = err;
449
-                        return [];
450
-                    }),
451
-                connect(options.roomName)
452
-            ]);
453
-        }).then(([tracks, con]) => {
454
-            if (audioAndVideoError) {
455
-                if (audioOnlyError) {
456
-                    // If both requests for 'audio' + 'video' and 'audio' only
457
-                    // failed, we assume that there is some problems with user's
458
-                    // microphone and show corresponding dialog.
459
-                    APP.UI.showDeviceErrorDialog(audioOnlyError, null);
460
-                } else {
461
-                    // If request for 'audio' + 'video' failed, but request for
462
-                    // 'audio' only was OK, we assume that we had problems with
463
-                    // camera and show corresponding dialog.
464
-                    APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
496
+        return JitsiMeetJS.init(config)
497
+            .then(() => createInitialLocalTracksAndConnect(options.roomName))
498
+            .then(([tracks, con]) => {
499
+                console.log('initialized with %s local tracks', tracks.length);
500
+                APP.connection = connection = con;
501
+                this._createRoom(tracks);
502
+                this.isDesktopSharingEnabled =
503
+                    JitsiMeetJS.isDesktopSharingEnabled();
504
+
505
+                // if user didn't give access to mic or camera or doesn't have
506
+                // them at all, we disable corresponding toolbar buttons
507
+                if (!tracks.find((t) => t.isAudioTrack())) {
508
+                    APP.UI.disableMicrophoneButton();
465 509
                 }
466
-            }
467
-
468
-            console.log('initialized with %s local tracks', tracks.length);
469
-            APP.connection = connection = con;
470
-            this._createRoom(tracks);
471
-            this.isDesktopSharingEnabled =
472
-                JitsiMeetJS.isDesktopSharingEnabled();
473 510
 
474
-            // if user didn't give access to mic or camera or doesn't have
475
-            // them at all, we disable corresponding toolbar buttons
476
-            if (!tracks.find((t) => t.isAudioTrack())) {
477
-                APP.UI.disableMicrophoneButton();
478
-            }
479
-
480
-            if (!tracks.find((t) => t.isVideoTrack())) {
481
-                APP.UI.disableCameraButton();
482
-            }
511
+                if (!tracks.find((t) => t.isVideoTrack())) {
512
+                    APP.UI.disableCameraButton();
513
+                }
483 514
 
484
-            this._initDeviceList();
515
+                this._initDeviceList();
485 516
 
486
-            if (config.iAmRecorder)
487
-                this.recorder = new Recorder();
517
+                if (config.iAmRecorder)
518
+                    this.recorder = new Recorder();
488 519
 
489
-            // XXX The API will take care of disconnecting from the XMPP server
490
-            // (and, thus, leaving the room) on unload.
491
-            return new Promise((resolve, reject) => {
492
-                (new ConferenceConnector(resolve, reject)).connect();
493
-            });
520
+                // XXX The API will take care of disconnecting from the XMPP
521
+                // server (and, thus, leaving the room) on unload.
522
+                return new Promise((resolve, reject) => {
523
+                    (new ConferenceConnector(resolve, reject)).connect();
524
+                });
494 525
         });
495 526
     },
496 527
     /**
@@ -834,7 +865,7 @@ export default {
834 865
         this.videoSwitchInProgress = true;
835 866
 
836 867
         if (shareScreen) {
837
-            createLocalTracks(['desktop']).then(([stream]) => {
868
+            createLocalTracks({ devices: ['desktop'] }).then(([stream]) => {
838 869
                 stream.on(
839 870
                     TrackEvents.LOCAL_TRACK_STOPPED,
840 871
                     () => {
@@ -891,7 +922,7 @@ export default {
891 922
                 APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false);
892 923
             });
893 924
         } else {
894
-            createLocalTracks(['video']).then(
925
+            createLocalTracks({ devices: ['video'] }).then(
895 926
                 ([stream]) => this.useVideoStream(stream)
896 927
             ).then(() => {
897 928
                 this.videoSwitchInProgress = false;
@@ -1247,32 +1278,40 @@ export default {
1247 1278
         APP.UI.addListener(
1248 1279
             UIEvents.VIDEO_DEVICE_CHANGED,
1249 1280
             (cameraDeviceId) => {
1250
-                createLocalTracks(['video'], cameraDeviceId, null)
1251
-                    .then(([stream]) => {
1252
-                        this.useVideoStream(stream);
1253
-                        console.log('switched local video device');
1254
-                        APP.settings.setCameraDeviceId(cameraDeviceId);
1255
-                    })
1256
-                    .catch((err) => {
1257
-                        APP.UI.showDeviceErrorDialog(null, err);
1258
-                        APP.UI.setSelectedCameraFromSettings();
1259
-                    });
1281
+                createLocalTracks({
1282
+                    devices: ['video'],
1283
+                    cameraDeviceId: cameraDeviceId,
1284
+                    micDeviceId: null
1285
+                })
1286
+                .then(([stream]) => {
1287
+                    this.useVideoStream(stream);
1288
+                    console.log('switched local video device');
1289
+                    APP.settings.setCameraDeviceId(cameraDeviceId);
1290
+                })
1291
+                .catch((err) => {
1292
+                    APP.UI.showDeviceErrorDialog(null, err);
1293
+                    APP.UI.setSelectedCameraFromSettings();
1294
+                });
1260 1295
             }
1261 1296
         );
1262 1297
 
1263 1298
         APP.UI.addListener(
1264 1299
             UIEvents.AUDIO_DEVICE_CHANGED,
1265 1300
             (micDeviceId) => {
1266
-                createLocalTracks(['audio'], null, micDeviceId)
1267
-                    .then(([stream]) => {
1268
-                        this.useAudioStream(stream);
1269
-                        console.log('switched local audio device');
1270
-                        APP.settings.setMicDeviceId(micDeviceId);
1271
-                    })
1272
-                    .catch((err) => {
1273
-                        APP.UI.showDeviceErrorDialog(err, null);
1274
-                        APP.UI.setSelectedMicFromSettings();
1275
-                    });
1301
+                createLocalTracks({
1302
+                    devices: ['audio'],
1303
+                    cameraDeviceId: null,
1304
+                    micDeviceId: micDeviceId
1305
+                })
1306
+                .then(([stream]) => {
1307
+                    this.useAudioStream(stream);
1308
+                    console.log('switched local audio device');
1309
+                    APP.settings.setMicDeviceId(micDeviceId);
1310
+                })
1311
+                .catch((err) => {
1312
+                    APP.UI.showDeviceErrorDialog(err, null);
1313
+                    APP.UI.setSelectedMicFromSettings();
1314
+                });
1276 1315
             }
1277 1316
         );
1278 1317
 

+ 15
- 0
css/overlay.css ファイルの表示

@@ -11,6 +11,10 @@
11 11
     display: block;
12 12
 }
13 13
 
14
+.overlay_transparent {
15
+    background: none;
16
+}
17
+
14 18
 .overlay_container {
15 19
     width: 100%;
16 20
     height: 100%;
@@ -49,3 +53,14 @@
49 53
     margin-top: 20px;
50 54
     float: left;
51 55
 }
56
+
57
+.overlay_text_small {
58
+    font-size: 18px;
59
+}
60
+
61
+.overlay_icon {
62
+    position: relative;
63
+    z-index: 1013;
64
+    float: none;
65
+    font-size: 100px;
66
+}

+ 12
- 2
lang/main.json ファイルの表示

@@ -12,6 +12,16 @@
12 12
     "defaultNickname": "ex. Jane Pink",
13 13
     "defaultLink": "e.g. __url__",
14 14
     "calling": "Calling __name__ ...",
15
+    "userMedia": {
16
+      "react-nativeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>Allow</i> button",
17
+      "chromeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>Allow</i> button",
18
+      "androidGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>Allow</i> button",
19
+      "firefoxGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>Share Selected Device</i> button",
20
+      "operaGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>Allow</i> button",
21
+      "iexplorerGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>OK</i> button",
22
+      "safariGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <i>OK</i> button",
23
+      "nwjsGrantPermissions": "Please grant permissions to use your camera and microphone"
24
+    },
15 25
     "keyboardShortcuts": {
16 26
         "keyboardShortcuts": "Keyboard shortcuts:",
17 27
         "raiseHand": "Raise your hand.",
@@ -247,11 +257,11 @@
247 257
         "cameraErrorPresent": "There was an error connecting to your camera.",
248 258
         "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
249 259
         "cameraUnknownError": "Cannot use camera for a unknown reason.",
250
-        "cameraPermissionDeniedError": "You have not granted permission to use your camera.",
260
+        "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.",
251 261
         "cameraNotFoundError": "Requested camera was not found.",
252 262
         "cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.",
253 263
         "micUnknownError": "Cannot use microphone for a unknown reason.",
254
-        "micPermissionDeniedError": "You have not granted permission to use your microphone.",
264
+        "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.",
255 265
         "micNotFoundError": "Requested microphone was not found.",
256 266
         "micConstraintFailedError": "Yor microphone does not satisfy some of required constraints."
257 267
     },

+ 17
- 0
modules/UI/UI.js ファイルの表示

@@ -15,6 +15,7 @@ import CQEvents from '../../service/connectionquality/CQEvents';
15 15
 import EtherpadManager from './etherpad/Etherpad';
16 16
 import SharedVideoManager from './shared_video/SharedVideo';
17 17
 import Recording from "./recording/Recording";
18
+import GumPermissionsOverlay from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
18 19
 
19 20
 import VideoLayout from "./videolayout/VideoLayout";
20 21
 import FilmStrip from "./videolayout/FilmStrip";
@@ -1415,6 +1416,22 @@ UI.hideRingOverLay = function () {
1415 1416
     FilmStrip.toggleFilmStrip(true);
1416 1417
 };
1417 1418
 
1419
+/**
1420
+ * Shows browser-specific overlay with guidance how to proceed with gUM prompt.
1421
+ * @param {string} browser - name of browser for which to show the guidance
1422
+ *      overlay.
1423
+ */
1424
+UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
1425
+    GumPermissionsOverlay.show(browser);
1426
+};
1427
+
1428
+/**
1429
+ * Hides browser-specific overlay with guidance how to proceed with gUM prompt.
1430
+ */
1431
+UI.hideUserMediaPermissionsGuidanceOverlay = function () {
1432
+    GumPermissionsOverlay.hide();
1433
+};
1434
+
1418 1435
 /**
1419 1436
  * Shows or hides the keyboard shortcuts panel, depending on the current state.'
1420 1437
  */

+ 46
- 0
modules/UI/gum_overlay/UserMediaPermissionsGuidanceOverlay.js ファイルの表示

@@ -0,0 +1,46 @@
1
+/* global $, APP, JitsiMeetJS */
2
+
3
+let $overlay;
4
+
5
+/**
6
+ * Internal function that constructs overlay with guidance how to proceed with
7
+ * gUM prompt.
8
+ * @param {string} browser - name of browser for which to construct the
9
+ *      guidance overlay.
10
+ */
11
+function buildOverlayHtml(browser) {
12
+    $overlay = $(`
13
+        <div class='overlay_container'>
14
+            <div class='overlay overlay_transparent' />
15
+            <div class='overlay_content'>
16
+                <span class="overlay_icon icon-microphone"></span>
17
+                <span class="overlay_icon icon-camera"></span>
18
+                <span data-i18n='[html]userMedia.${browser}GrantPermissions' 
19
+                    class='overlay_text overlay_text_small'></span>
20
+            </div>
21
+        </div>`);
22
+
23
+    APP.translation.translateElement($overlay);
24
+}
25
+
26
+export default {
27
+    /**
28
+     * Shows browser-specific overlay with guidance how to proceed with
29
+     * gUM prompt.
30
+     * @param {string} browser - name of browser for which to show the
31
+     *      guidance overlay.
32
+     */
33
+    show(browser) {
34
+        !$overlay && buildOverlayHtml(browser);
35
+
36
+        !$overlay.parents('body').length && $overlay.appendTo('body');
37
+    },
38
+
39
+    /**
40
+     * Hides browser-specific overlay with guidance how to proceed with
41
+     * gUM prompt.
42
+     */
43
+    hide() {
44
+        $overlay && $overlay.detach();
45
+    }
46
+};

+ 15
- 4
modules/devices/mediaDeviceHelper.js ファイルの表示

@@ -191,8 +191,11 @@ export default {
191 191
 
192 192
         if (audioRequested && videoRequested) {
193 193
             // First we try to create both audio and video tracks together.
194
-            return createLocalTracks(
195
-                    ['audio', 'video'], cameraDeviceId, micDeviceId)
194
+            return createLocalTracks({
195
+                        devices: ['audio', 'video'],
196
+                        cameraDeviceId: cameraDeviceId,
197
+                        micDeviceId: micDeviceId
198
+                    })
196 199
                     // If we fail to do this, try to create them separately.
197 200
                     .catch(() => Promise.all([
198 201
                         createAudioTrack(false).then(([stream]) => stream),
@@ -215,7 +218,11 @@ export default {
215 218
         }
216 219
 
217 220
         function createAudioTrack(showError) {
218
-            return createLocalTracks(['audio'], null, micDeviceId)
221
+            return createLocalTracks({
222
+                    devices: ['audio'],
223
+                    cameraDeviceId: null,
224
+                    micDeviceId: micDeviceId
225
+                })
219 226
                 .catch(err => {
220 227
                     audioTrackError = err;
221 228
                     showError && APP.UI.showDeviceErrorDialog(err, null);
@@ -224,7 +231,11 @@ export default {
224 231
         }
225 232
 
226 233
         function createVideoTrack(showError) {
227
-            return createLocalTracks(['video'], cameraDeviceId, null)
234
+            return createLocalTracks({
235
+                    devices: ['video'],
236
+                    cameraDeviceId: cameraDeviceId,
237
+                    micDeviceId: null
238
+                })
228 239
                 .catch(err => {
229 240
                     videoTrackError = err;
230 241
                     showError && APP.UI.showDeviceErrorDialog(null, err);

読み込み中…
キャンセル
保存