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

Commit

Permalink
Merge pull request #2498 from matrix-org/travis/usettings/tab/voice
Browse files Browse the repository at this point in the history
Implement the "Voice & Video" tab of new user settings
  • Loading branch information
turt2live authored Jan 24, 2019
2 parents 77b551a + 0f89668 commit c5deeea
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 12 deletions.
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
3 changes: 2 additions & 1 deletion res/css/views/elements/_AccessibleButton.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ limitations under the License.
}

.mx_AccessibleButton_hasKind {
padding: 10px 25px;
padding: 7px 18px;
text-align: center;
border-radius: 4px;
display: inline-block;
font-size: 14px;
}

.mx_AccessibleButton_kind_primary {
Expand Down
28 changes: 28 additions & 0 deletions res/css/views/settings/tabs/_VoiceSettingsTab.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
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
}

.mx_VoiceSettingsTab_missingMediaPermissions {
margin-bottom: 15px;
}
12 changes: 12 additions & 0 deletions src/CallMediaHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
},
};
3 changes: 2 additions & 1 deletion src/components/views/dialogs/UserSettingsDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -68,7 +69,7 @@ export default class UserSettingsDialog extends React.Component {
tabs.push(new Tab(
_td("Voice & Video"),
"mx_UserSettingsDialog_voiceIcon",
<div>Voice Test</div>,
<VoiceSettingsTab />,
));
tabs.push(new Tab(
_td("Security & Privacy"),
Expand Down
177 changes: 177 additions & 0 deletions src/components/views/settings/tabs/VoiceSettingsTab.js
Original file line number Diff line number Diff line change
@@ -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) => <option key={`${category}-${d.deviceId}`} value={d.deviceId}>{d.label}</option>);
}

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 = (
<div className='mx_VoiceSettingsTab_missingMediaPermissions'>
<p>{_t("Missing media permissions, click the button below to request.")}</p>
<AccessibleButton onClick={this._requestMediaPermissions} kind="primary">
{_t("Request media permissions")}
</AccessibleButton>
</div>
);
} else if (this.state.mediaDevices) {
speakerDropdown = <p>{ _t('No Audio Outputs detected') }</p>;
microphoneDropdown = <p>{ _t('No Microphones detected') }</p>;
webcamDropdown = <p>{ _t('No Webcams detected') }</p>;

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 = (
<Field element="select" label={_t("Audio Output")} id="audioOutput"
value={this.state.activeAudioOutput || defaultDevice}
onChange={this._setAudioOutput}>
{this._renderDeviceOptions(audioOutputs, 'audioOutput')}
</Field>
);
}

const audioInputs = this.state.mediaDevices.audioinput.slice(0);
if (audioInputs.length > 0) {
const defaultDevice = getDefaultDevice(audioInputs);
microphoneDropdown = (
<Field element="select" label={_t("Microphone")} id="audioInput"
value={this.state.activeAudioInput || defaultDevice}
onChange={this._setAudioInput}>
{this._renderDeviceOptions(audioInputs, 'audioInput')}
</Field>
);
}

const videoInputs = this.state.mediaDevices.videoinput.slice(0);
if (videoInputs.length > 0) {
const defaultDevice = getDefaultDevice(videoInputs);
webcamDropdown = (
<Field element="select" label={_t("Camera")} id="videoInput"
value={this.state.activeVideoInput || defaultDevice}
onChange={this._setVideoInput}>
{this._renderDeviceOptions(videoInputs, 'videoInput')}
</Field>
);
}
}

// TODO: Make 'webRtcForceTURN' be positively worded
return (
<div className="mx_SettingsTab mx_VoiceSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Voice & Video")}</div>
<div className="mx_SettingsTab_section">
{requestButton}
{speakerDropdown}
{microphoneDropdown}
{webcamDropdown}
<SettingsFlag name='VideoView.flipVideoHorizontally' level={SettingLevel.ACCOUNT} />
<SettingsFlag name='webRtcForceTURN' level={SettingLevel.DEVICE} onChange={this._setForceTurn} />
</div>
</div>
);
}
}
22 changes: 12 additions & 10 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit c5deeea

Please sign in to comment.