feat: Change the screenshare capture fps from UI. (#9438)

* feat: Change the screenshare capture fps from UI.
Add the ability to change the capture frame rate for screenshare from the UI. The fps becomes effective only on screen shares that are started after the setting is changed.

* squash: add missing JSDOCs and translations for frames-per-second.
This commit is contained in:
Jaya Allamsetty 2021-06-28 03:48:16 -04:00 committed by GitHub
parent ea56010e09
commit 765fbe5e1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 293 additions and 41 deletions

View File

@ -36,6 +36,12 @@
.calendar-tab,
.more-tab,
.box {
display: flex;
justify-content: space-between;
width: 100%;
}
.profile-edit {
display: flex;
width: 100%;
@ -45,18 +51,24 @@
flex: 1;
}
.settings-sub-pane {
flex-grow: 1;
flex: 1;
}
.settings-sub-pane .right {
flex: 1;
}
.settings-sub-pane .left {
flex: 1;
}
.settings-sub-pane-element {
text-align: left;
flex: 1;
}
.profile-edit-field {
margin-right: 20px;
}
.language-settings {
max-width: 50%;
width: 35%;
}
.calendar-tab {
align-items: center;
flex-direction: column;

View File

@ -719,8 +719,12 @@
"signedIn": "Currently accessing calendar events for {{email}}. Click the Disconnect button below to stop accessing calendar events.",
"title": "Calendar"
},
"desktopShareFramerate": "Desktop sharing frame rate",
"desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
"desktopShareHighFpsWarning": "A higher frame rate for desktop sharing might affect your bandwidth. You need to restart the screen share for the new settings to take effect.",
"devices": "Devices",
"followMe": "Everyone follows me",
"framesPerSecond": "frames-per-second",
"language": "Language",
"loggedIn": "Logged in as {{name}}",
"microphones": "Microphones",

View File

@ -13,6 +13,7 @@ import '../old-client-notification/middleware';
import '../power-monitor/middleware';
import '../prejoin/middleware';
import '../remote-control/middleware';
import '../screen-share/middleware';
import '../shared-video/middleware';
import '../talk-while-muted/middleware';
import '../virtual-background/middleware';

View File

@ -11,6 +11,7 @@ import '../participants-pane/reducer';
import '../power-monitor/reducer';
import '../prejoin/reducer';
import '../remote-control/reducer';
import '../screen-share/reducer';
import '../screenshot-capture/reducer';
import '../shared-video/reducer';
import '../talk-while-muted/reducer';

View File

@ -10,3 +10,11 @@
*/
export const SET_SCREEN_AUDIO_SHARE_STATE = 'SET_SCREEN_AUDIO_SHARE_STATE';
/**
* Type of action which sets the capture frame rate for screenshare.
* {
* type: SET_SCREENSHARE_CAPTURE_FRAME_RATE,
* captureFrameRate: number
* }
*/
export const SET_SCREENSHARE_CAPTURE_FRAME_RATE = 'SET_SCREENSHARE_CAPTURE_FRAME_RATE';

View File

@ -1,6 +1,6 @@
// @flow
import { SET_SCREEN_AUDIO_SHARE_STATE } from './actionTypes';
import { SET_SCREEN_AUDIO_SHARE_STATE, SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
/**
* Updates the current known status of the shared video.
@ -17,3 +17,19 @@ export function setScreenAudioShareState(isSharingAudio: boolean) {
isSharingAudio
};
}
/**
* Updates the capture frame rate for screenshare in redux.
*
* @param {number} captureFrameRate - The frame rate to be used for screenshare.
* @returns {{
* type: SET_SCREENSHARE_CAPTURE_FRAME_RATE,
* captureFrameRate: number
* }}
*/
export function setScreenshareFramerate(captureFrameRate: number) {
return {
type: SET_SCREENSHARE_CAPTURE_FRAME_RATE,
captureFrameRate
};
}

View File

@ -0,0 +1,5 @@
// @flow
import { getLogger } from '../base/logging/functions';
export default getLogger('features/screen-share');

View File

@ -0,0 +1,57 @@
// @flow
import { CONFERENCE_JOINED } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
import logger from './logger';
/**
* Implements the middleware of the feature screen-share.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case CONFERENCE_JOINED: {
_setScreenshareCaptureFps(store);
break;
}
case SET_SCREENSHARE_CAPTURE_FRAME_RATE: {
const { captureFrameRate } = action;
_setScreenshareCaptureFps(store, captureFrameRate);
break;
}
}
return result;
});
/**
* Sets the capture frame rate for screenshare.
*
* @param {Store} store - The redux store.
* @param {number} frameRate - Frame rate to be configured.
* @private
* @returns {void}
*/
function _setScreenshareCaptureFps(store, frameRate) {
const state = store.getState();
const { conference } = state['features/base/conference'];
const { captureFrameRate } = state['features/screen-share'];
const screenShareFps = frameRate ?? captureFrameRate;
if (!conference) {
return;
}
if (screenShareFps) {
logger.debug(`Setting screenshare capture frame rate as ${screenShareFps}`);
conference.setDesktopSharingFrameRate(screenShareFps);
}
}

View File

@ -1,13 +1,13 @@
import { ReducerRegistry } from '../base/redux';
import { SET_SCREEN_AUDIO_SHARE_STATE } from './actionTypes';
import { SET_SCREEN_AUDIO_SHARE_STATE, SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
/**
* Reduces the Redux actions of the feature features/screen-share.
*/
ReducerRegistry.register('features/screen-share', (state = {}, action) => {
const { isSharingAudio } = action;
const { captureFrameRate, isSharingAudio } = action;
switch (action.type) {
case SET_SCREEN_AUDIO_SHARE_STATE:
@ -16,6 +16,12 @@ ReducerRegistry.register('features/screen-share', (state = {}, action) => {
isSharingAudio
};
case SET_SCREENSHARE_CAPTURE_FRAME_RATE:
return {
...state,
captureFrameRate
};
default:
return state;
}

View File

@ -5,6 +5,7 @@ import { openDialog } from '../base/dialog';
import { i18next } from '../base/i18n';
import { updateSettings } from '../base/settings';
import { setPrejoinPageVisibility } from '../prejoin/actions';
import { setScreenshareFramerate } from '../screen-share/actions';
import {
SET_AUDIO_SETTINGS_VISIBILITY,
@ -99,6 +100,12 @@ export function submitMoreTab(newState: Object): Function {
if (newState.currentLanguage !== currentState.currentLanguage) {
i18next.changeLanguage(newState.currentLanguage);
}
if (newState.currentFramerate !== currentState.currentFramerate) {
const frameRate = parseInt(newState.currentFramerate, 10);
dispatch(setScreenshareFramerate(frameRate));
}
};
}

View File

@ -11,6 +11,7 @@ import { AbstractDialogTab } from '../../../base/dialog';
import type { Props as AbstractDialogTabProps } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import TouchmoveHack from '../../../chat/components/web/TouchmoveHack';
import { SS_DEFAULT_FRAME_RATE } from '../../constants';
/**
* The type of the React {@code Component} props of {@link MoreTab}.
@ -18,12 +19,22 @@ import TouchmoveHack from '../../../chat/components/web/TouchmoveHack';
export type Props = {
...$Exact<AbstractDialogTabProps>,
/**
* The currently selected desktop share frame rate in the frame rate select dropdown.
*/
currentFramerate: string,
/**
* The currently selected language to display in the language select
* dropdown.
*/
currentLanguage: string,
/**
* All available desktop capture frame rates.
*/
desktopShareFramerates: Array<number>,
/**
* Whether or not follow me is currently active (enabled by some other participant).
*/
@ -59,7 +70,6 @@ export type Props = {
*/
showPrejoinPage: boolean,
/**
* Whether or not the user has selected the Start Audio Muted feature to be
* enabled.
@ -83,6 +93,11 @@ export type Props = {
*/
type State = {
/**
* Whether or not the desktop share frame rate select dropdown is open.
*/
isFramerateSelectOpen: boolean,
/**
* Whether or not the language select dropdown is open.
*/
@ -105,12 +120,14 @@ class MoreTab extends AbstractDialogTab<Props, State> {
super(props);
this.state = {
isFramerateSelectOpen: false,
isLanguageSelectOpen: false
};
// Bind event handler so it is only bound once for every instance.
this._onLanguageDropdownOpenChange
= this._onLanguageDropdownOpenChange.bind(this);
this._onFramerateDropdownOpenChange = this._onFramerateDropdownOpenChange.bind(this);
this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this);
this._onLanguageDropdownOpenChange = this._onLanguageDropdownOpenChange.bind(this);
this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this);
this._onStartAudioMutedChanged = this._onStartAudioMutedChanged.bind(this);
this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
@ -126,25 +143,40 @@ class MoreTab extends AbstractDialogTab<Props, State> {
* @returns {ReactElement}
*/
render() {
const { showModeratorSettings, showLanguageSettings, showPrejoinSettings } = this.props;
const content = [];
if (showPrejoinSettings) {
content.push(this._renderPrejoinScreenSettings());
}
content.push(this._renderSettingsLeft());
content.push(this._renderSettingsRight());
content.push(this._renderKeyboardShortcutCheckbox());
return <div className = 'more-tab box'>{ content }</div>;
}
_onFramerateDropdownOpenChange: (Object) => void;
if (showModeratorSettings) {
content.push(this._renderModeratorSettings());
}
/**
* Callback invoked to toggle display of the desktop share framerate select dropdown.
*
* @param {Object} event - The event for opening or closing the dropdown.
* @private
* @returns {void}
*/
_onFramerateDropdownOpenChange({ isOpen }) {
this.setState({ isFramerateSelectOpen: isOpen });
}
if (showLanguageSettings) {
content.push(this._renderLangaugeSelect());
}
_onFramerateItemSelect: (Object) => void;
return <div className = 'more-tab'>{ content }</div>;
/**
* Callback invoked to select a frame rate from the select dropdown.
*
* @param {Object} e - The key event to handle.
* @private
* @returns {void}
*/
_onFramerateItemSelect(e) {
const frameRate = e.currentTarget.getAttribute('data-framerate');
super._onChange({ currentFramerate: frameRate });
}
_onLanguageDropdownOpenChange: (Object) => void;
@ -246,13 +278,89 @@ class MoreTab extends AbstractDialogTab<Props, State> {
super._onChange({ keyboardShortcutEnable: checked });
}
/**
* Returns the React Element for the desktop share frame rate dropdown.
*
* @returns {ReactElement}
*/
_renderFramerateSelect() {
const { currentFramerate, desktopShareFramerates, t } = this.props;
const frameRateItems = desktopShareFramerates.map(frameRate => (
<DropdownItem
data-framerate = { frameRate }
key = { frameRate }
onClick = { this._onFramerateItemSelect }>
{ `${frameRate} ${t('settings.framesPerSecond')}` }
</DropdownItem>));
return (
<div
className = 'settings-sub-pane-element'
key = 'frameRate'>
<h2 className = 'mock-atlaskit-label'>
{ t('settings.desktopShareFramerate') }
</h2>
<div className = 'dropdown-menu'>
<TouchmoveHack isModal = { true }>
<DropdownMenu
isOpen = { this.state.isFramerateSelectOpen }
onOpenChange = { this._onFramerateDropdownOpenChange }
shouldFitContainer = { true }
trigger = { currentFramerate
? `${currentFramerate} ${t('settings.framesPerSecond')}`
: '' }
triggerButtonProps = {{
shouldFitContainer: true
}}
triggerType = 'button'>
<DropdownItemGroup>
{ frameRateItems }
</DropdownItemGroup>
</DropdownMenu>
</TouchmoveHack>
</div>
<div
className = 'mock-atlaskit-label'>
{ parseInt(currentFramerate, 10) > SS_DEFAULT_FRAME_RATE
? t('settings.desktopShareHighFpsWarning')
: t('settings.desktopShareWarning') }
</div>
</div>
);
}
/**
* Returns the React Element for keyboardShortcut settings.
*
* @private
* @returns {ReactElement}
*/
_renderKeyboardShortcutCheckbox() {
const { t } = this.props;
return (
<div
className = 'settings-sub-pane-element'
key = 'keyboard-shortcut'>
<h2 className = 'mock-atlaskit-label'>
{ t('keyboardShortcuts.keyboardShortcuts') }
</h2>
<Checkbox
isChecked = { keyboardShortcut.getEnabled() }
label = { t('prejoin.keyboardShortcuts') }
name = 'enable-keyboard-shortcuts'
onChange = { this._onKeyboardShortcutEnableChanged } />
</div>
);
}
/**
* Returns the menu item for changing displayed language.
*
* @private
* @returns {ReactElement}
*/
_renderLangaugeSelect() {
_renderLanguageSelect() {
const {
currentLanguage,
languages,
@ -270,7 +378,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
return (
<div
className = 'settings-sub-pane language-settings'
className = 'settings-sub-pane-element'
key = 'language'>
<h2 className = 'mock-atlaskit-label'>
{ t('settings.language') }
@ -315,7 +423,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
return (
<div
className = 'settings-sub-pane'
className = 'settings-sub-pane-element'
key = 'moderator'>
<h2 className = 'mock-atlaskit-label'>
{ t('settings.moderator') }
@ -351,7 +459,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
return (
<div
className = 'settings-sub-pane'
className = 'settings-sub-pane-element'
key = 'prejoin-screen'>
<h2 className = 'mock-atlaskit-label'>
{ t('prejoin.premeeting') }
@ -366,26 +474,37 @@ class MoreTab extends AbstractDialogTab<Props, State> {
}
/**
* Returns the React Element for keyboardShortcut settings.
* Returns the React element that needs to be displayed on the right half of the more tabs.
*
* @private
* @returns {ReactElement}
*/
_renderKeyboardShortcutCheckbox() {
const { t } = this.props;
_renderSettingsRight() {
const { showLanguageSettings } = this.props;
return (
<div
className = 'settings-sub-pane'
key = 'keyboard-shortcut'>
<h2 className = 'mock-atlaskit-label'>
{ t('keyboardShortcuts.keyboardShortcuts') }
</h2>
<Checkbox
isChecked = { keyboardShortcut.getEnabled() }
label = { t('prejoin.keyboardShortcuts') }
name = 'enable-keyboard-shortcuts'
onChange = { this._onKeyboardShortcutEnableChanged } />
className = 'settings-sub-pane right'>
{ showLanguageSettings && this._renderLanguageSelect() }
{ this._renderFramerateSelect() }
</div>
);
}
/**
* Returns the React element that needs to be displayed on the left half of the more tabs.
*
* @returns {ReactElement}
*/
_renderSettingsLeft() {
const { showPrejoinSettings, showModeratorSettings } = this.props;
return (
<div
className = 'settings-sub-pane left'>
{ showPrejoinSettings && this._renderPrejoinScreenSettings() }
{ this._renderKeyboardShortcutCheckbox() }
{ showModeratorSettings && this._renderModeratorSettings() }
</div>
);
}

View File

@ -194,6 +194,7 @@ function _mapStateToProps(state) {
return {
...newProps,
currentFramerate: tabState.currentFramerate,
currentLanguage: tabState.currentLanguage,
followMeEnabled: tabState.followMeEnabled,
showPrejoinPage: tabState.showPrejoinPage,

View File

@ -9,3 +9,13 @@ export const SETTINGS_TABS = {
* View ID for the Settings modal.
*/
export const SETTINGS_VIEW_ID = 'SETTINGS_VIEW_ID';
/**
* Default frame rate to be used for capturing screenshare.
*/
export const SS_DEFAULT_FRAME_RATE = 5;
/**
* Supported framerates to be used for capturing screenshare.
*/
export const SS_SUPPORTED_FRAMERATES = [ 5, 15, 30 ];

View File

@ -11,6 +11,8 @@ import { toState } from '../base/redux';
import { parseStandardURIString } from '../base/util';
import { isFollowMeActive } from '../follow-me';
import { SS_DEFAULT_FRAME_RATE, SS_SUPPORTED_FRAMERATES } from './constants';
declare var interfaceConfig: Object;
/**
@ -95,6 +97,7 @@ export function shouldShowOnlyDeviceSelection() {
*/
export function getMoreTabProps(stateful: Object | Function) {
const state = toState(stateful);
const framerate = state['features/screen-share'].captureFrameRate ?? SS_DEFAULT_FRAME_RATE;
const language = i18next.language || DEFAULT_LANGUAGE;
const {
conference,
@ -112,7 +115,9 @@ export function getMoreTabProps(stateful: Object | Function) {
&& isLocalParticipantModerator(state));
return {
currentFramerate: framerate,
currentLanguage: language,
desktopShareFramerates: SS_SUPPORTED_FRAMERATES,
followMeActive: Boolean(conference && followMeActive),
followMeEnabled: Boolean(conference && followMeEnabled),
languages: LANGUAGES,