瀏覽代碼

Switch local audio and video track when list of available devices changes

j8
Kostiantyn Tsaregradskyi 9 年之前
父節點
當前提交
b270256a7a
共有 7 個文件被更改,包括 395 次插入70 次删除
  1. 218
    13
      conference.js
  2. 4
    0
      css/main.css
  3. 6
    2
      lang/main.json
  4. 28
    0
      modules/UI/UI.js
  5. 79
    46
      modules/UI/side_pannels/settings/SettingsMenu.js
  6. 49
    1
      modules/UI/toolbars/Toolbar.js
  7. 11
    8
      modules/settings/Settings.js

+ 218
- 13
conference.js 查看文件

@@ -170,16 +170,25 @@ function hangup (requestFeedback = false) {
170 170
 
171 171
 /**
172 172
  * Create local tracks of specified types.
173
- * @param {string[]} devices required track types ('audio', 'video' etc.)
173
+ * @param {string[]} devices - required track types ('audio', 'video' etc.)
174
+ * @param {string|null} [cameraDeviceId] - camera device id, if undefined - one
175
+ *      from settings will be used
176
+ * @param {string|null} [micDeviceId] - microphone device id, if undefined - one
177
+ *      from settings will be used
174 178
  * @returns {Promise<JitsiLocalTrack[]>}
175 179
  */
176
-function createLocalTracks (...devices) {
180
+function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
177 181
     return JitsiMeetJS.createLocalTracks({
178 182
         // copy array to avoid mutations inside library
179 183
         devices: devices.slice(0),
180 184
         resolution: config.resolution,
181
-        cameraDeviceId: APP.settings.getCameraDeviceId(),
182
-        micDeviceId: APP.settings.getMicDeviceId(),
185
+        cameraDeviceId: typeof cameraDeviceId === 'undefined'
186
+            || cameraDeviceId === null
187
+                ? APP.settings.getCameraDeviceId()
188
+                : cameraDeviceId,
189
+        micDeviceId: typeof micDeviceId === 'undefined' || micDeviceId === null
190
+            ? APP.settings.getMicDeviceId()
191
+            : micDeviceId,
183 192
         // adds any ff fake device settings if any
184 193
         firefox_fake_device: config.firefox_fake_device
185 194
     }).catch(function (err) {
@@ -327,6 +336,7 @@ export default {
327 336
      * @returns {Promise}
328 337
      */
329 338
     init(options) {
339
+        let self = this;
330 340
         this.roomName = options.roomName;
331 341
         JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
332 342
 
@@ -356,9 +366,9 @@ export default {
356 366
         return JitsiMeetJS.init(config).then(() => {
357 367
             return Promise.all([
358 368
                 // try to retrieve audio and video
359
-                createLocalTracks('audio', 'video')
369
+                createLocalTracks(['audio', 'video'])
360 370
                 // if failed then try to retrieve only audio
361
-                    .catch(() => createLocalTracks('audio'))
371
+                    .catch(() => createLocalTracks(['audio']))
362 372
                 // if audio also failed then just return empty array
363 373
                     .catch(() => []),
364 374
                 connect(options.roomName)
@@ -370,15 +380,49 @@ export default {
370 380
             this.isDesktopSharingEnabled =
371 381
                 JitsiMeetJS.isDesktopSharingEnabled();
372 382
 
383
+            // if user didn't give access to mic or camera or doesn't have
384
+            // them at all, we disable corresponding toolbar buttons
385
+            if (!tracks.find((t) => t.isAudioTrack())) {
386
+                APP.UI.disableMicrophoneButton();
387
+            }
388
+
389
+            if (!tracks.find((t) => t.isVideoTrack())) {
390
+                APP.UI.disableCameraButton();
391
+            }
392
+
373 393
             // update list of available devices
374 394
             if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
375 395
                 JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
376
-                JitsiMeetJS.mediaDevices.enumerateDevices(
377
-                    APP.UI.onAvailableDevicesChanged);
396
+                JitsiMeetJS.mediaDevices.enumerateDevices(function(devices) {
397
+                    // Ugly way to synchronize real device IDs with local
398
+                    // storage and settings menu. This is a workaround until
399
+                    // getConstraints() method will be implemented in browsers.
400
+                    if (localAudio) {
401
+                        localAudio._setRealDeviceIdFromDeviceList(devices);
402
+                        APP.settings.setMicDeviceId(localAudio.getDeviceId());
403
+                    }
404
+
405
+                    if (localVideo) {
406
+                        localVideo._setRealDeviceIdFromDeviceList(devices);
407
+                        APP.settings.setCameraDeviceId(
408
+                            localVideo.getDeviceId());
409
+                    }
410
+
411
+                    APP.UI.onAvailableDevicesChanged(devices);
412
+                });
378 413
 
379 414
                 JitsiMeetJS.mediaDevices.addEventListener(
380 415
                     JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
381
-                    APP.UI.onAvailableDevicesChanged);
416
+                    (devices) => {
417
+                        // Just defer callback until other event callbacks are
418
+                        // processed.
419
+                        window.setTimeout(() => {
420
+                            checkLocalDevicesAfterDeviceListChanged(devices)
421
+                                .then(() => {
422
+                                    APP.UI.onAvailableDevicesChanged(devices);
423
+                                });
424
+                        }, 0);
425
+                    });
382 426
             }
383 427
             if (config.iAmRecorder)
384 428
                 this.recorder = new Recorder();
@@ -388,6 +432,164 @@ export default {
388 432
             return new Promise((resolve, reject) => {
389 433
                 (new ConferenceConnector(resolve, reject)).connect();
390 434
             });
435
+
436
+            function checkAudioOutputDeviceAfterDeviceListChanged(newDevices) {
437
+                if (!JitsiMeetJS.mediaDevices
438
+                        .isDeviceChangeAvailable('output')) {
439
+                    return;
440
+                }
441
+
442
+                var selectedAudioOutputDeviceId =
443
+                        APP.settings.getAudioOutputDeviceId(),
444
+                    availableAudioOutputDevices = newDevices.filter(d => {
445
+                        return d.kind === 'audiooutput';
446
+                    });
447
+
448
+                if (selectedAudioOutputDeviceId !== 'default' &&
449
+                    !availableAudioOutputDevices.find(d =>
450
+                    d.deviceId === selectedAudioOutputDeviceId)) {
451
+                    APP.settings.setAudioOutputDeviceId('default');
452
+                }
453
+            }
454
+
455
+            function checkLocalDevicesAfterDeviceListChanged(newDevices) {
456
+                checkAudioOutputDeviceAfterDeviceListChanged(newDevices);
457
+
458
+                let availableAudioInputDevices = newDevices.filter(
459
+                        d => d.kind === 'audioinput'),
460
+                    availableVideoInputDevices = newDevices.filter(
461
+                        d => d.kind === 'videoinput'),
462
+                    selectedAudioInputDeviceId = APP.settings.getMicDeviceId(),
463
+                    selectedVideoInputDeviceId =
464
+                        APP.settings.getCameraDeviceId(),
465
+                    selectedAudioInputDevice = availableAudioInputDevices.find(
466
+                        d => d.deviceId === selectedAudioInputDeviceId),
467
+                    selectedVideoInputDevice = availableVideoInputDevices.find(
468
+                        d => d.deviceId === selectedVideoInputDeviceId),
469
+                    tracksToCreate = [],
470
+                    micIdToUse = null,
471
+                    cameraIdToUse = null;
472
+
473
+                // Here we handle case when no device was initially plugged, but
474
+                // then it's connected OR new device was connected when previous
475
+                // track has ended.
476
+                if (!localAudio || localAudio.disposed || localAudio.isEnded()){
477
+                    if (availableAudioInputDevices.length
478
+                        && availableAudioInputDevices[0].label !== '') {
479
+                        tracksToCreate.push('audio');
480
+                        micIdToUse = availableAudioInputDevices[0].deviceId;
481
+                    } else {
482
+                        APP.UI.disableMicrophoneButton();
483
+                    }
484
+                }
485
+
486
+                if ((!localVideo || localVideo.disposed || localVideo.isEnded())
487
+                    && !self.isSharingScreen){
488
+                    if (availableVideoInputDevices.length
489
+                        && availableVideoInputDevices[0].label !== '') {
490
+                        tracksToCreate.push('video');
491
+                        cameraIdToUse = availableVideoInputDevices[0].deviceId;
492
+                    } else {
493
+                        APP.UI.disableCameraButton();
494
+                    }
495
+                }
496
+
497
+                if (localAudio && !localAudio.disposed && !localAudio.isEnded()
498
+                    && selectedAudioInputDevice
499
+                    && selectedAudioInputDeviceId !== localAudio.getDeviceId()
500
+                    && tracksToCreate.indexOf('audio') === -1) {
501
+                    tracksToCreate.push('audio');
502
+                    micIdToUse = selectedAudioInputDeviceId;
503
+                }
504
+
505
+                if (localVideo && !localVideo.disposed && !localVideo.isEnded()
506
+                    && selectedVideoInputDevice
507
+                    && selectedVideoInputDeviceId !== localVideo.getDeviceId()
508
+                    && tracksToCreate.indexOf('video') === -1
509
+                    && !self.isSharingScreen) {
510
+                    tracksToCreate.push('video');
511
+                    cameraIdToUse = selectedVideoInputDeviceId;
512
+                }
513
+
514
+                if (tracksToCreate.length) {
515
+                    return createNewTracks(
516
+                        tracksToCreate, cameraIdToUse, micIdToUse);
517
+                } else {
518
+                    return Promise.resolve();
519
+                }
520
+
521
+                function createNewTracks(type, cameraDeviceId, micDeviceId) {
522
+                    return createLocalTracks(type, cameraDeviceId, micDeviceId)
523
+                        .then(onTracksCreated)
524
+                        .catch(() => {
525
+                            // if we tried to create both audio and video tracks
526
+                            // at once and failed, let's try again only with
527
+                            // audio. Such situation may happen in case if we
528
+                            // granted access only to microphone, but not to
529
+                            // camera.
530
+                            if (type.indexOf('audio') !== -1
531
+                                && type.indexOf('video') !== -1) {
532
+                                return createLocalTracks(['audio'], null,
533
+                                    micDeviceId);
534
+                            }
535
+
536
+                        })
537
+                        .then(onTracksCreated)
538
+                        .catch(() => {
539
+                            // if we tried to create both audio and video tracks
540
+                            // at once and failed, let's try again only with
541
+                            // video. Such situation may happen in case if we
542
+                            // granted access only to camera, but not to
543
+                            // microphone.
544
+                            if (type.indexOf('audio') !== -1
545
+                                && type.indexOf('video') !== -1) {
546
+                                return createLocalTracks(['video'],
547
+                                    cameraDeviceId,
548
+                                    null);
549
+                            }
550
+                        })
551
+                        .then(onTracksCreated)
552
+                        .catch(() => {
553
+                            // can't do anything in this case, so just ignore;
554
+                        });
555
+                }
556
+
557
+                function onTracksCreated(tracks) {
558
+                    tracks && tracks.forEach(track => {
559
+                        if (track.isAudioTrack()) {
560
+                            self.useAudioStream(track).then(() => {
561
+                                console.log('switched local audio');
562
+
563
+                                // If we have more than 1 device - mute.
564
+                                // We check with 2 for audio, because
565
+                                // it always has 'default' if device is
566
+                                // available at all.
567
+                                // TODO: this is not 100% solution - need
568
+                                // to investigate more
569
+                                if (availableAudioInputDevices.length > 2) {
570
+                                    muteLocalAudio(true);
571
+                                }
572
+                            });
573
+                        } else if (track.isVideoTrack()) {
574
+                            self.useVideoStream(track).then(() => {
575
+                                console.log('switched local video');
576
+
577
+                                // TODO: maybe make video large if we
578
+                                // are not in conference yet
579
+                                // If we have more than 1 device - mute.
580
+                                // TODO: this is not 100% solution - need
581
+                                // to investigate more
582
+                                if (availableVideoInputDevices.length > 1) {
583
+                                    muteLocalVideo(true);
584
+                                }
585
+                            });
586
+                        } else {
587
+                            console.error("Ignored not an audio nor a "
588
+                                + "video track: ", track);
589
+                        }
590
+                    });
591
+                }
592
+            }
391 593
         });
392 594
     },
393 595
     /**
@@ -667,6 +869,8 @@ export default {
667 869
                 this.isSharingScreen = false;
668 870
             }
669 871
 
872
+            stream.videoType === 'camera' && APP.UI.enableCameraButton();
873
+
670 874
             APP.UI.setVideoMuted(this.localId, this.videoMuted);
671 875
 
672 876
             APP.UI.updateDesktopSharingButtons();
@@ -701,6 +905,7 @@ export default {
701 905
                 this.audioMuted = false;
702 906
             }
703 907
 
908
+            APP.UI.enableMicrophoneButton();
704 909
             APP.UI.setAudioMuted(this.localId, this.audioMuted);
705 910
         });
706 911
     },
@@ -719,7 +924,7 @@ export default {
719 924
         this.videoSwitchInProgress = true;
720 925
 
721 926
         if (shareScreen) {
722
-            createLocalTracks('desktop').then(([stream]) => {
927
+            createLocalTracks(['desktop']).then(([stream]) => {
723 928
                 stream.on(
724 929
                     TrackEvents.LOCAL_TRACK_STOPPED,
725 930
                     () => {
@@ -767,7 +972,7 @@ export default {
767 972
                 );
768 973
             });
769 974
         } else {
770
-            createLocalTracks('video').then(
975
+            createLocalTracks(['video']).then(
771 976
                 ([stream]) => this.useVideoStream(stream)
772 977
             ).then(() => {
773 978
                 this.videoSwitchInProgress = false;
@@ -1118,7 +1323,7 @@ export default {
1118 1323
             UIEvents.VIDEO_DEVICE_CHANGED,
1119 1324
             (cameraDeviceId) => {
1120 1325
                 APP.settings.setCameraDeviceId(cameraDeviceId);
1121
-                createLocalTracks('video').then(([stream]) => {
1326
+                createLocalTracks(['video']).then(([stream]) => {
1122 1327
                     this.useVideoStream(stream);
1123 1328
                     console.log('switched local video device');
1124 1329
                 });
@@ -1129,7 +1334,7 @@ export default {
1129 1334
             UIEvents.AUDIO_DEVICE_CHANGED,
1130 1335
             (micDeviceId) => {
1131 1336
                 APP.settings.setMicDeviceId(micDeviceId);
1132
-                createLocalTracks('audio').then(([stream]) => {
1337
+                createLocalTracks(['audio']).then(([stream]) => {
1133 1338
                     this.useAudioStream(stream);
1134 1339
                     console.log('switched local audio device');
1135 1340
                 });

+ 4
- 0
css/main.css 查看文件

@@ -58,6 +58,10 @@ html, body{
58 58
     vertical-align: middle;
59 59
 }
60 60
 
61
+.button[disabled] {
62
+    opacity: 0.5;
63
+}
64
+
61 65
 .toolbar_span>span {
62 66
     display: inline-block;
63 67
     position: absolute;

+ 6
- 2
lang/main.json 查看文件

@@ -66,7 +66,9 @@
66 66
         "dialpad": "Show dialpad",
67 67
         "sharedVideoMutedPopup": "Your shared video has been muted so<br/>that you can talk to the other participants.",
68 68
         "micMutedPopup": "Your microphone has been muted so that you<br/>would fully enjoy your shared video.",
69
-        "unableToUnmutePopup": "You cannot un-mute while the shared video is on."
69
+        "unableToUnmutePopup": "You cannot un-mute while the shared video is on.",
70
+        "cameraDisabled": "Camera is not available",
71
+        "micDisabled": "Microphone is not available"
70 72
     },
71 73
     "bottomtoolbar": {
72 74
         "chat": "Open / close chat",
@@ -90,7 +92,9 @@
90 92
         "selectCamera": "Select camera",
91 93
         "selectMic": "Select microphone",
92 94
         "selectAudioOutput": "Select audio output",
93
-        "followMe": "Enable follow me"
95
+        "followMe": "Enable follow me",
96
+        "noDevice": "None",
97
+        "noPermission": "Permission to use device is not granted"
94 98
     },
95 99
     "videothumbnail":
96 100
     {

+ 28
- 0
modules/UI/UI.js 查看文件

@@ -1142,4 +1142,32 @@ UI.onSharedVideoStop = function (id, attributes) {
1142 1142
         sharedVideoManager.onSharedVideoStop(id, attributes);
1143 1143
 };
1144 1144
 
1145
+/**
1146
+ * Disables camera toolbar button.
1147
+ */
1148
+UI.disableCameraButton = function () {
1149
+    Toolbar.markVideoIconAsDisabled(true);
1150
+};
1151
+
1152
+/**
1153
+ * Enables camera toolbar button.
1154
+ */
1155
+UI.enableCameraButton = function () {
1156
+    Toolbar.markVideoIconAsDisabled(false);
1157
+};
1158
+
1159
+/**
1160
+ * Disables microphone toolbar button.
1161
+ */
1162
+UI.disableMicrophoneButton = function () {
1163
+    Toolbar.markAudioIconAsDisabled(true);
1164
+};
1165
+
1166
+/**
1167
+ * Enables microphone toolbar button.
1168
+ */
1169
+UI.enableMicrophoneButton = function () {
1170
+    Toolbar.markAudioIconAsDisabled(false);
1171
+};
1172
+
1145 1173
 module.exports = UI;

+ 79
- 46
modules/UI/side_pannels/settings/SettingsMenu.js 查看文件

@@ -30,10 +30,16 @@ function generateLanguagesOptions(items, currentLang) {
30 30
  * Generate html select options for available physical devices.
31 31
  * @param {{ deviceId, label }[]} items available devices
32 32
  * @param {string} [selectedId] id of selected device
33
+ * @param {boolean} permissionGranted if permission to use selected device type
34
+ *      is granted
33 35
  * @returns {string}
34 36
  */
35
-function generateDevicesOptions(items, selectedId) {
36
-    return items.map(function (item) {
37
+function generateDevicesOptions(items, selectedId, permissionGranted) {
38
+    if (!permissionGranted && items.length) {
39
+        return '<option data-i18n="settings.noPermission"></option>';
40
+    }
41
+
42
+    var options = items.map(function (item) {
37 43
         let attrs = {
38 44
             value: item.deviceId
39 45
         };
@@ -44,7 +50,13 @@ function generateDevicesOptions(items, selectedId) {
44 50
 
45 51
         let attrsStr = UIUtil.attrsToString(attrs);
46 52
         return `<option ${attrsStr}>${item.label}</option>`;
47
-    }).join('\n');
53
+    });
54
+
55
+    if (!items.length) {
56
+        options.unshift('<option data-i18n="settings.noDevice"></option>');
57
+    }
58
+
59
+    return options.join('');
48 60
 }
49 61
 
50 62
 
@@ -111,26 +123,30 @@ export default {
111 123
 
112 124
 
113 125
         // DEVICES LIST
114
-        this.changeDevicesList([]);
115
-        $('#selectCamera').change(function () {
116
-            let cameraDeviceId = $(this).val();
117
-            if (cameraDeviceId !== Settings.getCameraDeviceId()) {
118
-                emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
119
-            }
120
-        });
121
-        $('#selectMic').change(function () {
122
-            let micDeviceId = $(this).val();
123
-            if (micDeviceId !== Settings.getMicDeviceId()) {
124
-                emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
125
-            }
126
-        });
127
-        $('#selectAudioOutput').change(function () {
128
-            let audioOutputDeviceId = $(this).val();
129
-            if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
130
-                emitter.emit(UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
131
-                    audioOutputDeviceId);
132
-            }
133
-        });
126
+        if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
127
+            JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
128
+            this.changeDevicesList([]);
129
+
130
+            $('#selectCamera').change(function () {
131
+                let cameraDeviceId = $(this).val();
132
+                if (cameraDeviceId !== Settings.getCameraDeviceId()) {
133
+                    emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
134
+                }
135
+            });
136
+            $('#selectMic').change(function () {
137
+                let micDeviceId = $(this).val();
138
+                if (micDeviceId !== Settings.getMicDeviceId()) {
139
+                    emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
140
+                }
141
+            });
142
+            $('#selectAudioOutput').change(function () {
143
+                let audioOutputDeviceId = $(this).val();
144
+                if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
145
+                    emitter.emit(UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
146
+                        audioOutputDeviceId);
147
+                }
148
+            });
149
+        }
134 150
     },
135 151
 
136 152
     /**
@@ -193,41 +209,58 @@ export default {
193 209
      * @param {{ deviceId, label, kind }[]} devices list of available devices
194 210
      */
195 211
     changeDevicesList (devices) {
196
-        let $devicesOptions =  $('#devicesOptions');
197
-
198
-        if (!devices.length) {
199
-            $devicesOptions.hide();
200
-            return;
201
-        }
202
-
203 212
         let $selectCamera= $('#selectCamera'),
204 213
             $selectMic = $('#selectMic'),
205 214
             $selectAudioOutput = $('#selectAudioOutput'),
206 215
             $selectAudioOutputParent = $selectAudioOutput.parent();
207 216
 
208
-        let audio = devices.filter(device => device.kind === 'audioinput');
209
-        let video = devices.filter(device => device.kind === 'videoinput');
210
-        let audioOutput = devices
211
-            .filter(device => device.kind === 'audiooutput');
217
+        let audio = devices.filter(device => device.kind === 'audioinput'),
218
+            video = devices.filter(device => device.kind === 'videoinput'),
219
+            audioOutput = devices
220
+                .filter(device => device.kind === 'audiooutput'),
221
+            selectedAudioDevice = audio.find(
222
+                d => d.deviceId === Settings.getMicDeviceId()) || audio[0],
223
+            selectedVideoDevice = video.find(
224
+                d => d.deviceId === Settings.getCameraDeviceId()) || video[0],
225
+            selectedAudioOutputDevice = audioOutput.find(
226
+                    d => d.deviceId === Settings.getAudioOutputDeviceId()),
227
+            videoPermissionGranted =
228
+                JitsiMeetJS.mediaDevices.isDevicePermissionGranted('video'),
229
+            audioPermissionGranted =
230
+                JitsiMeetJS.mediaDevices.isDevicePermissionGranted('audio');
212 231
 
213
-        $selectCamera.html(
214
-            generateDevicesOptions(video, Settings.getCameraDeviceId())
215
-        );
216
-        $selectMic.html(
217
-            generateDevicesOptions(audio, Settings.getMicDeviceId())
218
-        );
232
+        $selectCamera
233
+            .html(generateDevicesOptions(
234
+                video,
235
+                selectedVideoDevice ? selectedVideoDevice.deviceId : '',
236
+                videoPermissionGranted))
237
+            .prop('disabled', !video.length || !videoPermissionGranted);
219 238
 
220
-        if (audioOutput.length &&
221
-            JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
222
-            $selectAudioOutput.html(
223
-                generateDevicesOptions(audioOutput,
224
-                    Settings.getAudioOutputDeviceId()));
239
+        $selectMic
240
+            .html(generateDevicesOptions(
241
+                audio,
242
+                selectedAudioDevice ? selectedAudioDevice.deviceId : '',
243
+                audioPermissionGranted))
244
+            .prop('disabled', !audio.length || !audioPermissionGranted);
245
+
246
+        if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
247
+            $selectAudioOutput
248
+                .html(generateDevicesOptions(
249
+                    audioOutput,
250
+                    selectedAudioOutputDevice
251
+                        ? selectedAudioOutputDevice.deviceId
252
+                        : 'default',
253
+                    videoPermissionGranted || audioPermissionGranted))
254
+                .prop('disabled', !audioOutput.length ||
255
+                    (!videoPermissionGranted && !audioPermissionGranted));
225 256
 
226 257
             $selectAudioOutputParent.show();
227 258
         } else {
228 259
             $selectAudioOutputParent.hide();
229 260
         }
230 261
 
231
-        $devicesOptions.show();
262
+        $('#devicesOptions').show();
263
+
264
+        APP.translation.translateElement($('#settingsmenu option'));
232 265
     }
233 266
 };

+ 49
- 1
modules/UI/toolbars/Toolbar.js 查看文件

@@ -191,7 +191,9 @@ const Toolbar = {
191 191
         UIUtil.hideDisabledButtons(defaultToolbarButtons);
192 192
 
193 193
         Object.keys(buttonHandlers).forEach(
194
-            buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
194
+            buttonId => $(`#${buttonId}`).click(function(event) {
195
+                !$(this).prop('disabled') && buttonHandlers[buttonId](event);
196
+            })
195 197
         );
196 198
     },
197 199
     /**
@@ -361,6 +363,29 @@ const Toolbar = {
361 363
         $('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
362 364
     },
363 365
 
366
+    /**
367
+     * Marks video icon as disabled or not.
368
+     * @param {boolean} disabled if icon should look like disabled or not
369
+     */
370
+    markVideoIconAsDisabled (disabled) {
371
+        var $btn = $('#toolbar_button_camera');
372
+
373
+        $btn
374
+            .prop("disabled", disabled)
375
+            .attr("data-i18n", disabled
376
+                ? "[content]toolbar.cameraDisabled"
377
+                : "[content]toolbar.videomute")
378
+            .attr("shortcut", disabled ? "" : "toggleVideoPopover");
379
+
380
+        disabled
381
+            ? $btn.attr("disabled", "disabled")
382
+            : $btn.removeAttr("disabled");
383
+
384
+        APP.translation.translateElement($btn);
385
+
386
+        disabled && this.markVideoIconAsMuted(disabled);
387
+    },
388
+
364 389
     /**
365 390
      * Marks audio icon as muted or not.
366 391
      * @param {boolean} muted if icon should look like muted or not
@@ -370,6 +395,29 @@ const Toolbar = {
370 395
             !muted).toggleClass("icon-mic-disabled", muted);
371 396
     },
372 397
 
398
+    /**
399
+     * Marks audio icon as disabled or not.
400
+     * @param {boolean} disabled if icon should look like disabled or not
401
+     */
402
+    markAudioIconAsDisabled (disabled) {
403
+        var $btn = $('#toolbar_button_mute');
404
+
405
+        $btn
406
+            .prop("disabled", disabled)
407
+            .attr("data-i18n", disabled
408
+                ? "[content]toolbar.micDisabled"
409
+                : "[content]toolbar.mute")
410
+            .attr("shortcut", disabled ? "" : "mutePopover");
411
+
412
+        disabled
413
+            ? $btn.attr("disabled", "disabled")
414
+            : $btn.removeAttr("disabled");
415
+
416
+        APP.translation.translateElement($btn);
417
+
418
+        disabled && this.markAudioIconAsMuted(disabled);
419
+    },
420
+
373 421
     /**
374 422
      * Indicates if the toolbar is currently hovered.
375 423
      * @return {true} if the toolbar is currently hovered, {false} otherwise

+ 11
- 8
modules/settings/Settings.js 查看文件

@@ -43,12 +43,15 @@ if (supportsLocalStorage()) {
43 43
         window.localStorage.welcomePageDisabled || false
44 44
     );
45 45
 
46
-    var audioOutputDeviceId = window.localStorage.audioOutputDeviceId;
47
-
48
-    if (typeof audioOutputDeviceId !== 'undefined' && audioOutputDeviceId !==
49
-            JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
50
-        JitsiMeetJS.mediaDevices.setAudioOutputDevice(
51
-            window.localStorage.audioOutputDeviceId).catch((ex) => {
46
+    // Currently audio output device change is supported only in Chrome and
47
+    // default output always has 'default' device ID
48
+    var audioOutputDeviceId = window.localStorage.audioOutputDeviceId
49
+        || 'default';
50
+
51
+    if (audioOutputDeviceId !==
52
+        JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
53
+        JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId)
54
+            .catch((ex) => {
52 55
                 console.error('failed to set audio output device from local ' +
53 56
                     'storage', ex);
54 57
             });
@@ -166,10 +169,10 @@ export default {
166 169
     /**
167 170
      * Set device id of the audio output device which is currently in use.
168 171
      * Empty string stands for default device.
169
-     * @param {string} newId new audio output device id
172
+     * @param {string} newId='default' - new audio output device id
170 173
      * @returns {Promise}
171 174
      */
172
-    setAudioOutputDeviceId: function (newId = '') {
175
+    setAudioOutputDeviceId: function (newId = 'default') {
173 176
         return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
174 177
             .then(() => window.localStorage.audioOutputDeviceId = newId);
175 178
     },

Loading…
取消
儲存