feat(bwe) support setting the bandwidth from the client (#13335)

* feat(bwe) support setting the bandwidth from the client
pull/13436/head jitsi-meet_8726
Mihaela Dumitru 3 years ago committed by GitHub
parent 935a391525
commit cd37cdd675
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      config.js
  2. 12
      lang/main.json
  3. 10
      modules/API/API.js
  4. 2
      modules/API/constants.js
  5. 10
      react/features/base/conference/actionTypes.ts
  6. 18
      react/features/base/conference/actions.ts
  7. 40
      react/features/base/conference/middleware.any.ts
  8. 10
      react/features/base/conference/reducer.ts
  9. 1
      react/features/base/config/configType.ts
  10. 15
      react/features/base/ui/components/web/Input.tsx
  11. 143
      react/features/connection-indicator/components/web/BandwidthSettingsDialog.tsx
  12. 27
      react/features/connection-indicator/components/web/ConnectionIndicatorContent.tsx
  13. 33
      react/features/connection-stats/components/ConnectionStatsTable.tsx

@ -74,6 +74,9 @@ var config = {
//
testing: {
// Allows the setting of a custom bandwidth value from the UI.
// assumeBandwidth: true,
// Disables the End to End Encryption feature. Useful for debugging
// issues related to insertable streams.
// disableE2EE: false,

@ -39,6 +39,18 @@
"audioOnly": {
"audioOnly": "Low bandwidth"
},
"bandwidthSettings": {
"assumedBandwidthBps": "e.g. 10000000 for 10 Mbps",
"assumedBandwidthBpsWarning": "Higher values might cause network issues.",
"customValue": "custom value",
"customValueEffect": "to set the actual bps value",
"leaveEmpty": "leave empty",
"leaveEmptyEffect": "to allow estimations to take place",
"possibleValues": "Possible values",
"setAssumedBandwidthBps": "Assumed bandwidth (bps)",
"title": "Bandwidth settings",
"zeroEffect": "to disable video"
},
"breakoutRooms": {
"actions": {
"add": "Add breakout room",

@ -17,6 +17,7 @@ import { isEnabledFromState } from '../../react/features/av-moderation/functions
import {
endConference,
sendTones,
setAssumedBandwidthBps,
setFollowMe,
setLocalSubject,
setPassword,
@ -117,7 +118,6 @@ import { getJitsiMeetTransport } from '../transport';
import {
API_ID,
ASSUMED_BANDWIDTH_BPS,
ENDPOINT_TEXT_MESSAGE_NAME
} from './constants';
@ -321,13 +321,7 @@ function initCommands() {
return;
}
const { conference } = APP.store.getState()['features/base/conference'];
if (conference) {
conference.setAssumedBandwidthBps(value < ASSUMED_BANDWIDTH_BPS
? ASSUMED_BANDWIDTH_BPS
: value);
}
APP.store.dispatch(setAssumedBandwidthBps(value));
},
'set-follow-me': value => {
logger.debug('Set follow me command received');

@ -21,4 +21,4 @@ export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message';
* Setting it to this value means not assuming any bandwidth,
* but rather allowing the estimations to take place.
*/
export const ASSUMED_BANDWIDTH_BPS = -1;
export const MIN_ASSUMED_BANDWIDTH_BPS = -1;

@ -295,3 +295,13 @@ export const SET_ROOM = 'SET_ROOM';
* }
*/
export const SET_START_MUTED_POLICY = 'SET_START_MUTED_POLICY';
/**
* The type of (redux) action which updates the assumed bandwidth bps.
*
* {
* type: SET_ASSUMED_BANDWIDTH_BPS,
* assumedBandwidthBps: number
* }
*/
export const SET_ASSUMED_BANDWIDTH_BPS = 'SET_ASSUMED_BANDWIDTH_BPS';

@ -51,6 +51,7 @@ import {
NON_PARTICIPANT_MESSAGE_RECEIVED,
P2P_STATUS_CHANGED,
SEND_TONES,
SET_ASSUMED_BANDWIDTH_BPS,
SET_FOLLOW_ME,
SET_OBFUSCATED_ROOM,
SET_PASSWORD,
@ -956,3 +957,20 @@ export function setLocalSubject(localSubject: string) {
localSubject
};
}
/**
* Sets the assumed bandwidth bps.
*
* @param {number} assumedBandwidthBps - The new assumed bandwidth.
* @returns {{
* type: SET_ASSUMED_BANDWIDTH_BPS,
* assumedBandwidthBps: number
* }}
*/
export function setAssumedBandwidthBps(assumedBandwidthBps: number) {
return {
type: SET_ASSUMED_BANDWIDTH_BPS,
assumedBandwidthBps
};
}

@ -1,5 +1,7 @@
import { AnyAction } from 'redux';
// @ts-ignore
import { MIN_ASSUMED_BANDWIDTH_BPS } from '../../../../modules/API/constants';
import {
ACTION_PINNED,
ACTION_UNPINNED,
@ -39,6 +41,7 @@ import {
CONFERENCE_WILL_LEAVE,
P2P_STATUS_CHANGED,
SEND_TONES,
SET_ASSUMED_BANDWIDTH_BPS,
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM
} from './actionTypes';
@ -97,6 +100,9 @@ MiddlewareRegistry.register(store => next => action => {
_conferenceWillLeave(store);
break;
case P2P_STATUS_CHANGED:
return _p2pStatusChanged(next, action);
case PARTICIPANT_UPDATED:
return _updateLocalParticipantInConference(store, next, action);
@ -113,8 +119,8 @@ MiddlewareRegistry.register(store => next => action => {
case TRACK_REMOVED:
return _trackAddedOrRemoved(store, next, action);
case P2P_STATUS_CHANGED:
return _p2pStatusChanged(next, action);
case SET_ASSUMED_BANDWIDTH_BPS:
return _setAssumedBandwidthBps(store, next, action);
}
return next(action);
@ -690,3 +696,33 @@ function _p2pStatusChanged(next: Function, action: AnyAction) {
return result;
}
/**
* Notifies the feature base/conference that the action
* {@code SET_ASSUMED_BANDWIDTH_BPS} is being dispatched within a specific
* redux store.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code SET_ASSUMED_BANDWIDTH_BPS}
* which is being dispatched in the specified {@code store}.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _setAssumedBandwidthBps({ getState }: IStore, next: Function, action: AnyAction) {
const state = getState();
const conference = getCurrentConference(state);
const payload = Number(action.assumedBandwidthBps);
const assumedBandwidthBps = isNaN(payload) || payload < MIN_ASSUMED_BANDWIDTH_BPS
? MIN_ASSUMED_BANDWIDTH_BPS
: payload;
if (conference) {
conference.setAssumedBandwidthBps(assumedBandwidthBps);
}
return next(action);
}

@ -20,6 +20,7 @@ import {
CONFERENCE_WILL_LEAVE,
LOCK_STATE_CHANGED,
P2P_STATUS_CHANGED,
SET_ASSUMED_BANDWIDTH_BPS,
SET_FOLLOW_ME,
SET_OBFUSCATED_ROOM,
SET_PASSWORD,
@ -31,6 +32,7 @@ import {
import { isRoomValid } from './functions';
const DEFAULT_STATE = {
assumedBandwidthBps: undefined,
conference: undefined,
e2eeSupported: undefined,
joining: undefined,
@ -124,6 +126,7 @@ export interface IJitsiConference {
}
export interface IConferenceState {
assumedBandwidthBps?: number;
authEnabled?: boolean;
authLogin?: string;
authRequired?: IJitsiConference;
@ -197,6 +200,13 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
case P2P_STATUS_CHANGED:
return _p2pStatusChanged(state, action);
case SET_ASSUMED_BANDWIDTH_BPS: {
const assumedBandwidthBps = action.assumedBandwidthBps >= 0
? Number(action.assumedBandwidthBps)
: undefined;
return set(state, 'assumedBandwidthBps', assumedBandwidthBps);
}
case SET_FOLLOW_ME:
return set(state, 'followMeEnabled', action.enabled);

@ -512,6 +512,7 @@ export interface IConfig {
stereo?: boolean;
subject?: string;
testing?: {
assumeBandwidth?: boolean;
callStatsThreshold?: number;
disableE2EE?: boolean;
mobileXmppWsThreshold?: number;

@ -18,7 +18,9 @@ interface IProps extends IInputProps {
id?: string;
maxLength?: number;
maxRows?: number;
maxValue?: number;
minRows?: number;
minValue?: number;
name?: string;
onBlur?: (e: any) => void;
onFocus?: (event: React.FocusEvent) => void;
@ -92,6 +94,15 @@ const useStyles = makeStyles()(theme => {
}
},
'input::-webkit-outer-spin-button, input::-webkit-inner-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'input[type=number]': {
'-moz-appearance': 'textfield'
},
icon: {
position: 'absolute',
top: '50%',
@ -146,8 +157,10 @@ const Input = React.forwardRef<any, IProps>(({
iconClick,
id,
label,
maxValue,
maxLength,
maxRows,
minValue,
minRows,
name,
onBlur,
@ -210,7 +223,9 @@ const Input = React.forwardRef<any, IProps>(({
data-testid = { testId }
disabled = { disabled }
{ ...(id ? { id } : {}) }
{ ...(type === 'number' ? { max: maxValue } : {}) }
maxLength = { maxLength }
{ ...(type === 'number' ? { min: minValue } : {}) }
name = { name }
onBlur = { onBlur }
onChange = { handleChange }

@ -0,0 +1,143 @@
import React, { KeyboardEvent, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
// @ts-ignore
import { MIN_ASSUMED_BANDWIDTH_BPS } from '../../../../../modules/API/constants';
import { IReduxState } from '../../../app/types';
import { setAssumedBandwidthBps as saveAssumedBandwidthBps } from '../../../base/conference/actions';
import { IconInfoCircle } from '../../../base/icons/svg';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import Dialog from '../../../base/ui/components/web/Dialog';
import Input from '../../../base/ui/components/web/Input';
const useStyles = makeStyles()(theme => {
return {
content: {
color: theme.palette.text01
},
info: {
background: theme.palette.ui01,
...withPixelLineHeight(theme.typography.labelRegular),
color: theme.palette.text02,
marginTop: theme.spacing(2)
},
possibleValues: {
margin: 0,
paddingLeft: theme.spacing(4)
}
};
});
/**
* Bandwidth settings dialog component.
*
* @returns {ReactElement}
*/
const BandwidthSettingsDialog = () => {
const { classes } = useStyles();
const { t } = useTranslation();
const dispatch = useDispatch();
const [ showAssumedBandwidthInfo, setShowAssumedBandwidthInfo ] = useState(false);
const currentAssumedBandwidthBps = useSelector(
(state: IReduxState) => state['features/base/conference'].assumedBandwidthBps
);
const [ assumedBandwidthBps, setAssumedBandwidthBps ] = useState(
currentAssumedBandwidthBps === MIN_ASSUMED_BANDWIDTH_BPS
|| currentAssumedBandwidthBps === undefined
? ''
: currentAssumedBandwidthBps
);
/**
* Changes the assumed bandwidth bps.
*
* @param {string} value - The key event to handle.
*
* @returns {void}
*/
const onAssumedBandwidthBpsChange = useCallback((value: string) => {
setAssumedBandwidthBps(value);
}, [ setAssumedBandwidthBps ]);
/**
* Persists the assumed bandwidth bps.
*
* @param {string} value - The key event to handle.
*
* @returns {void}
*/
const onAssumedBandwidthBpsSave = useCallback(() => {
if (assumedBandwidthBps !== currentAssumedBandwidthBps) {
dispatch(saveAssumedBandwidthBps(Number(
assumedBandwidthBps === '' ? MIN_ASSUMED_BANDWIDTH_BPS : assumedBandwidthBps
)));
}
}, [ assumedBandwidthBps, currentAssumedBandwidthBps, dispatch, saveAssumedBandwidthBps ]);
/**
* Validates the assumed bandwidth bps.
*
* @param {KeyboardEvent<any>} e - The key event to handle.
*
* @returns {void}
*/
const onAssumedBandwidthBpsKeyPress = useCallback((e: KeyboardEvent<any>) => {
const isValid = (e.charCode !== 8 && e.charCode === 0) || (e.charCode >= 48 && e.charCode <= 57);
if (!isValid) {
e.preventDefault();
}
}, []);
/**
* Callback invoked to hide or show the possible values
* of the assumed bandwidth setting.
*
* @returns {void}
*/
const toggleInfoPanel = useCallback(() => {
setShowAssumedBandwidthInfo(!showAssumedBandwidthInfo);
}, [ setShowAssumedBandwidthInfo, showAssumedBandwidthInfo ]);
return (
<Dialog
onSubmit = { onAssumedBandwidthBpsSave }
titleKey = 'bandwidthSettings.title'>
<div className = { classes.content }>
<Input
bottomLabel = { t('bandwidthSettings.assumedBandwidthBpsWarning') }
icon = { IconInfoCircle }
iconClick = { toggleInfoPanel }
id = 'setAssumedBandwidthBps'
label = { t('bandwidthSettings.setAssumedBandwidthBps') }
minValue = { 0 }
name = 'assumedBandwidthBps'
onChange = { onAssumedBandwidthBpsChange }
onKeyPress = { onAssumedBandwidthBpsKeyPress }
placeholder = { t('bandwidthSettings.assumedBandwidthBps') }
type = 'number'
value = { assumedBandwidthBps } />
{showAssumedBandwidthInfo && (
<div className = { classes.info }>
<span>{t('bandwidthSettings.possibleValues')}:</span>
<ul className = { classes.possibleValues }>
<li>
<b>{t('bandwidthSettings.leaveEmpty')}</b> {t('bandwidthSettings.leaveEmptyEffect')}
</li>
<li><b>0</b> {t('bandwidthSettings.zeroEffect')}</li>
<li>
<b>{t('bandwidthSettings.customValue')}</b> {t('bandwidthSettings.customValueEffect')}
</li>
</ul>
</div>
)}
</div>
</Dialog>
);
};
export default BandwidthSettingsDialog;

@ -3,6 +3,7 @@ import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import { openDialog } from '../../../base/dialog/actions';
import { translate } from '../../../base/i18n/functions';
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
@ -26,6 +27,8 @@ import AbstractConnectionIndicator, {
INDICATOR_DISPLAY_THRESHOLD
} from '../AbstractConnectionIndicator';
import BandwidthSettingsDialog from './BandwidthSettingsDialog';
/**
* An array of display configurations for the connection indicator and its bars.
* The ordering is done specifically for faster iteration to find a matching
@ -78,6 +81,11 @@ interface IProps extends AbstractProps, WithTranslation {
*/
_disableShowMoreStats: boolean;
/**
* Whether to enable assumed bandwidth.
*/
_enableAssumedBandwidth?: boolean;
/**
* Whether or not should display the "Save Logs" link in the local video
* stats table.
@ -100,6 +108,11 @@ interface IProps extends AbstractProps, WithTranslation {
*/
_isNarrowLayout: boolean;
/**
* Invoked to open the bandwidth settings dialog.
*/
_onOpenBandwidthDialog: () => void;
/**
* Invoked to save the conference logs.
*/
@ -201,12 +214,14 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<IProps, ISt
connectionSummary = { this._getConnectionStatusTip() }
disableShowMoreStats = { this.props._disableShowMoreStats }
e2eeVerified = { this.props._isE2EEVerified }
enableAssumedBandwidth = { this.props._enableAssumedBandwidth }
enableSaveLogs = { this.props._enableSaveLogs }
framerate = { framerate }
isLocalVideo = { this.props._isLocalVideo }
isNarrowLayout = { this.props._isNarrowLayout }
isVirtualScreenshareParticipant = { this.props._isVirtualScreenshareParticipant }
maxEnabledResolution = { maxEnabledResolution }
onOpenBandwidthDialog = { this.props._onOpenBandwidthDialog }
onSaveLogs = { this.props._onSaveLogs }
onShowMore = { this._onToggleShowMore }
packetLoss = { packetLoss }
@ -304,6 +319,15 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
*/
_onSaveLogs() {
dispatch(saveLogs());
},
/**
* Opens the bandwidth settings dialog.
*
* @returns {void}
*/
_onOpenBandwidthDialog() {
dispatch(openDialog(BandwidthSettingsDialog));
}
};
}
@ -335,8 +359,9 @@ export function _mapStateToProps(state: IReduxState, ownProps: any) {
return {
_audioSsrc: audioTrack ? conference?.getSsrcByTrack(audioTrack.jitsiTrack) : undefined,
_enableSaveLogs: Boolean(state['features/base/config'].enableSaveLogs),
_disableShowMoreStats: Boolean(state['features/base/config'].disableShowMoreStats),
_enableAssumedBandwidth: state['features/base/config'].testing?.assumeBandwidth,
_enableSaveLogs: Boolean(state['features/base/config'].enableSaveLogs),
_isConnectionStatusInactive,
_isConnectionStatusInterrupted,
_isE2EEVerified: Boolean(participant?.e2eeVerified),

@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next';
import { makeStyles } from 'tss-react/mui';
import { isMobileBrowser } from '../../base/environment/utils';
import Icon from '../../base/icons/components/Icon';
import { IconGear } from '../../base/icons/svg';
import ContextMenu from '../../base/ui/components/web/ContextMenu';
type DownloadUpload = {
@ -71,6 +73,11 @@ interface IProps {
*/
e2eeVerified: boolean;
/**
* Whether to enable assumed bandwidth.
*/
enableAssumedBandwidth?: boolean;
/**
* Whether or not should display the "Save Logs" link.
*/
@ -107,6 +114,11 @@ interface IProps {
*/
maxEnabledResolution: number;
/**
* Callback to invoke when the user clicks on the open bandwidth settings dialog icon.
*/
onOpenBandwidthDialog: () => void;
/**
* Callback to invoke when the user clicks on the download logs link.
*/
@ -200,6 +212,14 @@ const useStyles = makeStyles()(theme => {
margin: '10px auto',
textAlign: 'center'
},
assumedBandwidth: {
cursor: 'pointer',
margin: '0 5px'
},
bandwidth: {
alignItems: 'center',
display: 'flex'
},
connectionStatsTable: {
'&, & > table': {
fontSize: '12px',
@ -261,12 +281,14 @@ const ConnectionStatsTable = ({
connectionSummary,
disableShowMoreStats,
e2eeVerified,
enableAssumedBandwidth,
enableSaveLogs,
framerate,
isVirtualScreenshareParticipant,
isLocalVideo,
isNarrowLayout,
maxEnabledResolution,
onOpenBandwidthDialog,
onSaveLogs,
onShowMore,
packetLoss,
@ -351,7 +373,7 @@ const ConnectionStatsTable = ({
<td>
{t('connectionindicator.bandwidth')}
</td>
<td>
<td className = { classes.bandwidth }>
<span className = { classes.download }>
&darr;
</span>
@ -360,6 +382,15 @@ const ConnectionStatsTable = ({
&uarr;
</span>
{upload ? `${upload} Kbps` : 'N/A'}
{enableAssumedBandwidth && (
<div
className = { classes.assumedBandwidth }
onClick = { onOpenBandwidthDialog }>
<Icon
size = { 10 }
src = { IconGear } />
</div>
)}
</td>
</tr>
);

Loading…
Cancel
Save