ソースを参照

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
     });
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
  * Share data to other users.
112
  * Share data to other users.
61
  * @param command the command
113
  * @param command the command
182
 
234
 
183
 /**
235
 /**
184
  * Create local tracks of specified types.
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
  * @returns {Promise<JitsiLocalTrack[]>}
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
                 ? APP.settings.getCameraDeviceId()
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
  * Changes the email for the local user
275
  * Changes the email for the local user
406
      * @returns {Promise}
468
      * @returns {Promise}
407
      */
469
      */
408
     init(options) {
470
     init(options) {
409
-        let self = this;
410
         this.roomName = options.roomName;
471
         this.roomName = options.roomName;
411
         JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
472
         JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
412
 
473
 
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
         this.videoSwitchInProgress = true;
865
         this.videoSwitchInProgress = true;
835
 
866
 
836
         if (shareScreen) {
867
         if (shareScreen) {
837
-            createLocalTracks(['desktop']).then(([stream]) => {
868
+            createLocalTracks({ devices: ['desktop'] }).then(([stream]) => {
838
                 stream.on(
869
                 stream.on(
839
                     TrackEvents.LOCAL_TRACK_STOPPED,
870
                     TrackEvents.LOCAL_TRACK_STOPPED,
840
                     () => {
871
                     () => {
891
                 APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false);
922
                 APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false);
892
             });
923
             });
893
         } else {
924
         } else {
894
-            createLocalTracks(['video']).then(
925
+            createLocalTracks({ devices: ['video'] }).then(
895
                 ([stream]) => this.useVideoStream(stream)
926
                 ([stream]) => this.useVideoStream(stream)
896
             ).then(() => {
927
             ).then(() => {
897
                 this.videoSwitchInProgress = false;
928
                 this.videoSwitchInProgress = false;
1247
         APP.UI.addListener(
1278
         APP.UI.addListener(
1248
             UIEvents.VIDEO_DEVICE_CHANGED,
1279
             UIEvents.VIDEO_DEVICE_CHANGED,
1249
             (cameraDeviceId) => {
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
         APP.UI.addListener(
1298
         APP.UI.addListener(
1264
             UIEvents.AUDIO_DEVICE_CHANGED,
1299
             UIEvents.AUDIO_DEVICE_CHANGED,
1265
             (micDeviceId) => {
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
     display: block;
11
     display: block;
12
 }
12
 }
13
 
13
 
14
+.overlay_transparent {
15
+    background: none;
16
+}
17
+
14
 .overlay_container {
18
 .overlay_container {
15
     width: 100%;
19
     width: 100%;
16
     height: 100%;
20
     height: 100%;
49
     margin-top: 20px;
53
     margin-top: 20px;
50
     float: left;
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
     "defaultNickname": "ex. Jane Pink",
12
     "defaultNickname": "ex. Jane Pink",
13
     "defaultLink": "e.g. __url__",
13
     "defaultLink": "e.g. __url__",
14
     "calling": "Calling __name__ ...",
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
     "keyboardShortcuts": {
25
     "keyboardShortcuts": {
16
         "keyboardShortcuts": "Keyboard shortcuts:",
26
         "keyboardShortcuts": "Keyboard shortcuts:",
17
         "raiseHand": "Raise your hand.",
27
         "raiseHand": "Raise your hand.",
247
         "cameraErrorPresent": "There was an error connecting to your camera.",
257
         "cameraErrorPresent": "There was an error connecting to your camera.",
248
         "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
258
         "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
249
         "cameraUnknownError": "Cannot use camera for a unknown reason.",
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
         "cameraNotFoundError": "Requested camera was not found.",
261
         "cameraNotFoundError": "Requested camera was not found.",
252
         "cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.",
262
         "cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.",
253
         "micUnknownError": "Cannot use microphone for a unknown reason.",
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
         "micNotFoundError": "Requested microphone was not found.",
265
         "micNotFoundError": "Requested microphone was not found.",
256
         "micConstraintFailedError": "Yor microphone does not satisfy some of required constraints."
266
         "micConstraintFailedError": "Yor microphone does not satisfy some of required constraints."
257
     },
267
     },

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

15
 import EtherpadManager from './etherpad/Etherpad';
15
 import EtherpadManager from './etherpad/Etherpad';
16
 import SharedVideoManager from './shared_video/SharedVideo';
16
 import SharedVideoManager from './shared_video/SharedVideo';
17
 import Recording from "./recording/Recording";
17
 import Recording from "./recording/Recording";
18
+import GumPermissionsOverlay from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
18
 
19
 
19
 import VideoLayout from "./videolayout/VideoLayout";
20
 import VideoLayout from "./videolayout/VideoLayout";
20
 import FilmStrip from "./videolayout/FilmStrip";
21
 import FilmStrip from "./videolayout/FilmStrip";
1415
     FilmStrip.toggleFilmStrip(true);
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
  * Shows or hides the keyboard shortcuts panel, depending on the current state.'
1436
  * Shows or hides the keyboard shortcuts panel, depending on the current state.'
1420
  */
1437
  */

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

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

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