浏览代码

allow user to select camera and microphone

j8
isymchych 9 年前
父节点
当前提交
f65d630ad8

+ 105
- 40
conference.js 查看文件

@@ -19,7 +19,7 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
19 19
 const TrackEvents = JitsiMeetJS.events.track;
20 20
 const TrackErrors = JitsiMeetJS.errors.track;
21 21
 
22
-let room, connection, localTracks, localAudio, localVideo, roomLocker;
22
+let room, connection, localAudio, localVideo, roomLocker;
23 23
 
24 24
 /**
25 25
  * Known custom conference commands.
@@ -120,6 +120,8 @@ function createLocalTracks (...devices) {
120 120
         // copy array to avoid mutations inside library
121 121
         devices: devices.slice(0),
122 122
         resolution: config.resolution,
123
+        cameraDeviceId: APP.settings.getCameraDeviceId(),
124
+        micDeviceId: APP.settings.getMicDeviceId(),
123 125
         // adds any ff fake device settings if any
124 126
         firefox_fake_device: config.firefox_fake_device
125 127
     }).catch(function (err) {
@@ -293,11 +295,19 @@ export default {
293 295
             ]);
294 296
         }).then(([tracks, con]) => {
295 297
             console.log('initialized with %s local tracks', tracks.length);
296
-            localTracks = tracks;
297 298
             connection = con;
298
-            this._createRoom();
299
+            this._createRoom(tracks);
299 300
             this.isDesktopSharingEnabled =
300 301
                 JitsiMeetJS.isDesktopSharingEnabled();
302
+
303
+            // update list of available devices
304
+            if (JitsiMeetJS.isDeviceListAvailable() &&
305
+                JitsiMeetJS.isDeviceChangeAvailable()) {
306
+                JitsiMeetJS.enumerateDevices((devices) => {
307
+                    this.availableDevices = devices;
308
+                    APP.UI.onAvailableDevicesChanged();
309
+                });
310
+            }
301 311
             // XXX The API will take care of disconnecting from the XMPP server
302 312
             // (and, thus, leaving the room) on unload.
303 313
             return new Promise((resolve, reject) => {
@@ -360,6 +370,10 @@ export default {
360 370
     listMembersIds () {
361 371
         return room.getParticipants().map(p => p.getId());
362 372
     },
373
+    /**
374
+     * List of available cameras and microphones.
375
+     */
376
+    availableDevices: [],
363 377
     /**
364 378
      * Check if SIP is supported.
365 379
      * @returns {boolean}
@@ -449,32 +463,30 @@ export default {
449 463
     getLogs () {
450 464
         return room.getLogs();
451 465
     },
452
-    _createRoom () {
466
+    _createRoom (localTracks) {
453 467
         room = connection.initJitsiConference(APP.conference.roomName,
454 468
             this._getConferenceOptions());
455 469
         this.localId = room.myUserId();
456 470
         localTracks.forEach((track) => {
457
-            if(track.isAudioTrack()) {
458
-                localAudio = track;
459
-            }
460
-            else if (track.isVideoTrack()) {
461
-                localVideo = track;
462
-            }
463 471
             room.addTrack(track);
464
-            APP.UI.addLocalStream(track);
472
+
473
+            if (track.isAudioTrack()) {
474
+                this.useAudioStream(track);
475
+            } else if (track.isVideoTrack()) {
476
+                this.useVideoStream(track);
477
+            }
465 478
         });
466 479
         roomLocker = createRoomLocker(room);
467 480
         this._room = room; // FIXME do not use this
468
-        this.localId = room.myUserId();
469 481
 
470 482
         let email = APP.settings.getEmail();
471 483
         email && sendEmail(email);
472 484
 
473 485
         let nick = APP.settings.getDisplayName();
474
-        (config.useNicks && !nick) && (() => {
486
+        if (config.useNicks && !nick) {
475 487
             nick = APP.UI.askForNickname();
476 488
             APP.settings.setDisplayName(nick);
477
-        })();
489
+        }
478 490
         nick && room.setDisplayName(nick);
479 491
 
480 492
         this._setupListeners();
@@ -489,6 +501,55 @@ export default {
489 501
         return options;
490 502
     },
491 503
 
504
+    /**
505
+     * Start using provided video stream.
506
+     * Stops previous video stream.
507
+     * @param {JitsiLocalTrack} [stream] new stream to use or null
508
+     */
509
+    useVideoStream (stream) {
510
+        if (localVideo) {
511
+            localVideo.stop();
512
+        }
513
+        localVideo = stream;
514
+
515
+        if (stream) {
516
+            this.videoMuted = stream.isMuted();
517
+
518
+            APP.UI.addLocalStream(stream);
519
+
520
+            this.isSharingScreen = stream.videoType === 'desktop';
521
+        } else {
522
+            this.videoMuted = false;
523
+            this.isSharingScreen = false;
524
+        }
525
+
526
+        APP.UI.setVideoMuted(this.localId, this.videoMuted);
527
+
528
+        APP.UI.updateDesktopSharingButtons();
529
+    },
530
+
531
+    /**
532
+     * Start using provided audio stream.
533
+     * Stops previous audio stream.
534
+     * @param {JitsiLocalTrack} [stream] new stream to use or null
535
+     */
536
+    useAudioStream (stream) {
537
+        if (localAudio) {
538
+            localAudio.stop();
539
+        }
540
+        localAudio = stream;
541
+
542
+        if (stream) {
543
+            this.audioMuted = stream.isMuted();
544
+
545
+            APP.UI.addLocalStream(stream);
546
+        } else {
547
+            this.audioMuted = false;
548
+        }
549
+
550
+        APP.UI.setAudioMuted(this.localId, this.audioMuted);
551
+    },
552
+
492 553
     videoSwitchInProgress: false,
