Move audio/video controls out to bottom of screen if on mobile

auto_join_3.3
Håvar Aambø Fosstveit 2020-03-23 15:30:43 +01:00
parent ec8c347a23
commit 33ef7746a3
7 changed files with 299 additions and 96 deletions

View File

@ -200,7 +200,10 @@ export default class RoomClient
if (device.bowser.getPlatformType() === 'desktop') if (device.bowser.getPlatformType() === 'desktop')
this._maxSpotlights = lastN; this._maxSpotlights = lastN;
else else
{
this._maxSpotlights = mobileLastN; this._maxSpotlights = mobileLastN;
store.dispatch(meActions.setIsMobile());
}
store.dispatch( store.dispatch(
settingsActions.setLastN(this._maxSpotlights)); settingsActions.setLastN(this._maxSpotlights));

View File

@ -4,6 +4,11 @@ export const setMe = ({ peerId, loginEnabled }) =>
payload : { peerId, loginEnabled } payload : { peerId, loginEnabled }
}); });
export const setIsMobile = () =>
({
type : 'SET_IS_MOBILE'
});
export const loggedIn = (flag) => export const loggedIn = (flag) =>
({ ({
type : 'LOGGED_IN', type : 'LOGGED_IN',

View File

@ -299,6 +299,8 @@ const Me = (props) =>
defaultMessage='ME' defaultMessage='ME'
/> />
</p> </p>
{ !me.isMobile &&
<React.Fragment>
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'left'}> <Tooltip title={micTip} placement={smallScreen ? 'top' : 'left'}>
<div> <div>
<Fab <Fab
@ -395,6 +397,8 @@ const Me = (props) =>
</Fab> </Fab>
</div> </div>
</Tooltip> </Tooltip>
</React.Fragment>
}
</div> </div>
<VideoView <VideoView

View File

@ -121,6 +121,7 @@ const Peer = (props) =>
advancedMode, advancedMode,
peer, peer,
activeSpeaker, activeSpeaker,
isMobile,
micConsumer, micConsumer,
webcamConsumer, webcamConsumer,
screenConsumer, screenConsumer,
@ -259,7 +260,7 @@ const Peer = (props) =>
</div> </div>
</Tooltip> </Tooltip>
{ !smallScreen && { !isMobile &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -407,7 +408,7 @@ const Peer = (props) =>
}, 2000); }, 2000);
}} }}
> >
{ !smallScreen && { !isMobile &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -507,6 +508,7 @@ Peer.propTypes =
screenConsumer : appPropTypes.Consumer, screenConsumer : appPropTypes.Consumer,
windowConsumer : PropTypes.string, windowConsumer : PropTypes.string,
activeSpeaker : PropTypes.bool, activeSpeaker : PropTypes.bool,
isMobile : PropTypes.bool,
spacing : PropTypes.number, spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
smallButtons : PropTypes.bool, smallButtons : PropTypes.bool,
@ -526,7 +528,8 @@ const makeMapStateToProps = (initialState, { id }) =>
peer : state.peers[id], peer : state.peers[id],
...getPeerConsumers(state, id), ...getPeerConsumers(state, id),
windowConsumer : state.room.windowConsumer, windowConsumer : state.room.windowConsumer,
activeSpeaker : id === state.room.activeSpeakerId activeSpeaker : id === state.room.activeSpeakerId,
isMobile : state.me.isMobile
}; };
}; };
@ -560,7 +563,8 @@ export default withRoomContext(connect(
prev.peers === next.peers && prev.peers === next.peers &&
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.activeSpeakerId === next.room.activeSpeakerId &&
prev.room.windowConsumer === next.room.windowConsumer prev.room.windowConsumer === next.room.windowConsumer &&
prev.me.isMobile === next.me.isMobile
); );
} }
} }

View File

