Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit ec43390

Browse files
authored
Merge pull request #850 from t3chguy/webrtc_settings
webrtc config electron
2 parents c7285b9 + b1973d7 commit ec43390

File tree

4 files changed

+200
-0
lines changed

4 files changed

+200
-0
lines changed

src/CallMediaHandler.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright 2017 Michael Telatynski <[email protected]>
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import UserSettingsStore from './UserSettingsStore';
18+
import * as Matrix from 'matrix-js-sdk';
19+
import q from 'q';
20+
21+
export default {
22+
getDevices: function() {
23+
// Only needed for Electron atm, though should work in modern browsers
24+
// once permission has been granted to the webapp
25+
return navigator.mediaDevices.enumerateDevices().then(function(devices) {
26+
const audioIn = [];
27+
const videoIn = [];
28+
29+
if (devices.some((device) => !device.label)) return false;
30+
31+
devices.forEach((device) => {
32+
switch (device.kind) {
33+
case 'audioinput': audioIn.push(device); break;
34+
case 'videoinput': videoIn.push(device); break;
35+
}
36+
});
37+
38+
// console.log("Loaded WebRTC Devices", mediaDevices);
39+
return {
40+
audioinput: audioIn,
41+
videoinput: videoIn,
42+
};
43+
}, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
44+
},
45+
46+
loadDevices: function() {
47+
// this.getDevices().then((devices) => {
48+
const localSettings = UserSettingsStore.getLocalSettings();
49+
// // if deviceId is not found, automatic fallback is in spec
50+
// // recall previously stored inputs if any
51+
Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']);
52+
Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']);
53+
// });
54+
},
55+
56+
setAudioInput: function(deviceId) {
57+
UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId);
58+
Matrix.setMatrixCallAudioInput(deviceId);
59+
},
60+
61+
setVideoInput: function(deviceId) {
62+
UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId);
63+
Matrix.setMatrixCallVideoInput(deviceId);
64+
},
65+
};

src/components/structures/LoggedInView.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import UserSettingsStore from '../../UserSettingsStore';
2222
import KeyCode from '../../KeyCode';
2323
import Notifier from '../../Notifier';
2424
import PageTypes from '../../PageTypes';
25+
import CallMediaHandler from '../../CallMediaHandler';
2526
import sdk from '../../index';
2627
import dis from '../../dispatcher';
2728

@@ -79,6 +80,8 @@ export default React.createClass({
7980
// RoomView.getScrollState()
8081
this._scrollStateMap = {};
8182

83+
CallMediaHandler.loadDevices();
84+
8285
document.addEventListener('keydown', this._onKeyDown);
8386
this._matrixClient.on("accountData", this.onAccountData);
8487
},

src/components/structures/UserSettings.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const dis = require("../../dispatcher");
2424
const q = require('q');
2525
const packageJson = require('../../../package.json');
2626
const UserSettingsStore = require('../../UserSettingsStore');
27+
const CallMediaHandler = require('../../CallMediaHandler');
2728
const GeminiScrollbar = require('react-gemini-scrollbar');
2829
const Email = require('../../email');
2930
const AddThreepid = require('../../AddThreepid');
@@ -176,6 +177,7 @@ module.exports = React.createClass({
176177
email_add_pending: false,
177178
vectorVersion: undefined,
178179
rejectingInvites: false,
180+
mediaDevices: null,
179181
};
180182
},
181183

@@ -196,6 +198,8 @@ module.exports = React.createClass({
196198
});
197199
}
198200

201+
this._refreshMediaDevices();
202+
199203
// Bulk rejecting invites:
200204
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
201205
// will still return rooms with invites. To get around this, add a listener for
@@ -257,6 +261,20 @@ module.exports = React.createClass({
257261
this.setState({ electron_settings: settings });
258262
},
259263

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+
260278
_refreshFromServer: function() {
261279
const self = this;
262280
q.all([
@@ -883,6 +901,110 @@ module.exports = React.createClass({
883901
</div>;
884902
},
885903

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+
8861008
_showSpoiler: function(event) {
8871009
const target = event.target;
8881010
target.innerHTML = target.getAttribute('data-spoiler');
@@ -1080,6 +1202,7 @@ module.exports = React.createClass({
10801202

10811203
{this._renderUserInterfaceSettings()}
10821204
{this._renderLabs()}
1205+
{this._renderWebRtcSettings()}
10831206
{this._renderDevicesPanel()}
10841207
{this._renderCryptoInfo()}
10851208
{this._renderBulkOptions()}

src/i18n/strings/en_EN.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@
129129
"Add email address": "Add email address",
130130
"Add phone number": "Add phone number",
131131
"Admin": "Admin",
132+
"VoIP": "VoIP",
133+
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",
134+
"No Microphones detected": "No Microphones detected",
135+
"No Webcams detected": "No Webcams detected",
136+
"No media permissions": "No media permissions",
137+
"You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
138+
"Default Device": "Default Device",
139+
"Microphone": "Microphone",
140+
"Camera": "Camera",
132141
"Advanced": "Advanced",
133142
"Algorithm": "Algorithm",
134143
"Always show message timestamps": "Always show message timestamps",

0 commit comments

Comments
 (0)