493 554
     toggleScreenSharing () {
494 555
         if (this.videoSwitchInProgress) {
@@ -507,22 +568,13 @@ export default {
507 568
             createLocalTracks('video').then(function ([stream]) {
508 569
                 return room.addTrack(stream);
509 570
             }).then((stream) => {
510
-                if (localVideo) {
511
-                    localVideo.stop();
512
-                }
513
-                localVideo = stream;
514
-                this.videoMuted = stream.isMuted();
515
-                APP.UI.setVideoMuted(this.localId, this.videoMuted);
516
-
517
-                APP.UI.addLocalStream(stream);
571
+                this.useVideoStream(stream);
572
+                this.videoSwitchInProgress = false;
518 573
                 console.log('sharing local video');
519
-            }).catch((err) => {
520
-                localVideo = null;
521
-                console.error('failed to share local video', err);
522
-            }).then(() => {
574
+            }).catch(function (err) {
575
+                this.useVideoStream(null);
523 576
                 this.videoSwitchInProgress = false;
524
-                this.isSharingScreen = false;
525
-                APP.UI.updateDesktopSharingButtons();
577
+                console.error('failed to share local video', err);
526 578
             });
527 579
         } else {
528 580
             // stop sharing video and share desktop
@@ -541,19 +593,8 @@ export default {
541 593
                 );
542 594
                 return room.addTrack(stream);
543 595
             }).then((stream) => {
544
-                if (localVideo) {
545
-                    localVideo.stop();
546
-                }
547
-                localVideo = stream;
548
-
549
-                this.videoMuted = stream.isMuted();
550
-                APP.UI.setVideoMuted(this.localId, this.videoMuted);
551
-
552
-                APP.UI.addLocalStream(stream);
553
-
596
+                this.useVideoStream(stream);
554 597
                 this.videoSwitchInProgress = false;
555
-                this.isSharingScreen = true;
556
-                APP.UI.updateDesktopSharingButtons();
557 598
                 console.log('sharing local desktop');
558 599
             }).catch((err) => {
559 600
                 this.videoSwitchInProgress = false;
@@ -907,6 +948,30 @@ export default {
907 948
             room.pinParticipant(id);
908 949
         });
909 950
 
951
+        APP.UI.addListener(
952
+            UIEvents.VIDEO_DEVICE_CHANGED,
953
+            (cameraDeviceId) => {
954
+                APP.settings.setCameraDeviceId(cameraDeviceId);
955
+                createLocalTracks('video').then(([stream]) => {
956
+                    room.addTrack(stream);
957
+                    this.useVideoStream(stream);
958
+                    console.log('switched local video device');
959
+                });
960
+            }
961
+        );
962
+
963
+        APP.UI.addListener(
964
+            UIEvents.AUDIO_DEVICE_CHANGED,
965
+            (micDeviceId) => {
966
+                APP.settings.setMicDeviceId(micDeviceId);
967
+                createLocalTracks('audio').then(([stream]) => {
968
+                    room.addTrack(stream);
969
+                    this.useAudioStream(stream);
970
+                    console.log('switched local audio device');
971
+                });
972
+            }
973
+        );
974
+
910 975
         APP.UI.addListener(
911 976
             UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this)
912 977
         );

+ 22
- 0
css/settingsmenu.css 查看文件

@@ -1,6 +1,7 @@
1 1
 #settingsmenu {
2 2
     background: black;
3 3
     color: #00ccff;
4
+    overflow-y: auto;
4 5
 }
