diff --git a/config.js b/config.js index 2923079e8f..eb3063691f 100644 --- a/config.js +++ b/config.js @@ -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, diff --git a/lang/main.json b/lang/main.json index 236326c8cc..8f79221fb9 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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", diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.js index f6b5309641..d5604c127d 100644 --- a/react/features/base/config/configWhitelist.js +++ b/react/features/base/config/configWhitelist.js @@ -113,6 +113,7 @@ export default [ 'disableRemoteMute', 'disableResponsiveTiles', 'disableRtx', + 'disableSelfView', 'disableScreensharingVirtualBackground', 'disableShortcuts', 'disableShowMoreStats', diff --git a/react/features/base/config/middleware.js b/react/features/base/config/middleware.js index a512def0c7..fecd1e80f0 100644 --- a/react/features/base/config/middleware.js +++ b/react/features/base/config/middleware.js @@ -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 diff --git a/react/features/base/settings/reducer.js b/react/features/base/settings/reducer.js index ad286385a3..3ba7b7d44b 100644 --- a/react/features/base/settings/reducer.js +++ b/react/features/base/settings/reducer.js @@ -21,6 +21,7 @@ const DEFAULT_STATE = { disableCallIntegration: undefined, disableCrashReporting: undefined, disableP2P: undefined, + disableSelfView: false, displayName: undefined, email: undefined, localFlipX: true, diff --git a/react/features/filmstrip/actions.web.js b/react/features/filmstrip/actions.web.js index d153f9a404..a328f38258 100644 --- a/react/features/filmstrip/actions.web.js +++ b/react/features/filmstrip/actions.web.js @@ -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, 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, 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 } diff --git a/react/features/filmstrip/components/native/Filmstrip.js b/react/features/filmstrip/components/native/Filmstrip.js index 3f02a549be..d28b53cd66 100644 --- a/react/features/filmstrip/components/native/Filmstrip.js +++ b/react/features/filmstrip/components/native/Filmstrip.js @@ -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 { * @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 { ? width / (thumbnailWidth + (2 * margin)) : height / (thumbnailHeight + (2 * margin)) ); - const participants = this._separateLocalThumbnail ? _participants : [ _localParticipantId, ..._participants ]; + const participants = this._separateLocalThumbnail || _disableSelfView + ? _participants : [ _localParticipantId, ..._participants ]; return ( { this._separateLocalThumbnail && !isNarrowAspectRatio + && !_disableSelfView && } { viewabilityConfig = { this._viewabilityConfig } windowSize = { 2 } /> { - this._separateLocalThumbnail && isNarrowAspectRatio + this._separateLocalThumbnail + && isNarrowAspectRatio + && !_disableSelfView && } @@ -271,6 +281,7 @@ class Filmstrip extends PureComponent { */ 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) diff --git a/react/features/filmstrip/components/native/TileView.js b/react/features/filmstrip/components/native/TileView.js index 8822fbae8f..39b19c0a54 100644 --- a/react/features/filmstrip/components/native/TileView.js +++ b/react/features/filmstrip/components/native/TileView.js @@ -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 { * @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 { 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), diff --git a/react/features/filmstrip/components/web/Filmstrip.js b/react/features/filmstrip/components/web/Filmstrip.js index 77ad566d28..8f9f8600ad 100644 --- a/react/features/filmstrip/components/web/Filmstrip.js +++ b/react/features/filmstrip/components/web/Filmstrip.js @@ -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 { */ render() { const filmstripStyle = { }; - const { _currentLayout } = this.props; + const { _currentLayout, _disableSelfView } = this.props; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; switch (_currentLayout) { @@ -214,16 +220,18 @@ class Filmstrip extends PureComponent {
-
-
- { - !tileViewActive && - } + {!_disableSelfView && ( +
+
+ { + !tileViewActive && + } +
-
+ )} { this._renderRemoteParticipants() } @@ -301,6 +309,7 @@ class Filmstrip extends PureComponent { */ _gridItemKey({ columnIndex, rowIndex }) { const { + _disableSelfView, _columns, _iAmRecorder, _remoteParticipants, @@ -310,8 +319,8 @@ class Filmstrip extends PureComponent { 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), diff --git a/react/features/filmstrip/components/web/ThumbnailWrapper.js b/react/features/filmstrip/components/web/ThumbnailWrapper.js index 1e4d2a7f23..d9655bb2d2 100644 --- a/react/features/filmstrip/components/web/ThumbnailWrapper.js +++ b/react/features/filmstrip/components/web/ThumbnailWrapper.js @@ -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 { * @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 : ( { + /* 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 }); /** diff --git a/react/features/notifications/middleware.js b/react/features/notifications/middleware.js index fcc2c11382..b8003d4d20 100644 --- a/react/features/notifications/middleware.js +++ b/react/features/notifications/middleware.js @@ -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; diff --git a/react/features/settings/actions.js b/react/features/settings/actions.js index 69a362668f..e44dab7680 100644 --- a/react/features/settings/actions.js +++ b/react/features/settings/actions.js @@ -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)); + } + } }; } diff --git a/react/features/settings/components/web/ProfileTab.js b/react/features/settings/components/web/ProfileTab.js index 0af6ec5ad7..c7645dbf7a 100644 --- a/react/features/settings/components/web/ProfileTab.js +++ b/react/features/settings/components/web/ProfileTab.js @@ -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 { 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 { 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 { const { authEnabled, displayName, + disableSelfView, email, readOnlyName, t @@ -148,6 +169,12 @@ class ProfileTab extends AbstractDialogTab { value = { email } />
+
+ { authEnabled && this._renderAuth() } ); diff --git a/react/features/settings/functions.js b/react/features/settings/functions.js index 8355c9ed17..e2ff6c7af8 100644 --- a/react/features/settings/functions.js +++ b/react/features/settings/functions.js @@ -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) }; diff --git a/react/features/video-layout/functions.js b/react/features/video-layout/functions.js index 1161dae580..d3cd6be74d 100644 --- a/react/features/video-layout/functions.js +++ b/react/features/video-layout/functions.js @@ -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); diff --git a/react/features/video-menu/components/web/HideSelfViewVideoButton.js b/react/features/video-menu/components/web/HideSelfViewVideoButton.js new file mode 100644 index 0000000000..7684c4f03d --- /dev/null +++ b/react/features/video-menu/components/web/HideSelfViewVideoButton.js @@ -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 { + /** + * 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 ( + + ); + } + + _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)); diff --git a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js index c30038c8ee..5b5b29b503 100644 --- a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js +++ b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js @@ -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 { : ( + { isMobileBrowser() && }