@ -0,0 +1,172 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { meProducersSelector } from '../Selectors';
import { withStyles } from '@material-ui/core/styles';
import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext';
import Fab from '@material-ui/core/Fab';
import Tooltip from '@material-ui/core/Tooltip';
import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff';
import VideoIcon from '@material-ui/icons/Videocam';
import VideoOffIcon from '@material-ui/icons/VideocamOff';
const styles = (theme) =>
({
root :
{
position : 'fixed',
zIndex : 500,
display : 'flex',
flexDirection : 'row',
bottom : '0.5em',
left : '50%',
transform : 'translate(-50%, -0%)'
},
fab :
{
margin : theme.spacing(1)
}
});
const MobileControls = (props) =>
{
const {
roomClient,
me,
micProducer,
webcamProducer,
classes
} = props;
let micState;
let micTip;
if (!me.canSendMic)
{
micState = 'unsupported';
micTip = 'Audio unsupported';
}
else if (!micProducer)
{
micState = 'off';
micTip = 'Activate audio';
}
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
{
micState = 'on';
micTip = 'Mute audio';
}
else
{
micState = 'muted';
micTip = 'Unmute audio';
}
let webcamState;
let webcamTip;
if (!me.canSendWebcam)
{
webcamState = 'unsupported';
webcamTip = 'Video unsupported';
}
else if (webcamProducer)
{
webcamState = 'on';
webcamTip = 'Stop video';
}
else
{
webcamState = 'off';
webcamTip = 'Start video';
}
return (
<div className={classes.root}>
<Tooltip title={micTip} placement='top'>
<div>
<Fab
aria-label='Mute mic'
className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'}
size='large'
onClick={() =>
{
if (micState === 'off')
roomClient.enableMic();
else if (micState === 'on')
roomClient.muteMic();
else
roomClient.unmuteMic();
}}
>
{ micState === 'on' ?
<MicIcon />
:
<MicOffIcon />
}
</Fab>
</div>
</Tooltip>
<Tooltip title={webcamTip} placement='top'>
<div>
<Fab
aria-label='Mute video'
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'}
size='large'
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.enableWebcam();
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</Fab>
</div>
</Tooltip>
</div>
);
};
MobileControls.propTypes =
{
roomClient : PropTypes.any.isRequired,
me : appPropTypes.Me.isRequired,
micProducer : appPropTypes.Producer,
webcamProducer : appPropTypes.Producer,
classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
...meProducersSelector(state),
me : state.me
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.producers === next.producers &&
prev.me === next.me
);
}
}
)(withStyles(styles, { withTheme: true })(MobileControls)));

View File

@ -23,6 +23,7 @@ import VideoWindow from './VideoWindow/VideoWindow';
import LockDialog from './AccessControl/LockDialog/LockDialog'; import LockDialog from './AccessControl/LockDialog/LockDialog';
import Settings from './Settings/Settings'; import Settings from './Settings/Settings';
import TopBar from './Controls/TopBar'; import TopBar from './Controls/TopBar';
import MobileControls from './Controls/MobileControls';
const TIMEOUT = 5 * 1000; const TIMEOUT = 5 * 1000;
@ -140,6 +141,7 @@ class Room extends React.PureComponent
room, room,
advancedMode, advancedMode,
toolAreaOpen, toolAreaOpen,
isMobile,
toggleToolArea, toggleToolArea,
classes, classes,
theme theme
@ -204,6 +206,10 @@ class Room extends React.PureComponent
<View advancedMode={advancedMode} /> <View advancedMode={advancedMode} />
{ isMobile &&
<MobileControls />
}
{ room.lockDialogOpen && { room.lockDialogOpen &&
<LockDialog /> <LockDialog />
} }
@ -222,6 +228,7 @@ Room.propTypes =
advancedMode : PropTypes.bool.isRequired, advancedMode : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired,
setToolbarsVisible : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired,
isMobile : PropTypes.bool,
toggleToolArea : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired
@ -231,7 +238,8 @@ const mapStateToProps = (state) =>
({ ({
room : state.room, room : state.room,
advancedMode : state.settings.advancedMode, advancedMode : state.settings.advancedMode,
toolAreaOpen : state.toolarea.toolAreaOpen toolAreaOpen : state.toolarea.toolAreaOpen,
isMobile : state.me.isMobile
}); });
const mapDispatchToProps = (dispatch) => const mapDispatchToProps = (dispatch) =>
@ -256,7 +264,8 @@ export default connect(
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.settings.advancedMode === next.settings.advancedMode && prev.settings.advancedMode === next.settings.advancedMode &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
prev.me.isMobile === next.me.isMobile
); );
} }
} }

View File

@ -2,6 +2,7 @@ const initialState =
{ {
id : null, id : null,
picture : null, picture : null,
isMobile : false,
canSendMic : false, canSendMic : false,
canSendWebcam : false, canSendWebcam : false,
canShareScreen : false, canShareScreen : false,
@ -36,6 +37,11 @@ const me = (state = initialState, action) =>
}; };
} }
case 'SET_IS_MOBILE':
{
return { ...state, isMobile: true };
}
case 'LOGGED_IN': case 'LOGGED_IN':
{ {
const { flag } = action.payload; const { flag } = action.payload;