fix(shared-video,video-menu) add ability to stop shared video from video menu

Specifically, in the bottom sheet (on mobile) and participants pane.
This commit is contained in:
Calinteodor 2021-08-04 11:51:05 +03:00 committed by GitHub
parent e421a119e1
commit 3c2ad24652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 427 additions and 82 deletions

View File

@ -13,7 +13,8 @@ import {
getParticipantCount, getParticipantCount,
isEveryoneModerator, isEveryoneModerator,
pinParticipant, pinParticipant,
getParticipantByIdOrUndefined getParticipantByIdOrUndefined,
getLocalParticipant
} from '../../../base/participants'; } from '../../../base/participants';
import { Container } from '../../../base/react'; import { Container } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -24,6 +25,8 @@ import { DisplayNameLabel } from '../../../display-name';
import { toggleToolboxVisible } from '../../../toolbox/actions.native'; import { toggleToolboxVisible } from '../../../toolbox/actions.native';
import { RemoteVideoMenu } from '../../../video-menu'; import { RemoteVideoMenu } from '../../../video-menu';
import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent'; import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
import SharedVideoMenu
from '../../../video-menu/components/native/SharedVideoMenu';
import AudioMutedIndicator from './AudioMutedIndicator'; import AudioMutedIndicator from './AudioMutedIndicator';
import DominantSpeakerIndicator from './DominantSpeakerIndicator'; import DominantSpeakerIndicator from './DominantSpeakerIndicator';
@ -48,6 +51,11 @@ type Props = {
*/ */
_largeVideo: Object, _largeVideo: Object,
/**
* Shared video local participant owner.
*/
_localVideoOwner: boolean,
/** /**
* The Redux representation of the participant to display. * The Redux representation of the participant to display.
*/ */
@ -116,6 +124,7 @@ function Thumbnail(props: Props) {
const { const {
_audioMuted: audioMuted, _audioMuted: audioMuted,
_largeVideo: largeVideo, _largeVideo: largeVideo,
_localVideoOwner,
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator, _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator, _renderModeratorIndicator: renderModeratorIndicator,
_participant: participant, _participant: participant,
@ -144,11 +153,19 @@ function Thumbnail(props: Props) {
dispatch(openDialog(ConnectionStatusComponent, { dispatch(openDialog(ConnectionStatusComponent, {
participantID: participant.id participantID: participant.id
})); }));
} else { } else if (participant.isFakeParticipant) {
dispatch(openDialog(RemoteVideoMenu, { if (_localVideoOwner) {
dispatch(openDialog(SharedVideoMenu, {
participant participant
})); }));
} }
return null;
}
dispatch(openDialog(RemoteVideoMenu, {
participant
}));
}, [ participant, dispatch ]); }, [ participant, dispatch ]);
return ( return (
@ -223,9 +240,11 @@ function _mapStateToProps(state, ownProps) {
// filmstrip doesn't render the video of the participant who is rendered on // filmstrip doesn't render the video of the participant who is rendered on
// the stage i.e. as a large video. // the stage i.e. as a large video.
const largeVideo = state['features/large-video']; const largeVideo = state['features/large-video'];
const { ownerId } = state['features/shared-video'];
const tracks = state['features/base/tracks']; const tracks = state['features/base/tracks'];
const { participantID } = ownProps; const { participantID } = ownProps;
const participant = getParticipantByIdOrUndefined(state, participantID); const participant = getParticipantByIdOrUndefined(state, participantID);
const localParticipantId = getLocalParticipant(state).id;
const id = participant?.id; const id = participant?.id;
const audioTrack const audioTrack
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id); = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
@ -240,6 +259,7 @@ function _mapStateToProps(state, ownProps) {
return { return {
_audioMuted: audioTrack?.muted ?? true, _audioMuted: audioTrack?.muted ?? true,
_largeVideo: largeVideo, _largeVideo: largeVideo,
_localVideoOwner: Boolean(ownerId === localParticipantId),
_participant: participant, _participant: participant,
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator, _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator, _renderModeratorIndicator: renderModeratorIndicator,

View File

@ -1,6 +1,7 @@
// @flow // @flow
import { openDialog } from '../base/dialog'; import { openDialog } from '../base/dialog';
import { SharedVideoMenu } from '../video-menu';
import ConnectionStatusComponent import ConnectionStatusComponent
from '../video-menu/components/native/ConnectionStatusComponent'; from '../video-menu/components/native/ConnectionStatusComponent';
import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu'; import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
@ -42,6 +43,16 @@ export function showContextMenuDetails(participant: Object) {
return openDialog(RemoteVideoMenu, { participant }); return openDialog(RemoteVideoMenu, { participant });
} }
/**
* Displays the shared video menu.
*
* @param {Object} participant - The selected meeting participant.
* @returns {Function}
*/
export function showSharedVideoMenu(participant: Object) {
return openDialog(SharedVideoMenu, { participant });
}
/** /**
* Sets the volume. * Sets the volume.
* *

View File

@ -6,20 +6,34 @@ import { Text, View } from 'react-native';
import { Button } from 'react-native-paper'; import { Button } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Icon, IconInviteMore } from '../../../base/icons'; import { Icon, IconInviteMore } from '../../../base/icons';
import { import {
getLocalParticipant, getLocalParticipant,
getParticipantCountWithFake, getParticipantCountWithFake,
getRemoteParticipants getRemoteParticipants
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { doInvitePeople } from '../../../invite/actions.native'; import { doInvitePeople } from '../../../invite/actions.native';
import { showConnectionStatus, showContextMenuDetails } from '../../actions.native'; import {
showConnectionStatus,
showContextMenuDetails,
showSharedVideoMenu
} from '../../actions.native';
import { shouldRenderInviteButton } from '../../functions'; import { shouldRenderInviteButton } from '../../functions';
import MeetingParticipantItem from './MeetingParticipantItem'; import MeetingParticipantItem from './MeetingParticipantItem';
import styles from './styles'; import styles from './styles';
export const MeetingParticipantList = () => { type Props = {
/**
* Shared video local participant owner.
*/
_localVideoOwner: boolean
}
const MeetingParticipantList = ({ _localVideoOwner }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const items = []; const items = [];
const localParticipant = useSelector(getLocalParticipant); const localParticipant = useSelector(getLocalParticipant);
@ -30,7 +44,26 @@ export const MeetingParticipantList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
// eslint-disable-next-line react/no-multi-comp // eslint-disable-next-line react/no-multi-comp
const renderParticipant = p => ( const renderParticipant = p => {
if (p.isFakeParticipant) {
if (_localVideoOwner) {
return (
<MeetingParticipantItem
key = { p.id }
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
onPress = { () => dispatch(showSharedVideoMenu(p)) }
participantID = { p.id } />
);
}
return (
<MeetingParticipantItem
key = { p.id }
participantID = { p.id } />
);
}
return (
<MeetingParticipantItem <MeetingParticipantItem
key = { p.id } key = { p.id }
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */ /* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
@ -38,6 +71,7 @@ export const MeetingParticipantList = () => {
? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) } ? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
participantID = { p.id } /> participantID = { p.id } />
); );
};
items.push(renderParticipant(localParticipant)); items.push(renderParticipant(localParticipant));
@ -71,3 +105,20 @@ export const MeetingParticipantList = () => {
); );
}; };
/**
* Maps (parts of) the redux state to the associated props for this component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state): Object {
const { ownerId } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state).id;
return {
_localVideoOwner: Boolean(ownerId === localParticipantId)
};
}
export default connect(_mapStateToProps)(MeetingParticipantList);

View File

@ -19,7 +19,7 @@ import { close } from '../../actions.native';
import { ContextMenuMore } from './ContextMenuMore'; import { ContextMenuMore } from './ContextMenuMore';
import LobbyParticipantList from './LobbyParticipantList'; import LobbyParticipantList from './LobbyParticipantList';
import { MeetingParticipantList } from './MeetingParticipantList'; import MeetingParticipantList from './MeetingParticipantList';
import styles from './styles'; import styles from './styles';
/** /**

View File

@ -1,5 +1,6 @@
// @flow // @flow
export { default as MeetingParticipantList } from './MeetingParticipantList';
export { default as ParticipantsPane } from './ParticipantsPane'; export { default as ParticipantsPane } from './ParticipantsPane';
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton'; export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject'; export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';

View File

@ -11,9 +11,11 @@ import {
IconMessage, IconMessage,
IconMicDisabled, IconMicDisabled,
IconMuteEveryoneElse, IconMuteEveryoneElse,
IconShareVideo,
IconVideoOff IconVideoOff
} from '../../../base/icons'; } from '../../../base/icons';
import { import {
getLocalParticipant,
getParticipantByIdOrUndefined, getParticipantByIdOrUndefined,
isLocalParticipantModerator, isLocalParticipantModerator,
isParticipantModerator isParticipantModerator
@ -21,6 +23,7 @@ import {
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks'; import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
import { openChat } from '../../../chat/actions'; import { openChat } from '../../../chat/actions';
import { stopSharedVideo } from '../../../shared-video/actions.any';
import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu'; import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog'; import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
import { getComputedOuterHeight } from '../../functions'; import { getComputedOuterHeight } from '../../functions';
@ -60,6 +63,11 @@ type Props = {
*/ */
_isParticipantAudioMuted: boolean, _isParticipantAudioMuted: boolean,
/**
* Shared video local participant owner.
*/
_localVideoOwner: boolean,
/** /**
* Participant reference * Participant reference
*/ */
@ -143,6 +151,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this); this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this);
this._onMuteVideo = this._onMuteVideo.bind(this); this._onMuteVideo = this._onMuteVideo.bind(this);
this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this); this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this);
this._onStopSharedVideo = this._onStopSharedVideo.bind(this);
this._position = this._position.bind(this); this._position = this._position.bind(this);
} }
@ -176,6 +185,19 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
})); }));
} }
_onStopSharedVideo: () => void;
/**
* Stops shared video.
*
* @returns {void}
*/
_onStopSharedVideo() {
const { dispatch } = this.props;
dispatch(stopSharedVideo());
}
_onMuteEveryoneElse: () => void; _onMuteEveryoneElse: () => void;
/** /**
@ -282,6 +304,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
_isParticipantModerator, _isParticipantModerator,
_isParticipantVideoMuted, _isParticipantVideoMuted,
_isParticipantAudioMuted, _isParticipantAudioMuted,
_localVideoOwner,
_participant, _participant,
onEnter, onEnter,
onLeave, onLeave,
@ -302,6 +325,9 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
onClick = { onSelect } onClick = { onSelect }
onMouseEnter = { onEnter } onMouseEnter = { onEnter }
onMouseLeave = { onLeave }> onMouseLeave = { onLeave }>
{
!_participant.isFakeParticipant && (
<>
<ContextMenuItemGroup> <ContextMenuItemGroup>
{ {
_isLocalModerator && ( _isLocalModerator && (
@ -362,6 +388,18 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
) )
} }
</ContextMenuItemGroup> </ContextMenuItemGroup>
</>
)
}
{
_participant.isFakeParticipant && _localVideoOwner && (
<ContextMenuItem onClick = { this._onStopSharedVideo }>
<ContextMenuIcon src = { IconShareVideo } />
<span>{t('toolbar.stopSharedVideo')}</span>
</ContextMenuItem>
)
}
</ContextMenu> </ContextMenu>
); );
} }
@ -377,7 +415,8 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
*/ */
function _mapStateToProps(state, ownProps): Object { function _mapStateToProps(state, ownProps): Object {
const { participantID } = ownProps; const { participantID } = ownProps;
const { ownerId } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state).id;
const participant = getParticipantByIdOrUndefined(state, participantID); const participant = getParticipantByIdOrUndefined(state, participantID);
const _isLocalModerator = isLocalParticipantModerator(state); const _isLocalModerator = isLocalParticipantModerator(state);
@ -392,6 +431,7 @@ function _mapStateToProps(state, ownProps): Object {
_isParticipantModerator, _isParticipantModerator,
_isParticipantVideoMuted, _isParticipantVideoMuted,
_isParticipantAudioMuted, _isParticipantAudioMuted,
_localVideoOwner: Boolean(ownerId === localParticipantId),
_participant: participant _participant: participant
}; };
} }

