Sfoglia il codice sorgente

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

j8
Kostiantyn Tsaregradskyi 9 anni fa
parent
commit
b270256a7a

+ 218
- 13
conference.js Vedi File

170
 
170
 
171
 /**
171
 /**
172
  * Create local tracks of specified types.
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
  * @returns {Promise<JitsiLocalTrack[]>}
178
  * @returns {Promise<JitsiLocalTrack[]>}
175
  */
179
  */
176
-function createLocalTracks (...devices) {
180
+function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
177
     return JitsiMeetJS.createLocalTracks({
181
     return JitsiMeetJS.createLocalTracks({
178
         // copy array to avoid mutations inside library
182
         // copy array to avoid mutations inside library
179
         devices: devices.slice(0),
183
         devices: devices.slice(0),
180
         resolution: config.resolution,
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
         // adds any ff fake device settings if any
192
         // adds any ff fake device settings if any
184
         firefox_fake_device: config.firefox_fake_device
193
         firefox_fake_device: config.firefox_fake_device
185
     }).catch(function (err) {
194
     }).catch(function (err) {
327
      * @returns {Promise}
336
      * @returns {Promise}
328
      */
337
      */
329
     init(options) {
338
     init(options) {
339
+        let self = this;
330
         this.roomName = options.roomName;
340
         this.roomName = options.roomName;
331
         JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
341
         JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
332
 
342
 
356
         return JitsiMeetJS.init(config).then(() => {
366
         return JitsiMeetJS.init(config).then(() => {
357
             return Promise.all([
367
             return Promise.all([
358
                 // try to retrieve audio and video
368
                 // try to retrieve audio and video
359
-                createLocalTracks('audio', 'video')
369
+                createLocalTracks(['audio', 'video'])
360
                 // if failed then try to retrieve only audio
370
                 // if failed then try to retrieve only audio
361
-                    .catch(() => createLocalTracks('audio'))
371
+                    .catch(() => createLocalTracks(['audio']))
362
                 // if audio also failed then just return empty array
372
                 // if audio also failed then just return empty array
363
                     .catch(() => []),
373
                     .catch(() => []),
364
                 connect(options.roomName)
374
                 connect(options.roomName)
370
             this.isDesktopSharingEnabled =
380
             this.isDesktopSharingEnabled =
371
                 JitsiMeetJS.isDesktopSharingEnabled();
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
             // update list of available devices
393
             // update list of available devices
374
             if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
394
             if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
375
                 JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
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
                 JitsiMeetJS.mediaDevices.addEventListener(
414
                 JitsiMeetJS.mediaDevices.addEventListener(
380
                     JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
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
             if (config.iAmRecorder)
427
             if (config.iAmRecorder)
384
                 this.recorder = new Recorder();
428
                 this.recorder = new Recorder();
388
             return new Promise((resolve, reject) => {
432
             return new Promise((resolve, reject) => {
389
                 (new ConferenceConnector(resolve, reject)).connect();
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
                 this.isSharingScreen = false;
869
                 this.isSharingScreen = false;
668
             }
870
             }
669
 
871
 
872
+            stream.videoType === 'camera' && APP.UI.enableCameraButton();
873
+
670
             APP.UI.setVideoMuted(this.localId, this.videoMuted);
874
             APP.UI.setVideoMuted(this.localId, this.videoMuted);
671
 
875
 
672
             APP.UI.updateDesktopSharingButtons();
876
             APP.UI.updateDesktopSharingButtons();
701
                 this.audioMuted = false;
905
                 this.audioMuted = false;
702
             }
906
             }
703
 
907
 
908
+            APP.UI.enableMicrophoneButton();
704
             APP.UI.setAudioMuted(this.localId, this.audioMuted);
909
             APP.UI.setAudioMuted(this.localId, this.audioMuted);
705
         });
910
         });
706
     },
911
     },
719
         this.videoSwitchInProgress = true;
924
         this.videoSwitchInProgress = true;
720
 
925
 
721
         if (shareScreen) {
926
         if (shareScreen) {
722
-            createLocalTracks('desktop').then(([stream]) => {
927
+            createLocalTracks(['desktop']).then(([stream]) => {
723
                 stream.on(
928
                 stream.on(
724
                     TrackEvents.LOCAL_TRACK_STOPPED,
929
                     TrackEvents.LOCAL_TRACK_STOPPED,
725
                     () => {
930
                     () => {
767
                 );
972
                 );
768
             });
973
             });
769
         } else {
974
         } else {
770
-            createLocalTracks('video').then(
975
+            createLocalTracks(['video']).then(
771
                 ([stream]) => this.useVideoStream(stream)
976
                 ([stream]) => this.useVideoStream(stream)
772
             ).then(() => {
977
             ).then(() => {
773
                 this.videoSwitchInProgress = false;
978
                 this.videoSwitchInProgress = false;
1118
             UIEvents.VIDEO_DEVICE_CHANGED,
1323
             UIEvents.VIDEO_DEVICE_CHANGED,
1119
             (cameraDeviceId) => {
1324
             (cameraDeviceId) => {
1120
                 APP.settings.setCameraDeviceId(cameraDeviceId);
1325
                 APP.settings.setCameraDeviceId(cameraDeviceId);
1121
-                createLocalTracks('video').then(([stream]) => {
1326
+                createLocalTracks(['video']).then(([stream]) => {
1122
                     this.useVideoStream(stream);
1327
                     this.useVideoStream(stream);
1123
                     console.log('switched local video device');
1328
                     console.log('switched local video device');
1124
                 });
1329
                 });
1129
             UIEvents.AUDIO_DEVICE_CHANGED,
1334
             UIEvents.AUDIO_DEVICE_CHANGED,
1130
             (micDeviceId) => {
1335
             (micDeviceId) => {
1131
                 APP.settings.setMicDeviceId(micDeviceId);
1336
                 APP.settings.setMicDeviceId(micDeviceId);
1132
-                createLocalTracks('audio').then(([stream]) => {
1337
+                createLocalTracks(['audio']).then(([stream]) => {
1133
                     this.useAudioStream(stream);
1338
                     this.useAudioStream(stream);
1134
                     console.log('switched local audio device');
1339
                     console.log('switched local audio device');
1135
                 });
1340
                 });

+ 4
- 0
css/main.css Vedi File

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

+ 6
- 2
lang/main.json Vedi File

66
         "dialpad": "Show dialpad",
66
         "dialpad": "Show dialpad",
67
         "sharedVideoMutedPopup": "Your shared video has been muted so<br/>that you can talk to the other participants.",
67
         "sharedVideoMutedPopup": "Your shared video has been muted so<br/>that you can talk to the other participants.",
68
         "micMutedPopup": "Your microphone has been muted so that you<br/>would fully enjoy your shared video.",
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
     "bottomtoolbar": {
73
     "bottomtoolbar": {
72
         "chat": "Open / close chat",
74
         "chat": "Open / close chat",
90
         "selectCamera": "Select camera",
92
         "selectCamera": "Select camera",
91
         "selectMic": "Select microphone",
93
         "selectMic": "Select microphone",
92
         "selectAudioOutput": "Select audio output",
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
     "videothumbnail":
99
     "videothumbnail":
96
     {
100
     {

+ 28
- 0
modules/UI/UI.js Vedi File

1142
         sharedVideoManager.onSharedVideoStop(id, attributes);
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
 module.exports = UI;
1173
 module.exports = UI;

+ 79
- 46
modules/UI/side_pannels/settings/SettingsMenu.js Vedi File

30
  * Generate html select options for available physical devices.
30
  * Generate html select options for available physical devices.
31
  * @param {{ deviceId, label }[]} items available devices
31
  * @param {{ deviceId, label }[]} items available devices
32
  * @param {string} [selectedId] id of selected device
32
  * @param {string} [selectedId] id of selected device
33
+ * @param {boolean} permissionGranted if permission to use selected device type
34
+ *      is granted
33
  * @returns {string}
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
         let attrs = {
43
         let attrs = {
38
             value: item.deviceId
44
             value: item.deviceId
39
         };
45
         };
44
 
50
 
45
         let attrsStr = UIUtil.attrsToString(attrs);
51
         let attrsStr = UIUtil.attrsToString(attrs);
46
         return `<option ${attrsStr}>${item.label}</option>`;
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
 
123
 
112
 
124
 
113
         // DEVICES LIST
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
      * @param {{ deviceId, label, kind }[]} devices list of available devices
209
      * @param {{ deviceId, label, kind }[]} devices list of available devices
194
      */
210
      */
195
     changeDevicesList (devices) {
211
     changeDevicesList (devices) {
196
-        let $devicesOptions =  $('#devicesOptions');
197
-
198
-        if (!devices.length) {
199
-            $devicesOptions.hide();
200
-            return;
201
-        }
202
-
203
         let $selectCamera= $('#selectCamera'),
212
         let $selectCamera= $('#selectCamera'),
204
             $selectMic = $('#selectMic'),
213
             $selectMic = $('#selectMic'),
205
             $selectAudioOutput = $('#selectAudioOutput'),
214
             $selectAudioOutput = $('#selectAudioOutput'),
206
             $selectAudioOutputParent = $selectAudioOutput.parent();
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
             $selectAudioOutputParent.show();
257
             $selectAudioOutputParent.show();
227
         } else {
258
         } else {
228
             $selectAudioOutputParent.hide();
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 Vedi File

191
         UIUtil.hideDisabledButtons(defaultToolbarButtons);
191
         UIUtil.hideDisabledButtons(defaultToolbarButtons);
192
 
192
 
193
         Object.keys(buttonHandlers).forEach(
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
         $('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
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
      * Marks audio icon as muted or not.
390
      * Marks audio icon as muted or not.
366
      * @param {boolean} muted if icon should look like muted or not
391
      * @param {boolean} muted if icon should look like muted or not
370
             !muted).toggleClass("icon-mic-disabled", muted);
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
      * Indicates if the toolbar is currently hovered.
422
      * Indicates if the toolbar is currently hovered.
375
      * @return {true} if the toolbar is currently hovered, {false} otherwise
423
      * @return {true} if the toolbar is currently hovered, {false} otherwise

+ 11
- 8
modules/settings/Settings.js Vedi File

43
         window.localStorage.welcomePageDisabled || false
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
                 console.error('failed to set audio output device from local ' +
55
                 console.error('failed to set audio output device from local ' +
53
                     'storage', ex);
56
                     'storage', ex);
54
             });
57
             });
166
     /**
169
     /**
167
      * Set device id of the audio output device which is currently in use.
170
      * Set device id of the audio output device which is currently in use.
168
      * Empty string stands for default device.
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
      * @returns {Promise}
173
      * @returns {Promise}
171
      */
174
      */
172
-    setAudioOutputDeviceId: function (newId = '') {
175
+    setAudioOutputDeviceId: function (newId = 'default') {
173
         return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
176
         return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
174
             .then(() => window.localStorage.audioOutputDeviceId = newId);
177
             .then(() => window.localStorage.audioOutputDeviceId = newId);
175
     },
178
     },

Loading…
Annulla
Salva