feat(self-view) Added ability to hide self view

Added config option disableSelfView. This disables it on web and native

Added button on local video menu and toggle in settings on web to change the setting
This commit is contained in:
robertpin 2021-12-07 10:24:00 +02:00 committed by Saúl Ibarra Corretgé
parent c48aa44af3
commit 41f11e5adb
19 changed files with 297 additions and 27 deletions

View File

@ -83,6 +83,9 @@ var config = {
// Disables polls feature.
// disablePolls: false,
// Disables self-view tile. (hides it from tile view and from filmstrip)
// disableSelfView: false,
// Disables ICE/UDP by filtering out local and remote UDP candidates in
// signalling.
// webrtcIceUdpDisable: false,

View File

@ -608,6 +608,7 @@
"raisedHands": "{{participantName}} and {{raisedHands}} more people",
"screenShareNoAudio": " Share audio box was not checked in the window selection screen.",
"screenShareNoAudioTitle": "Couldn't share system audio!",
"selfViewTitle": "You can always un-hide the self-view from settings",
"somebody": "Somebody",
"startSilentTitle": "You joined with no audio output!",
"startSilentDescription": "Rejoin the meeting to enable audio",
@ -1124,6 +1125,7 @@
"domuteVideoOfOthers": "Disable camera of everyone else",
"flip": "Flip",
"grantModerator": "Grant Moderator Rights",
"hideSelfView": "Hide self view",
"kick": "Kick out",
"moderator": "Moderator",
"mute": "Participant is muted",

View File

@ -113,6 +113,7 @@ export default [
'disableRemoteMute',
'disableResponsiveTiles',
'disableRtx',
'disableSelfView',
'disableScreensharingVirtualBackground',
'disableShortcuts',
'disableShowMoreStats',

View File

@ -125,6 +125,12 @@ function _setConfig({ dispatch, getState }, next, action) {
}));
}
if (action.config.disableSelfView) {
dispatch(updateSettings({
disableSelfView: true
}));
}
dispatch(updateConfig(config));
// FIXME On Web we rely on the global 'config' variable which gets altered

View File