View File

@ -2,7 +2,11 @@
import React from 'react'; import React from 'react';
import { getParticipantByIdOrUndefined, getParticipantDisplayName } from '../../../base/participants'; import {
getLocalParticipant,
getParticipantByIdOrUndefined,
getParticipantDisplayName
} from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks'; import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
import { ACTION_TRIGGER, MEDIA_STATE, type MediaState } from '../../constants'; import { ACTION_TRIGGER, MEDIA_STATE, type MediaState } from '../../constants';
@ -34,6 +38,16 @@ type Props = {
*/ */
_local: boolean, _local: boolean,
/**
* Shared video local participant owner.
*/
_localVideoOwner: boolean,
/**
* The participant.
*/
_participant: Object,
/** /**
* The participant ID. * The participant ID.
* *
@ -108,7 +122,9 @@ function MeetingParticipantItem({
_audioMediaState, _audioMediaState,
_displayName, _displayName,
_isVideoMuted, _isVideoMuted,
_localVideoOwner,
_local, _local,
_participant,
_participantID, _participantID,
_quickActionButtonType, _quickActionButtonType,
_raisedHand, _raisedHand,
@ -133,6 +149,9 @@ function MeetingParticipantItem({
raisedHand = { _raisedHand } raisedHand = { _raisedHand }
videoMuteState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } videoMuteState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
youText = { youText }> youText = { youText }>
{
!_participant.isFakeParticipant && (
<>
<ParticipantQuickAction <ParticipantQuickAction
askUnmuteText = { askUnmuteText } askUnmuteText = { askUnmuteText }
buttonType = { _quickActionButtonType } buttonType = { _quickActionButtonType }
@ -142,6 +161,16 @@ function MeetingParticipantItem({
<ParticipantActionEllipsis <ParticipantActionEllipsis
aria-label = { participantActionEllipsisLabel } aria-label = { participantActionEllipsisLabel }
onClick = { onContextMenu } /> onClick = { onContextMenu } />
</>
)
}
{
_participant.isFakeParticipant && _localVideoOwner && (
<ParticipantActionEllipsis
aria-label = { participantActionEllipsisLabel }
onClick = { onContextMenu } />
)
}
</ParticipantItem> </ParticipantItem>
); );
} }
@ -156,6 +185,8 @@ function MeetingParticipantItem({
*/ */
function _mapStateToProps(state, ownProps): Object { function _mapStateToProps(state, ownProps): Object {
const { participantID } = ownProps; const { participantID } = ownProps;
const { ownerId } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state).id;
const participant = getParticipantByIdOrUndefined(state, participantID); const participant = getParticipantByIdOrUndefined(state, participantID);
@ -170,6 +201,8 @@ function _mapStateToProps(state, ownProps): Object {
_isAudioMuted, _isAudioMuted,
_isVideoMuted, _isVideoMuted,
_local: Boolean(participant?.local), _local: Boolean(participant?.local),
_localVideoOwner: Boolean(ownerId === localParticipantId),
_participant: participant,
_participantID: participant?.id, _participantID: participant?.id,
_quickActionButtonType, _quickActionButtonType,
_raisedHand: Boolean(participant?.raisedHand) _raisedHand: Boolean(participant?.raisedHand)

View File

@ -235,7 +235,6 @@ export const ParticipantActionsHover = styled(ParticipantActions)`
position: absolute; position: absolute;
top: 0; top: 0;
transform: translateX(-100%); transform: translateX(-100%);
width: 40px;
} }
`; `;

View File

@ -1,7 +1,7 @@
// @flow // @flow
import { hideDialog } from '../base/dialog'; import { hideDialog } from '../base/dialog';
import { RemoteVideoMenu } from './components/native'; import { RemoteVideoMenu, SharedVideoMenu } from './components/native';
/** /**
* Hides the remote video menu. * Hides the remote video menu.
@ -12,4 +12,13 @@ export function hideRemoteVideoMenu() {
return hideDialog(RemoteVideoMenu); return hideDialog(RemoteVideoMenu);
} }
/**
* Hides the shared video menu.
*
* @returns {Function}
*/
export function hideSharedVideoMenu() {
return hideDialog(SharedVideoMenu);
}
export * from './actions.any'; export * from './actions.any';

View File

@ -0,0 +1,180 @@
// @flow
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { Divider } from 'react-native-paper';
import { Avatar } from '../../../base/avatar';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, isDialogOpen } from '../../../base/dialog';
import {
getParticipantById,
getParticipantDisplayName
} from '../../../base/participants';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import { SharedVideoButton } from '../../../shared-video/components';
import { hideSharedVideoMenu } from '../../actions.native';
import styles from './styles';
/**
* Size of the rendered avatar in the menu.
*/
const AVATAR_SIZE = 24;
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The participant for which this menu opened for.
*/
participant: Object,
/**
* The color-schemed stylesheet of the BottomSheet.
*/
_bottomSheetStyles: StyleType,
/**
* True if the menu is currently open, false otherwise.
*/
_isOpen: boolean,
/**
* Whether the participant is present in the room or not.
*/
_isParticipantAvailable?: boolean,
/**
* Display name of the participant retrieved from Redux.
*/
_participantDisplayName: string,
/**
* The ID of the participant.
*/
_participantID: ?string,
}
// eslint-disable-next-line prefer-const
let SharedVideoMenu_;
/**
* Class to implement a popup menu that opens upon long pressing a fake participant thumbnail.
*/
class SharedVideoMenu extends PureComponent<Props> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onCancel = this._onCancel.bind(this);
this._renderMenuHeader = this._renderMenuHeader.bind(this);
}
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const {
_isParticipantAvailable,
participant
} = this.props;
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
participantID: participant.id,
styles: this.props._bottomSheetStyles.buttons
};
return (
<BottomSheet
onCancel = { this._onCancel }
renderHeader = { this._renderMenuHeader }
showSlidingView = { _isParticipantAvailable }>
<Divider style = { styles.divider } />
<SharedVideoButton { ...buttonProps } />
</BottomSheet>
);
}
_onCancel: () => boolean;
/**
* Callback to hide the {@code SharedVideoMenu}.
*
* @private
* @returns {boolean}
*/
_onCancel() {
if (this.props._isOpen) {
this.props.dispatch(hideSharedVideoMenu());
return true;
}
return false;
}
_renderMenuHeader: () => React$Element<any>;
/**
* Function to render the menu's header.
*
* @returns {React$Element}
*/
_renderMenuHeader() {
const { _bottomSheetStyles, participant } = this.props;
return (
<View
style = { [
_bottomSheetStyles.sheet,
styles.participantNameContainer ] }>
<Avatar
participantId = { participant.id }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
{ this.props._participantDisplayName }
</Text>
</View>
);
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @param {Object} ownProps - Properties of component.
* @private
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
const { participant } = ownProps;
const isParticipantAvailable = getParticipantById(state, participant.id);
return {
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
_isOpen: isDialogOpen(state, SharedVideoMenu_),
_isParticipantAvailable: Boolean(isParticipantAvailable),
_participantDisplayName: getParticipantDisplayName(state, participant.id),
_participantID: participant.id
};
}
SharedVideoMenu_ = connect(_mapStateToProps)(SharedVideoMenu);
export default SharedVideoMenu_;

View File

@ -8,4 +8,5 @@ export { default as MuteEveryonesVideoDialog } from './MuteEveryonesVideoDialog'
export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog'; export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog'; export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
export { default as RemoteVideoMenu } from './RemoteVideoMenu'; export { default as RemoteVideoMenu } from './RemoteVideoMenu';
export { default as SharedVideoMenu } from './SharedVideoMenu';
export { default as VolumeSlider } from './VolumeSlider'; export { default as VolumeSlider } from './VolumeSlider';