5 6
 
6 7
 #settingsmenu input, select {
@@ -52,6 +53,10 @@
52 53
 #startMutedOptions {
53 54
     padding-left: 10%;
54 55
     text-indent: -10%;
56
+
57
+    /* clearfix */
58
+    overflow: auto;
59
+    zoom: 1;
55 60
 }
56 61
 
57 62
 #startAudioMuted {
@@ -66,3 +71,20 @@
66 71
     width: 94%;
67 72
     float: left;
68 73
 }
74
+
75
+#devicesOptions {
76
+    display: none;
77
+}
78
+
79
+#devicesOptions label {
80
+    display: block;
81
+    margin-top: 15px;
82
+}
83
+
84
+#devicesOptions span {
85
+    padding-left: 10%;
86
+}
87
+
88
+#devicesOptions select {
89
+    height: 40px;
90
+}

+ 10
- 0
index.html 查看文件

@@ -231,6 +231,16 @@
231 231
                     <span data-i18n="settings.startVideoMuted"></span>
232 232
                 </label>
233 233
             </div>
234
+            <div id="devicesOptions">
235
+                <label className="devicesOptionsLabel">
236
+                    <span data-i18n="settings.selectCamera"></span>
237
+                    <select id="selectCamera"></select>
238
+                </label>
239
+                <label className="devicesOptionsLabel">
240
+                    <span data-i18n="settings.selectMic"></span>
241
+                    <select id="selectMic"></select>
242
+                </label>
243
+            </div>
234 244
             <button id="updateSettings" data-i18n="settings.update"></button>
235 245
             <a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
236 246
         </div>

+ 3
- 1
lang/main.json 查看文件

@@ -84,7 +84,9 @@
84 84
         "update": "Update",
85 85
         "name": "Name",
86 86
         "startAudioMuted": "start without audio",
87
-        "startVideoMuted": "start without video"
87
+        "startVideoMuted": "start without video",
88
+        "selectCamera": "select camera",
89
+        "selectMic": "select microphone"
88 90
     },
89 91
     "videothumbnail":
