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:
parent
ea56010e09
commit
765fbe5e1d
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/screen-share');
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -194,6 +194,7 @@ function _mapStateToProps(state) {
|
|||
|
||||
return {
|
||||
...newProps,
|
||||
currentFramerate: tabState.currentFramerate,
|
||||
currentLanguage: tabState.currentLanguage,
|
||||
followMeEnabled: tabState.followMeEnabled,
|
||||
showPrejoinPage: tabState.showPrejoinPage,
|
||||
|
|
|
@ -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 ];
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue