@@ -24,6 +24,7 @@ const dis = require("../../dispatcher");
24
24
const q = require ( 'q' ) ;
25
25
const packageJson = require ( '../../../package.json' ) ;
26
26
const UserSettingsStore = require ( '../../UserSettingsStore' ) ;
27
+ const CallMediaHandler = require ( '../../CallMediaHandler' ) ;
27
28
const GeminiScrollbar = require ( 'react-gemini-scrollbar' ) ;
28
29
const Email = require ( '../../email' ) ;
29
30
const AddThreepid = require ( '../../AddThreepid' ) ;
@@ -176,6 +177,7 @@ module.exports = React.createClass({
176
177
email_add_pending : false ,
177
178
vectorVersion : undefined ,
178
179
rejectingInvites : false ,
180
+ mediaDevices : null ,
179
181
} ;
180
182
} ,
181
183
@@ -196,6 +198,8 @@ module.exports = React.createClass({
196
198
} ) ;
197
199
}
198
200
201
+ this . _refreshMediaDevices ( ) ;
202
+
199
203
// Bulk rejecting invites:
200
204
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
201
205
// will still return rooms with invites. To get around this, add a listener for
@@ -257,6 +261,20 @@ module.exports = React.createClass({
257
261
this . setState ( { electron_settings : settings } ) ;
258
262
} ,
259
263
264
+ _refreshMediaDevices : function ( ) {
265
+ q ( ) . then ( ( ) => {
266
+ return CallMediaHandler . getDevices ( ) ;
267
+ } ) . then ( ( mediaDevices ) => {
268
+ // console.log("got mediaDevices", mediaDevices, this._unmounted);
269
+ if ( this . _unmounted ) return ;
270
+ this . setState ( {
271
+ mediaDevices,
272
+ activeAudioInput : this . _localSettings [ 'webrtc_audioinput' ] ,
273
+ activeVideoInput : this . _localSettings [ 'webrtc_videoinput' ] ,
274
+ } ) ;
275
+ } ) ;
276
+ } ,
277
+
260
278
_refreshFromServer : function ( ) {
261
279
const self = this ;
262
280
q . all ( [
@@ -883,6 +901,110 @@ module.exports = React.createClass({
883
901
</ div > ;
884
902
} ,
885
903
904
+ _mapWebRtcDevicesToSpans : function ( devices ) {
905
+ return devices . map ( ( device ) => < span key = { device . deviceId } > { device . label } </ span > ) ;
906
+ } ,
907
+
908
+ _setAudioInput : function ( deviceId ) {
909
+ this . setState ( { activeAudioInput : deviceId } ) ;
910
+ CallMediaHandler . setAudioInput ( deviceId ) ;
911
+ } ,
912
+
913
+ _setVideoInput : function ( deviceId ) {
914
+ this . setState ( { activeVideoInput : deviceId } ) ;
915
+ CallMediaHandler . setVideoInput ( deviceId ) ;
916
+ } ,
917
+
918
+ _requestMediaPermissions : function ( event ) {
919
+ const getUserMedia = (
920
+ window . navigator . getUserMedia || window . navigator . webkitGetUserMedia || window . navigator . mozGetUserMedia
921
+ ) ;
922
+ if ( getUserMedia ) {
923
+ return getUserMedia . apply ( window . navigator , [
924
+ { video : true , audio : true } ,
925
+ this . _refreshMediaDevices ,
926
+ function ( ) {
927
+ const ErrorDialog = sdk . getComponent ( 'dialogs.ErrorDialog' ) ;
928
+ Modal . createDialog ( ErrorDialog , {
929
+ title : _t ( 'No media permissions' ) ,
930
+ description : _t ( 'You may need to manually permit Riot to access your microphone/webcam' ) ,
931
+ } ) ;
932
+ } ,
933
+ ] ) ;
934
+ }
935
+ } ,
936
+
937
+ _renderWebRtcSettings : function ( ) {
938
+ if ( this . state . mediaDevices === false ) {
939
+ return < div >
940
+ < h3 > { _t ( 'VoIP' ) } </ h3 >
941
+ < div className = "mx_UserSettings_section" >
942
+ < p className = "mx_UserSettings_link" onClick = { this . _requestMediaPermissions } >
943
+ { _t ( 'Missing Media Permissions, click here to request.' ) }
944
+ </ p >
945
+ </ div >
946
+ </ div > ;
947
+ } else if ( ! this . state . mediaDevices ) return ;
948
+
949
+ const Dropdown = sdk . getComponent ( 'elements.Dropdown' ) ;
950
+
951
+ let microphoneDropdown = < p > { _t ( 'No Microphones detected' ) } </ p > ;
952
+ let webcamDropdown = < p > { _t ( 'No Webcams detected' ) } </ p > ;
953
+
954
+ const defaultOption = {
955
+ deviceId : '' ,
956
+ label : _t ( 'Default Device' ) ,
957
+ } ;
958
+
959
+ const audioInputs = this . state . mediaDevices . audioinput . slice ( 0 ) ;
960
+ if ( audioInputs . length > 0 ) {
961
+ let defaultInput = '' ;
962
+ if ( ! audioInputs . some ( ( input ) => input . deviceId === 'default' ) ) {
963
+ audioInputs . unshift ( defaultOption ) ;
964
+ } else {
965
+ defaultInput = 'default' ;
966
+ }
967
+
968
+ microphoneDropdown = < div >
969
+ < h4 > { _t ( 'Microphone' ) } </ h4 >
970
+ < Dropdown
971
+ className = "mx_UserSettings_webRtcDevices_dropdown"
972
+ value = { this . state . activeAudioInput || defaultInput }
973
+ onOptionChange = { this . _setAudioInput } >
974
+ { this . _mapWebRtcDevicesToSpans ( audioInputs ) }
975
+ </ Dropdown >
976
+ </ div > ;
977
+ }
978
+
979
+ const videoInputs = this . state . mediaDevices . videoinput . slice ( 0 ) ;
980
+ if ( videoInputs . length > 0 ) {
981
+ let defaultInput = '' ;
982
+ if ( ! videoInputs . some ( ( input ) => input . deviceId === 'default' ) ) {
983
+ videoInputs . unshift ( defaultOption ) ;
984
+ } else {
985
+ defaultInput = 'default' ;
986
+ }
987
+
988
+ webcamDropdown = < div >
989
+ < h4 > { _t ( 'Camera' ) } </ h4 >
990
+ < Dropdown
991
+ className = "mx_UserSettings_webRtcDevices_dropdown"
992
+ value = { this . state . activeVideoInput || defaultInput }
993
+ onOptionChange = { this . _setVideoInput } >
994
+ { this . _mapWebRtcDevicesToSpans ( videoInputs ) }
995
+ </ Dropdown >
996
+ </ div > ;
997
+ }
998
+
999
+ return < div >
1000
+ < h3 > { _t ( 'VoIP' ) } </ h3 >
1001
+ < div className = "mx_UserSettings_section" >
1002
+ { microphoneDropdown }
1003
+ { webcamDropdown }
1004
+ </ div >
1005
+ </ div > ;
1006
+ } ,
1007
+
886
1008
_showSpoiler : function ( event ) {
887
1009
const target = event . target ;
888
1010
target . innerHTML = target . getAttribute ( 'data-spoiler' ) ;
@@ -1080,6 +1202,7 @@ module.exports = React.createClass({
1080
1202
1081
1203
{ this . _renderUserInterfaceSettings ( ) }
1082
1204
{ this . _renderLabs ( ) }
1205
+ { this . _renderWebRtcSettings ( ) }
1083
1206
{ this . _renderDevicesPanel ( ) }
1084
1207
{ this . _renderCryptoInfo ( ) }
1085
1208
{ this . _renderBulkOptions ( ) }
0 commit comments