diff --git a/res/css/_components.scss b/res/css/_components.scss
index dcc5799329..25b7c9342e 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -138,6 +138,7 @@
@import "./views/settings/tabs/_GeneralSettingsTab.scss";
@import "./views/settings/tabs/_PreferencesSettingsTab.scss";
@import "./views/settings/tabs/_SettingsTab.scss";
+@import "./views/settings/tabs/_VoiceSettingsTab.scss";
@import "./views/voip/_CallView.scss";
@import "./views/voip/_IncomingCallbox.scss";
@import "./views/voip/_VideoView.scss";
diff --git a/res/css/views/settings/tabs/_VoiceSettingsTab.scss b/res/css/views/settings/tabs/_VoiceSettingsTab.scss
new file mode 100644
index 0000000000..6a6556eccd
--- /dev/null
+++ b/res/css/views/settings/tabs/_VoiceSettingsTab.scss
@@ -0,0 +1,24 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_VoiceSettingsTab .mx_Field select {
+ width: 100%;
+ max-width: 100%;
+}
+
+.mx_VoiceSettingsTab .mx_Field {
+ margin-right: 100px; // align with the rest of the fields
+}
diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js
index 2330f86b99..9a1c9d70b8 100644
--- a/src/CallMediaHandler.js
+++ b/src/CallMediaHandler.js
@@ -69,4 +69,16 @@ export default {
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
Matrix.setMatrixCallVideoInput(deviceId);
},
+
+ getAudioOutput: function() {
+ return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
+ },
+
+ getAudioInput: function() {
+ return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
+ },
+
+ getVideoInput: function() {
+ return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
+ },
};
diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js
index 0c11b29456..7b09970f56 100644
--- a/src/components/views/dialogs/UserSettingsDialog.js
+++ b/src/components/views/dialogs/UserSettingsDialog.js
@@ -25,6 +25,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import LabsSettingsTab from "../settings/tabs/LabsSettingsTab";
import NotificationSettingsTab from "../settings/tabs/NotificationSettingsTab";
import PreferencesSettingsTab from "../settings/tabs/PreferencesSettingsTab";
+import VoiceSettingsTab from "../settings/tabs/VoiceSettingsTab";
// TODO: Ditch this whole component
export class TempTab extends React.Component {
@@ -68,7 +69,7 @@ export default class UserSettingsDialog extends React.Component {
tabs.push(new Tab(
_td("Voice & Video"),
"mx_UserSettingsDialog_voiceIcon",
-
Voice Test
,
+ ,
));
tabs.push(new Tab(
_td("Security & Privacy"),
diff --git a/src/components/views/settings/tabs/VoiceSettingsTab.js b/src/components/views/settings/tabs/VoiceSettingsTab.js
new file mode 100644
index 0000000000..03d244ac7a
--- /dev/null
+++ b/src/components/views/settings/tabs/VoiceSettingsTab.js
@@ -0,0 +1,177 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import {_t} from "../../../../languageHandler";
+import CallMediaHandler from "../../../../CallMediaHandler";
+import Field from "../../elements/Field";
+import AccessibleButton from "../../elements/AccessibleButton";
+import {SettingLevel} from "../../../../settings/SettingsStore";
+const Modal = require("../../../../Modal");
+const sdk = require("../../../../index");
+const MatrixClientPeg = require("../../../../MatrixClientPeg");
+
+export default class VoiceSettingsTab extends React.Component {
+ constructor() {
+ super();
+
+ this.state = {
+ mediaDevices: null,
+ activeAudioOutput: null,
+ activeAudioInput: null,
+ activeVideoInput: null,
+ }
+ }
+
+ componentWillMount(): void {
+ this._refreshMediaDevices();
+ }
+
+ _refreshMediaDevices = async () => {
+ this.setState({
+ mediaDevices: await CallMediaHandler.getDevices(),
+ activeAudioOutput: CallMediaHandler.getAudioOutput(),
+ activeAudioInput: CallMediaHandler.getAudioInput(),
+ activeVideoInput: CallMediaHandler.getVideoInput(),
+ });
+ };
+
+ _requestMediaPermissions = () => {
+ const getUserMedia = (
+ window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia
+ );
+ if (getUserMedia) {
+ return getUserMedia.apply(window.navigator, [
+ { video: true, audio: true },
+ this._refreshMediaDevices,
+ function() {
+ const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
+ Modal.createTrackedDialog('No media permissions', '', ErrorDialog, {
+ title: _t('No media permissions'),
+ description: _t('You may need to manually permit Riot to access your microphone/webcam'),
+ });
+ },
+ ]);
+ }
+ };
+
+ _setAudioOutput = (e) => {
+ CallMediaHandler.setAudioOutput(e.target.value);
+ };
+
+ _setAudioInput = (e) => {
+ CallMediaHandler.setAudioInput(e.target.value);
+ };
+
+ _setVideoInput = (e) => {
+ CallMediaHandler.setVideoInput(e.target.value);
+ };
+
+ _setForceTurn = (forced) => {
+ MatrixClientPeg.get().setForceTURN(forced);
+ };
+
+ _renderDeviceOptions(devices, category) {
+ return devices.map((d) => {d.label} );
+ }
+
+ render() {
+ const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
+
+ let requestButton = null;
+ let speakerDropdown = null;
+ let microphoneDropdown = null;
+ let webcamDropdown = null;
+ if (this.state.mediaDevices === false) {
+ requestButton = (
+
+
{_t("Missing media permissions, click the button below to request.")}
+
+ {_t("Request media permissions")}
+
+
+ );
+ } else if (this.state.mediaDevices) {
+ speakerDropdown = { _t('No Audio Outputs detected') }
;
+ microphoneDropdown = { _t('No Microphones detected') }
;
+ webcamDropdown = { _t('No Webcams detected') }
;
+
+ const defaultOption = {
+ deviceId: '',
+ label: _t('Default Device'),
+ };
+ const getDefaultDevice = (devices) => {
+ if (!devices.some((i) => i.deviceId === 'default')) {
+ devices.unshift(defaultOption);
+ return '';
+ } else {
+ return 'default';
+ }
+ };
+
+ const audioOutputs = this.state.mediaDevices.audiooutput.slice(0);
+ if (audioOutputs.length > 0) {
+ const defaultDevice = getDefaultDevice(audioOutputs);
+ speakerDropdown = (
+
+ {this._renderDeviceOptions(audioOutputs, 'audioOutput')}
+
+ );
+ }
+
+ const audioInputs = this.state.mediaDevices.audioinput.slice(0);
+ if (audioInputs.length > 0) {
+ const defaultDevice = getDefaultDevice(audioInputs);
+ microphoneDropdown = (
+
+ {this._renderDeviceOptions(audioInputs, 'audioInput')}
+
+ );
+ }
+
+ const videoInputs = this.state.mediaDevices.videoinput.slice(0);
+ if (videoInputs.length > 0) {
+ const defaultDevice = getDefaultDevice(videoInputs);
+ webcamDropdown = (
+
+ {this._renderDeviceOptions(videoInputs, 'videoInput')}
+
+ );
+ }
+ }
+
+ // TODO: Make 'webRtcForceTURN' be positively worded
+ return (
+
+
{_t("Voice & Video")}
+
+ {requestButton}
+ {speakerDropdown}
+ {microphoneDropdown}
+ {webcamDropdown}
+
+
+
+
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 1ef41824d5..ab4e8b74c7 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -450,6 +450,18 @@
"Timeline": "Timeline",
"Advanced": "Advanced",
"Autocomplete delay (ms)": "Autocomplete delay (ms)",
+ "No media permissions": "No media permissions",
+ "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
+ "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.",
+ "Request media permissions": "Request media permissions",
+ "No Audio Outputs detected": "No Audio Outputs detected",
+ "No Microphones detected": "No Microphones detected",
+ "No Webcams detected": "No Webcams detected",
+ "Default Device": "Default Device",
+ "Audio Output": "Audio Output",
+ "Microphone": "Microphone",
+ "Camera": "Camera",
+ "Voice & Video": "Voice & Video",
"Cannot add any more widgets": "Cannot add any more widgets",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"Add a widget": "Add a widget",
@@ -1044,7 +1056,6 @@
"Room contains unknown devices": "Room contains unknown devices",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
"Unknown devices": "Unknown devices",
- "Voice & Video": "Voice & Video",
"Security & Privacy": "Security & Privacy",
"Help & About": "Help & About",
"Visit old settings": "Visit old settings",
@@ -1300,16 +1311,7 @@
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Bulk Options": "Bulk Options",
"Desktop specific": "Desktop specific",
- "No media permissions": "No media permissions",
- "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",
- "No Audio Outputs detected": "No Audio Outputs detected",
- "No Microphones detected": "No Microphones detected",
- "No Webcams detected": "No Webcams detected",
- "Default Device": "Default Device",
- "Audio Output": "Audio Output",
- "Microphone": "Microphone",
- "Camera": "Camera",
"VoIP": "VoIP",
"Email": "Email",
"Add email address": "Add email address",