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) => ); + } + + 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",