90 92
     {

+ 14
- 5
modules/UI/UI.js 查看文件

@@ -660,9 +660,9 @@ UI.askForNickname = function () {
660 660
  */
661 661
 UI.setAudioMuted = function (id, muted) {
662 662
     VideoLayout.onAudioMute(id, muted);
663
-    if(APP.conference.isLocalId(id))
664
-        UIUtil.buttonClick("#toolbar_button_mute",
665
-            "icon-microphone icon-mic-disabled");
663
+    if (APP.conference.isLocalId(id)) {
664
+        Toolbar.markAudioIconAsMuted(muted);
665
+    }
666 666
 };
667 667
 
668 668
 /**
@@ -670,8 +670,9 @@ UI.setAudioMuted = function (id, muted) {
670 670
  */
671 671
 UI.setVideoMuted = function (id, muted) {
672 672
     VideoLayout.onVideoMute(id, muted);
673
-    if(APP.conference.isLocalId(id))
674
-        $('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
673
+    if (APP.conference.isLocalId(id)) {
674
+        Toolbar.markVideoIconAsMuted(muted);
675
+    }
675 676
 };
676 677
 
677 678
 UI.addListener = function (type, listener) {
@@ -1040,6 +1041,14 @@ UI.onStartMutedChanged = function () {
1040 1041
     SettingsMenu.onStartMutedChanged();
1041 1042
 };
1042 1043
 
1044
+/**
1045
+ * Update list of available physical devices.
1046
+ * @param {object[]} devices new list of available devices
1047
+ */
1048
+UI.onAvailableDevicesChanged = function (devices) {
1049
+    SettingsMenu.onAvailableDevicesChanged(devices);
1050
+};
1051
+
1043 1052
 /**
1044 1053
  * Returns the id of the current video shown on large.
1045 1054
  * Currently used by tests (torture).

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

@@ -21,6 +21,21 @@ function generateLanguagesSelectBox() {
21 21
     return html + "</select>";
22 22
 }
23 23
 
24
+function generateDevicesOptions(items, selectedId) {
25
+    return items.map(function (item) {
26
+        let attrs = {
27
+            value: item.deviceId
28
+        };
29
+
30
+        if (item.deviceId === selectedId) {
31
+            attrs.selected = 'selected';
32
+        }
33
+
34
+        let attrsStr = UIUtil.attrsToString(attrs);
35
+        return `<option ${attrsStr}>${item.label}</option>`;
36
+    }).join('\n');
37
+}
38
+
24 39
 
25 40
 export default {
26 41
     init (emitter) {
@@ -51,12 +66,23 @@ export default {
51 66
                     startVideoMuted
52 67
                 );
53 68
             }
69
+
70
+            let cameraDeviceId = $('#selectCamera').val();
71
+            if (cameraDeviceId !== Settings.getCameraDeviceId()) {
72
+                emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
73
+            }
74
+
75
+            let micDeviceId = $('#selectMic').val();
76
+            if (micDeviceId !== Settings.getMicDeviceId()) {
77
+                emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
78
+            }
54 79
         }
55 80
 
56 81
         let startMutedBlock = $("#startMutedOptions");
57 82
         startMutedBlock.before(generateLanguagesSelectBox());
58 83
         APP.translation.translateElement($("#languages_selectbox"));
59 84
 
85
+        this.onAvailableDevicesChanged();
60 86
         this.onRoleChanged();
61 87
         this.onStartMutedChanged();
62 88
 
@@ -94,5 +120,25 @@ export default {
94 120
 
95 121
     changeAvatar (avatarUrl) {
96 122
         $('#avatar').attr('src', avatarUrl);
123
+    },
124
+
125
+    onAvailableDevicesChanged () {
126
+        let devices = APP.conference.availableDevices;
127
+        if (!devices.length) {
128
+            $('#devicesOptions').hide();
129
+            return;
130
+        }
131
+
132
+        let audio = devices.filter(device => device.kind === 'audioinput');
133
+        let video = devices.filter(device => device.kind === 'videoinput');
134
+
135
+        $('#selectCamera').html(
136
+            generateDevicesOptions(video, Settings.getCameraDeviceId())
137
+        );
138
+        $('#selectMic').html(
139
+            generateDevicesOptions(audio, Settings.getMicDeviceId())
140
+        );
141
+
142
+        $('#devicesOptions').show();
97 143
     }
98 144
 };

+ 16
- 0
modules/UI/toolbars/Toolbar.js 查看文件

@@ -385,6 +385,22 @@ const Toolbar = {
385 385
 
386 386
     updateRecordingState (state) {
387 387
         setRecordingButtonState(state);
388
+    },
389
+
390
+    /**
391
+     * Marks video icon as muted or not.
392
+     * @param {boolean} muted if icon should look like muted or not
393
+     */
394
+    markVideoIconAsMuted (muted) {
395
+        $('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
396
+    },
397
+
398
+    /**
399
+     * Marks audio icon as muted or not.
400
+     * @param {boolean} muted if icon should look like muted or not
401
+     */
402
+    markAudioIconAsMuted (muted) {
403
+        $('#toolbar_button_mute').toggleClass("icon-microphone", !muted).toggleClass("icon-mic-disabled", muted);
388 404
     }
389 405
 };
390 406
 

+ 11
- 0
modules/UI/util/UIUtil.js 查看文件

@@ -139,6 +139,17 @@
139 139
          return document.fullScreen
140 140
              || document.mozFullScreen
141 141
              || document.webkitIsFullScreen;
142
+     },
143
+
144
+     /**
145
+      * Create html attributes string out of object properties.
146
+      * @param {Object} attrs object with properties
147
+      * @returns {String} string of html element attributes
148
+      */
149
+     attrsToString: function (attrs) {
150
+         return Object.keys(attrs).map(
151
+             key => ` ${key}="${attrs[key]}"`
152
+         ).join(' ');
142 153
      }
143 154
 };
144 155
 

+ 5
- 4
modules/UI/videolayout/LocalVideo.js 查看文件

@@ -17,6 +17,11 @@ function LocalVideo(VideoLayout, emitter) {
17 17
     this.flipX = true;
18 18
     this.isLocal = true;
19 19
     this.emitter = emitter;
20
+    Object.defineProperty(this, 'id', {
21
+        get: function () {
22
+            return APP.conference.localId;
23
+        }
24
+    });
20 25
     SmallVideo.call(this);
21 26
 }
22 27
 
@@ -195,8 +200,4 @@ LocalVideo.prototype.changeVideo = function (stream) {
195 200
     stream.on(TrackEvents.TRACK_STOPPED, endedHandler);
196 201
 };
197 202
 
198
-LocalVideo.prototype.joined = function (id) {
199
-    this.id = id;
200
-};
201
-
202 203
 export default LocalVideo;

+ 1
- 4
modules/UI/videolayout/VideoLayout.js 查看文件

@@ -170,11 +170,8 @@ var VideoLayout = {
170 170
      * and setting them assume the id is already set.
171 171
      */
172 172
     mucJoined () {
173
-        let id = APP.conference.localId;
174
-        localVideoThumbnail.joined(id);
175
-
176 173
         if (largeVideo && !largeVideo.id) {
177
-            this.updateLargeVideo(id, true);
174
+            this.updateLargeVideo(APP.conference.localId, true);
178 175
         }
179 176
     },
180 177
 

+ 44
- 4
modules/settings/Settings.js 查看文件

@@ -1,9 +1,11 @@
1 1
 import {generateUsername} from '../util/UsernameGenerator';
2 2
 
3
-var email = '';
4
-var displayName = '';
5
-var userId;
6
-var language = null;
3
+let email = '';
4
+let displayName = '';
5
+let userId;
6
+let language = null;
7
+let cameraDeviceId = '';
8
+let micDeviceId = '';
7 9
 
8 10
 function supportsLocalStorage() {
9 11
     try {
@@ -32,6 +34,8 @@ if (supportsLocalStorage()) {
32 34
     email = window.localStorage.email || '';
33 35
     displayName = window.localStorage.displayname || '';
34 36
     language = window.localStorage.language;
37
+    cameraDeviceId = window.localStorage.cameraDeviceId || '';
38
+    micDeviceId = window.localStorage.micDeviceId || '';
35 39
 } else {
36 40
     console.log("local storage is not supported");
37 41
     userId = generateUniqueId();
@@ -86,5 +90,41 @@ export default {
86 90
     setLanguage: function (lang) {
87 91
         language = lang;
88 92
         window.localStorage.language = lang;
93
+    },
94
+
95
+    /**
96
+     * Get device id of the camera which is currently in use.
97
+     * Empty string stands for default device.
98
+     * @returns {String}
99
+     */
100
+    getCameraDeviceId: function () {
101
+        return cameraDeviceId;
102
+    },
103
+    /**
104
+     * Set device id of the camera which is currently in use.
105
+     * Empty string stands for default device.
106
+     * @param {string} newId new camera device id
107
+     */
108
+    setCameraDeviceId: function (newId = '') {
109
+        cameraDeviceId = newId;
110
+        window.localStorage.cameraDeviceId = newId;
111
+    },
112
+
113
+    /**
114
+     * Get device id of the microphone which is currently in use.
115
+     * Empty string stands for default device.
116
+     * @returns {String}
117
+     */
118
+    getMicDeviceId: function () {
119
+        return micDeviceId;
120
+    },
121
+    /**
122
+     * Set device id of the microphone which is currently in use.
123
+     * Empty string stands for default device.
124
+     * @param {string} newId new microphone device id
125
+     */
126
+    setMicDeviceId: function (newId = '') {
127
+        micDeviceId = newId;
128
+        window.localStorage.micDeviceId = newId;
89 129
     }
90 130
 };

+ 3
- 1
service/UI/UIEvents.js 查看文件

@@ -41,5 +41,7 @@ export default {
41 41
     LOGOUT: "UI.logout",
42 42
     RECORDING_TOGGLE: "UI.recording_toggle",
43 43
     SIP_DIAL: "UI.sip_dial",
44
-    SUBEJCT_CHANGED: "UI.subject_changed"
44
+    SUBEJCT_CHANGED: "UI.subject_changed",
45
+    VIDEO_DEVICE_CHANGED: "UI.video_device_changed",
46
+    AUDIO_DEVICE_CHANGED: "UI.audio_device_changed"
45 47
 };

正在加载...
取消
保存