@ -21,6 +21,7 @@ const DEFAULT_STATE = {
disableCallIntegration: undefined,
disableCrashReporting: undefined,
disableP2P: undefined,
disableSelfView: false,
displayName: undefined,
email: undefined,
localFlipX: true,

View File

@ -23,6 +23,7 @@ import {
calculateThumbnailSizeForTileView,
calculateThumbnailSizeForVerticalView
} from './functions';
import { getDisableSelfView } from './functions.any';
export * from './actions.any';
@ -78,6 +79,7 @@ export function setVerticalViewDimensions() {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
const disableSelfView = getDisableSelfView(state);
const thumbnails = calculateThumbnailSizeForVerticalView(clientWidth);
dispatch({
@ -87,7 +89,8 @@ export function setVerticalViewDimensions() {
remoteVideosContainer: {
width: thumbnails?.local?.width
+ TILE_HORIZONTAL_MARGIN + STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER + SCROLL_SIZE,
height: clientHeight - thumbnails?.local?.height - VERTICAL_FILMSTRIP_VERTICAL_MARGIN
height: clientHeight - (disableSelfView ? 0 : thumbnails?.local?.height)
- VERTICAL_FILMSTRIP_VERTICAL_MARGIN
}
}
@ -104,6 +107,7 @@ export function setHorizontalViewDimensions() {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
const disableSelfView = getDisableSelfView(state);
const thumbnails = calculateThumbnailSizeForHorizontalView(clientHeight);
dispatch({
@ -111,7 +115,7 @@ export function setHorizontalViewDimensions() {
dimensions: {
...thumbnails,
remoteVideosContainer: {
width: clientWidth - thumbnails?.local?.width - HORIZONTAL_FILMSTRIP_MARGIN,
width: clientWidth - (disableSelfView ? 0 : thumbnails?.local?.width) - HORIZONTAL_FILMSTRIP_MARGIN,
height: thumbnails?.local?.height
+ TILE_VERTICAL_MARGIN + STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER + SCROLL_SIZE
}

View File

@ -9,6 +9,7 @@ import { connect } from '../../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { setVisibleRemoteParticipants } from '../../actions';
import { isFilmstripVisible, shouldRemoteVideosBeVisible } from '../../functions';
import { getDisableSelfView } from '../../functions.any';
import LocalThumbnail from './LocalThumbnail';
import Thumbnail from './Thumbnail';
@ -31,6 +32,11 @@ type Props = {
_clientHeight: number,
/**
* Whether or not to hide the self view.
*/
_disableSelfView: boolean,
_localParticipantId: string,
/**
@ -215,7 +221,7 @@ class Filmstrip extends PureComponent<Props> {
* @returns {ReactElement}
*/
render() {
const { _aspectRatio, _localParticipantId, _participants, _visible } = this.props;
const { _aspectRatio, _localParticipantId, _participants, _visible, _disableSelfView } = this.props;
if (!_visible) {
return null;
@ -229,13 +235,15 @@ class Filmstrip extends PureComponent<Props> {
? width / (thumbnailWidth + (2 * margin))
: height / (thumbnailHeight + (2 * margin))
);
const participants = this._separateLocalThumbnail ? _participants : [ _localParticipantId, ..._participants ];
const participants = this._separateLocalThumbnail || _disableSelfView
? _participants : [ _localParticipantId, ..._participants ];
return (
<SafeAreaView style = { filmstripStyle }>
{
this._separateLocalThumbnail
&& !isNarrowAspectRatio
&& !_disableSelfView
&& <LocalThumbnail />
}
<FlatList
@ -254,7 +262,9 @@ class Filmstrip extends PureComponent<Props> {
viewabilityConfig = { this._viewabilityConfig }
windowSize = { 2 } />
{
this._separateLocalThumbnail && isNarrowAspectRatio
this._separateLocalThumbnail
&& isNarrowAspectRatio
&& !_disableSelfView
&& <LocalThumbnail />
}
</SafeAreaView>
@ -271,6 +281,7 @@ class Filmstrip extends PureComponent<Props> {
*/
function _mapStateToProps(state) {
const { enabled, remoteParticipants } = state['features/filmstrip'];
const disableSelfView = getDisableSelfView(state);
const showRemoteVideos = shouldRemoteVideosBeVisible(state);
const responsiveUI = state['features/base/responsive-ui'];
@ -278,6 +289,7 @@ function _mapStateToProps(state) {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_clientHeight: responsiveUI.clientHeight,
_clientWidth: responsiveUI.clientWidth,
_disableSelfView: disableSelfView,
_localParticipantId: getLocalParticipant(state)?.id,
_participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS,
_visible: enabled && isFilmstripVisible(state)

View File

@ -11,6 +11,7 @@ import type { Dispatch } from 'redux';
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { setVisibleRemoteParticipants } from '../../actions.web';
import { getDisableSelfView } from '../../functions.any';
import Thumbnail from './Thumbnail';
import styles from './styles';
@ -30,6 +31,11 @@ type Props = {
*/
_columns: number,
/**
* Whether or not to hide the self view.
*/
_disableSelfView: boolean,
/**
* Application's viewport height.
*/
@ -221,12 +227,16 @@ class TileView extends PureComponent<Props> {
* @returns {Participant[]}
*/
_getSortedParticipants() {
const { _localParticipant, _remoteParticipants } = this.props;
const { _localParticipant, _remoteParticipants, _disableSelfView } = this.props;
if (!_localParticipant) {
return EMPTY_ARRAY;
}
if (_disableSelfView) {
return _remoteParticipants;
}
return [ _localParticipant?.id, ..._remoteParticipants ];
}
@ -263,12 +273,14 @@ class TileView extends PureComponent<Props> {
function _mapStateToProps(state) {
const responsiveUi = state['features/base/responsive-ui'];
const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
const disableSelfView = getDisableSelfView(state);
const { height } = tileViewDimensions.thumbnailSize;
const { columns } = tileViewDimensions;
return {
_aspectRatio: responsiveUi.aspectRatio,
_columns: columns,
_disableSelfView: disableSelfView,
_height: responsiveUi.clientHeight,
_localParticipant: getLocalParticipant(state),
_participantCount: getParticipantCountWithFake(state),

View File

@ -26,6 +26,7 @@ import {
TOOLBAR_HEIGHT_MOBILE
} from '../../constants';
import { shouldRemoteVideosBeVisible } from '../../functions';
import { getDisableSelfView } from '../../functions.any';
import AudioTracksContainer from './AudioTracksContainer';
import Thumbnail from './Thumbnail';
@ -54,6 +55,11 @@ type Props = {
*/
_columns: number,
/**
* Whether or not to hide the self view.
*/
_disableSelfView: boolean,
/**
* The width of the filmstrip.
*/
@ -189,7 +195,7 @@ class Filmstrip extends PureComponent <Props> {
*/
render() {
const filmstripStyle = { };
const { _currentLayout } = this.props;
const { _currentLayout, _disableSelfView } = this.props;
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
switch (_currentLayout) {
@ -214,6 +220,7 @@ class Filmstrip extends PureComponent <Props> {
<div
className = { this.props._videosClassName }
id = 'remoteVideos'>
{!_disableSelfView && (
<div
className = 'filmstrip__videos'
id = 'filmstripLocalVideo'>
@ -224,6 +231,7 @@ class Filmstrip extends PureComponent <Props> {
}
</div>
</div>
)}
{
this._renderRemoteParticipants()
}
@ -301,6 +309,7 @@ class Filmstrip extends PureComponent <Props> {
*/
_gridItemKey({ columnIndex, rowIndex }) {
const {
_disableSelfView,
_columns,
_iAmRecorder,
_remoteParticipants,
@ -310,8 +319,8 @@ class Filmstrip extends PureComponent <Props> {
const index = (rowIndex * _columns) + columnIndex;
// When the thumbnails are reordered, local participant is inserted at index 0.
const localIndex = _thumbnailsReordered ? 0 : _remoteParticipantsLength;
const remoteIndex = _thumbnailsReordered && !_iAmRecorder ? index - 1 : index;
const localIndex = _thumbnailsReordered && !_disableSelfView ? 0 : _remoteParticipantsLength;
const remoteIndex = _thumbnailsReordered && !_iAmRecorder && !_disableSelfView ? index - 1 : index;
if (index > _remoteParticipantsLength - (_iAmRecorder ? 1 : 0)) {
return `empty-${index}`;
@ -571,6 +580,7 @@ function _mapStateToProps(state) {
thumbnailSize: tileViewThumbnailSize
} = state['features/filmstrip'].tileViewDimensions;
const _currentLayout = getCurrentLayout(state);
const disableSelfView = getDisableSelfView(state);
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const availableSpace = clientHeight - filmstripHeight;
@ -624,6 +634,7 @@ function _mapStateToProps(state) {
_className: className,
_columns: gridDimensions.columns,
_currentLayout,
_disableSelfView: disableSelfView,
_filmstripHeight: remoteFilmstripHeight,
_filmstripWidth: remoteFilmstripWidth,
_iAmRecorder: Boolean(iAmRecorder),

View File

@ -4,6 +4,7 @@ import { shouldComponentUpdate } from 'react-window';
import { connect } from '../../../base/redux';
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { getDisableSelfView } from '../../functions.any';
import Thumbnail from './Thumbnail';
@ -12,6 +13,11 @@ import Thumbnail from './Thumbnail';
*/
type Props = {
/**
* Whether or not to hide the self view.
*/
_disableSelfView: boolean,
/**
* The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view.
*/
@ -69,14 +75,14 @@ class ThumbnailWrapper extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _participantID, style, _horizontalOffset = 0 } = this.props;
const { _participantID, style, _horizontalOffset = 0, _disableSelfView } = this.props;
if (typeof _participantID !== 'string') {
return null;
}
if (_participantID === 'local') {
return (
return _disableSelfView ? null : (
<Thumbnail
horizontalOffset = { _horizontalOffset }
key = 'local'
@ -105,6 +111,7 @@ function _mapStateToProps(state, ownProps) {
const { remoteParticipants } = state['features/filmstrip'];
const remoteParticipantsLength = remoteParticipants.length;
const { testing = {} } = state['features/base/config'];
const disableSelfView = getDisableSelfView(state);
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
if (_currentLayout === LAYOUTS.TILE_VIEW) {
@ -114,7 +121,7 @@ function _mapStateToProps(state, ownProps) {
const index = (rowIndex * columns) + columnIndex;
let horizontalOffset;
const { iAmRecorder } = state['features/base/config'];
const participantsLenght = remoteParticipantsLength + (iAmRecorder ? 0 : 1);
const participantsLenght = remoteParticipantsLength + (iAmRecorder ? 0 : 1) - (disableSelfView ? 1 : 0);
if (rowIndex === rows - 1) { // center the last row
const { width: thumbnailWidth } = thumbnailSize;
@ -130,11 +137,12 @@ function _mapStateToProps(state, ownProps) {
}
// When the thumbnails are reordered, local participant is inserted at index 0.
const localIndex = enableThumbnailReordering ? 0 : remoteParticipantsLength;
const remoteIndex = enableThumbnailReordering && !iAmRecorder ? index - 1 : index;
const localIndex = enableThumbnailReordering && !disableSelfView ? 0 : remoteParticipantsLength;
const remoteIndex = enableThumbnailReordering && !iAmRecorder && !disableSelfView ? index - 1 : index;
if (!iAmRecorder && index === localIndex) {
return {
_disableSelfView: disableSelfView,
_participantID: 'local',
_horizontalOffset: horizontalOffset
};

View File

@ -1,5 +1,7 @@
// @flow
import { getParticipantCount } from '../base/participants';
import { setRemoteParticipants } from './actions';
/**
@ -80,3 +82,16 @@ export function updateRemoteParticipantsOnLeave(store: Object, participantId: ?s
reorderedParticipants.delete(participantId)
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
}
/**
* Gets the disable self view flag.
*
* @param {Object} state - Redux state.
* @returns {boolean}
*/
export function getDisableSelfView(state: Object) {
const { disableSelfView } = state['features/base/settings'];
const participantsCount = getParticipantCount(state);
return participantsCount === 1 ? false : disableSelfView;
}

View File

@ -27,8 +27,13 @@ import './subscriber.any';
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
*/
StateListenerRegistry.register(
/* selector */ getParticipantCountWithFake,
/* listener */ (numberOfParticipants, store) => {
/* selector */ state => {
return {
numberOfParticipants: getParticipantCountWithFake(state),
disableSelfView: state['features/base/settings'].disableSelfView
};
},
/* listener */ (currentState, store) => {
const state = store.getState();
if (shouldDisplayTileView(state)) {
@ -39,6 +44,8 @@ StateListenerRegistry.register(
store.dispatch(setTileViewDimensions(gridDimensions));
}
}
}, {
deepEquals: true
});
/**

View File

@ -1,6 +1,6 @@
/* @flow */
import { getCurrentConference } from '../base/conference';
import { CONFERENCE_JOINED, getCurrentConference } from '../base/conference';
import {
PARTICIPANT_JOINED,
PARTICIPANT_LEFT,
@ -12,6 +12,7 @@ import {
} from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { PARTICIPANTS_PANE_OPEN } from '../participants-pane/actionTypes';
import { openSettingsDialog, SETTINGS_TABS } from '../settings';
import {
clearNotifications,
@ -31,6 +32,21 @@ import { joinLeaveNotificationsDisabled } from './functions';
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED: {
const { dispatch, getState } = store;
const { disableSelfView } = getState()['features/base/settings'];
if (disableSelfView) {
dispatch(showNotification({
titleKey: 'notify.selfViewTitle',
customActionNameKey: [ 'settings.title' ],
customActionHandler: [ () =>
dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
]
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
}
break;
}
case PARTICIPANT_JOINED: {
const result = next(action);
const { participant: p } = action;

View File

@ -1,6 +1,7 @@
// @flow
import { batch } from 'react-redux';
import {
setFollowMe,
setStartMutedPolicy,
@ -9,6 +10,7 @@ import {
import { openDialog } from '../base/dialog';
import { i18next } from '../base/i18n';
import { updateSettings } from '../base/settings';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import { setPrejoinPageVisibility } from '../prejoin/actions';
import { setScreenshareFramerate } from '../screen-share/actions';
@ -24,6 +26,8 @@ import {
getSoundsTabProps
} from './functions';
import { SETTINGS_TABS } from '.';
declare var APP: Object;
/**
@ -155,6 +159,19 @@ export function submitProfileTab(newState: Object): Function {
if (newState.email !== currentState.email) {
APP.conference.changeLocalEmail(newState.email);
}
if (newState.disableSelfView !== currentState.disableSelfView) {
dispatch(updateSettings({ disableSelfView: newState.disableSelfView }));
if (newState.disableSelfView) {
dispatch(showNotification({
titleKey: 'notify.selfViewTitle',
customActionNameKey: [ 'settings.title' ],
customActionHandler: [ () =>
dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
]
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
}
};
}

View File

@ -1,6 +1,7 @@
// @flow
import Button from '@atlaskit/button/standard-button';
import Checkbox from '@atlaskit/checkbox';
import { FieldTextStateless } from '@atlaskit/field-text';
import React from 'react';
@ -32,6 +33,11 @@ export type Props = {
*/
authLogin: string,
/**
* Whether or not to hide the self view.
*/
disableSelfView: boolean,
/**
* The display name to display for the local participant.
*/
@ -77,6 +83,7 @@ class ProfileTab extends AbstractDialogTab<Props> {
this._onAuthToggle = this._onAuthToggle.bind(this);
this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
this._onEmailChange = this._onEmailChange.bind(this);
this._onChange = this._onChange.bind(this);
}
_onDisplayNameChange: (Object) => void;
@ -105,6 +112,19 @@ class ProfileTab extends AbstractDialogTab<Props> {
super._onChange({ email: value });
}
_onChange: (Object) => void;
/**
* Changes the disable self view state.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onChange({ target }) {
super._onChange({ disableSelfView: target.checked });
}
/**
* Implements React's {@link Component#render()}.
*
@ -115,6 +135,7 @@ class ProfileTab extends AbstractDialogTab<Props> {
const {
authEnabled,
displayName,
disableSelfView,
email,
readOnlyName,
t
@ -148,6 +169,12 @@ class ProfileTab extends AbstractDialogTab<Props> {
value = { email } />
</div>
</div>
<br />
<Checkbox
isChecked = { disableSelfView }
label = { t('videothumbnail.hideSelfView') }
name = 'disableSelfView'
onChange = { this._onChange } />
{ authEnabled && this._renderAuth() }
</div>
);

View File

@ -156,11 +156,13 @@ export function getProfileTabProps(stateful: Object | Function) {
conference
} = state['features/base/conference'];
const localParticipant = getLocalParticipant(state);
const { disableSelfView } = state['features/base/settings'];
return {
authEnabled: Boolean(conference && authEnabled),
authLogin,
displayName: localParticipant.name,
disableSelfView: Boolean(disableSelfView),
email: localParticipant.email,
readOnlyName: isNameReadOnly(state)
};

View File

@ -15,6 +15,7 @@ import {
SINGLE_COLUMN_BREAKPOINT,
TWO_COLUMN_BREAKPOINT
} from '../filmstrip/constants';
import { getDisableSelfView } from '../filmstrip/functions.any';
import { isVideoPlaying } from '../shared-video/functions';
import { LAYOUTS } from './constants';
@ -104,7 +105,10 @@ export function getTileViewGridDimensions(state: Object) {
// When in tile view mode, we must discount ourselves (the local participant) because our
// tile is not visible.
const { iAmRecorder } = state['features/base/config'];
const numberOfParticipants = getParticipantCountWithFake(state) - (iAmRecorder ? 1 : 0);
const disableSelfView = getDisableSelfView(state);
const numberOfParticipants = getParticipantCountWithFake(state)
- (iAmRecorder ? 1 : 0)
- (disableSelfView ? 1 : 0);
const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants));
const columns = Math.min(columnsToMaintainASquare, maxColumns);

View File

@ -0,0 +1,120 @@
/* @flow */
import React, { PureComponent } from 'react';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { updateSettings } from '../../../base/settings';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../../notifications';
import { openSettingsDialog, SETTINGS_TABS } from '../../../settings';
import VideoMenuButton from './VideoMenuButton';
/**
* The type of the React {@code Component} props of {@link HideSelfViewVideoButton}.
*/
type Props = {
/**
* Whether or not to hide the self view.
*/
disableSelfView: boolean,
/**
* The redux dispatch function.
*/
dispatch: Function,
/**
* Click handler executed aside from the main action.
*/
onClick?: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* Implements a React {@link Component} which displays a button for hiding the local video.
*
* @augments Component
*/
class HideSelfViewVideoButton extends PureComponent<Props> {
/**
* Initializes a new {@code HideSelfViewVideoButton} instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._onClick = this._onClick.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {null|ReactElement}
*/
render() {
const {
t
} = this.props;
return (
<VideoMenuButton
buttonText = { t('videothumbnail.hideSelfView') }
displayClass = 'hideselflink'
id = 'hideselfviewbutton'
onClick = { this._onClick } />
);
}
_onClick: () => void;
/**
* Hides the local video.
*
* @private
* @returns {void}
*/
_onClick() {
const { disableSelfView, dispatch, onClick } = this.props;
onClick && onClick();
dispatch(updateSettings({
disableSelfView: !disableSelfView
}));
if (!disableSelfView) {
dispatch(showNotification({
titleKey: 'notify.selfViewTitle',
customActionNameKey: [ 'settings.title' ],
customActionHandler: [ () =>
dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
]
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
}
}
/**
* Maps (parts of) the Redux state to the associated {@code FlipLocalVideoButton}'s props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
const { disableSelfView } = state['features/base/config'];
return {
disableSelfView: Boolean(disableSelfView)
};
}
export default translate(connect(_mapStateToProps)(HideSelfViewVideoButton));

View File

@ -19,6 +19,7 @@ import { renderConnectionStatus } from '../../actions.web';
import ConnectionStatusButton from './ConnectionStatusButton';
import FlipLocalVideoButton from './FlipLocalVideoButton';
import HideSelfViewVideoButton from './HideSelfViewVideoButton';
import VideoMenu from './VideoMenu';
@ -131,6 +132,7 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
: (
<VideoMenu id = 'localVideoMenu'>
<FlipLocalVideoButton onClick = { hidePopover } />
<HideSelfViewVideoButton onClick = { hidePopover } />
{ isMobileBrowser()
&& <ConnectionStatusButton participantId = { _localParticipantId } />
}