Added filmstrip and settings back. Still some issues with layout if a peer shares screen in filmstrip.

master
Håvar Aambø Fosstveit 2019-03-29 15:28:03 +01:00
parent 0449d6ff20
commit 6a9ac30a8b
5 changed files with 401 additions and 16 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
node_modules/
/app/build/
/app/public/config.js
/server/config/
!/server/config/config.example.js

View File

@ -33,7 +33,7 @@ const styles = (theme) =>
},
fab :
{
margin: theme.spacing.unit
margin : theme.spacing.unit
}
});

View File

@ -0,0 +1,303 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ResizeObserver from 'resize-observer-polyfill';
import { connect } from 'react-redux';
import debounce from 'lodash/debounce';
import { withStyles } from '@material-ui/core/styles';
import classnames from 'classnames';
import { withRoomContext } from '../../RoomContext';
import Peer from '../Containers/Peer';
import HiddenPeers from '../Containers/HiddenPeers';
const styles = (theme) =>
({
root :
{
display : 'flex',
flexDirection : 'column',
alignItems : 'center',
height : '100%',
width : '100%'
},
activePeerContainer :
{
width : '100%',
height : '80vh',
display : 'flex',
justifyContent : 'center',
alignItems : 'center'
},
activePeer :
{
width : '100%',
border : '5px solid rgba(255, 255, 255, 0.15)',
boxShadow : '0px 5px 12px 2px rgba(17, 17, 17, 0.5)',
marginTop : 60
},
filmStrip :
{
display : 'flex',
background : 'rgba(0, 0, 0 , 0.5)',
width : '100%',
overflowX : 'auto',
height : '20vh',
alignItems : 'center'
},
filmStripContent :
{
margin : '0 auto',
display : 'flex',
height : '100%',
alignItems : 'center'
},
film :
{
height : '18vh',
flexShrink : 0,
paddingLeft : '1vh',
'& .active' :
{
borderColor : 'var(--active-speaker-border-color)'
},
'&.selected' :
{
borderColor : 'var(--selected-peer-border-color)'
},
'&:last-child' :
{
paddingRight : '1vh'
}
},
filmContent :
{
height : '100%',
width : '100%',
border : '1px solid rgba(255,255,255,0.15)',
maxWidth : 'calc(18vh * (4 / 3))',
cursor : 'pointer',
'& .screen' :
{
maxWidth : 'calc(18vh * (2 * 4 / 3))',
border : 0
}
},
hiddenPeers :
{
}
});
class Filmstrip extends Component
{
constructor(props)
{
super(props);
this.activePeerContainer = React.createRef();
}
state = {
lastSpeaker : null,
width : 400
};
// Find the name of the peer which is currently speaking. This is either
// the latest active speaker, or the manually selected peer, or, if no
// person has spoken yet, the first peer in the list of peers.
getActivePeerName = () =>
{
if (this.props.selectedPeerName)
{
return this.props.selectedPeerName;
}
if (this.state.lastSpeaker)
{
return this.state.lastSpeaker;
}
const peerNames = Object.keys(this.props.peers);
if (peerNames.length > 0)
{
return peerNames[0];
}
};
isSharingCamera = (peerName) => this.props.peers[peerName] &&
this.props.peers[peerName].consumers.some((consumer) =>
this.props.consumers[consumer].source === 'screen');
getRatio = () =>
{
let ratio = 4 / 3;
if (this.isSharingCamera(this.getActivePeerName()))
{
ratio *= 2;
}
return ratio;
};
updateDimensions = debounce(() =>
{
const container = this.activePeerContainer.current;
if (container)
{
const ratio = this.getRatio();
let width = container.clientWidth;
if (width / ratio > (container.clientHeight - 100))
{
width = (container.clientHeight - 100) * ratio;
}
this.setState({
width
});
}
}, 200);
componentDidMount()
{
window.addEventListener('resize', this.updateDimensions);
const observer = new ResizeObserver(this.updateDimensions);
observer.observe(this.activePeerContainer.current);
this.updateDimensions();
}
componentWillUnmount()
{
window.removeEventListener('resize', this.updateDimensions);
}
componentDidUpdate(prevProps)
{
if (prevProps !== this.props)
{
this.updateDimensions();
if (this.props.activeSpeakerName !== this.props.myName)
{
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
lastSpeaker : this.props.activeSpeakerName
});
}
}
}
render()
{
const {
roomClient,
peers,
advancedMode,
spotlights,
spotlightsLength,
classes
} = this.props;
const activePeerName = this.getActivePeerName();
return (
<div className={classes.root}>
<div className={classes.activePeerContainer} ref={this.activePeerContainer}>
{ peers[activePeerName] ?
<div
className={classes.activePeer}
style={{
width : this.state.width,
height : this.state.width / this.getRatio()
}}
>
<Peer
advancedMode={advancedMode}
name={activePeerName}
/>
</div>
:null
}
</div>
<div className={classes.filmStrip}>
<div className={classes.filmStripContent}>
{ Object.keys(peers).map((peerName) =>
{
if (spotlights.find((spotlightsElement) => spotlightsElement === peerName))
{
return (
<div
key={peerName}
onClick={() => roomClient.setSelectedPeer(peerName)}
className={classnames(classes.film, {
selected : this.props.selectedPeerName === peerName,
active : this.state.lastSpeaker === peerName
})}
>
<div className={classes.filmContent}>
<Peer
advancedMode={advancedMode}
name={peerName}
/>
</div>
</div>
);
}
else
{
return ('');
}
})}
</div>
</div>
<div className={classes.hiddenPeers}>
{ spotlightsLength<Object.keys(peers).length ?
<HiddenPeers
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
/>
:null
}
</div>
</div>
);
}
}
Filmstrip.propTypes = {
roomClient : PropTypes.any.isRequired,
activeSpeakerName : PropTypes.string,
advancedMode : PropTypes.bool,
peers : PropTypes.object.isRequired,
consumers : PropTypes.object.isRequired,
myName : PropTypes.string.isRequired,
selectedPeerName : PropTypes.string,
spotlightsLength : PropTypes.number,
spotlights : PropTypes.array.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
const spotlightsLength = state.room.spotlights ? state.room.spotlights.length : 0;
return {
activeSpeakerName : state.room.activeSpeakerName,
selectedPeerName : state.room.selectedPeerName,
peers : state.peers,
consumers : state.consumers,
myName : state.me.name,
spotlights : state.room.spotlights,
spotlightsLength
};
};
export default withRoomContext(connect(
mapStateToProps,
undefined
)(withStyles(styles)(Filmstrip)));

View File

@ -25,6 +25,7 @@ import AccountCircle from '@material-ui/icons/AccountCircle';
import Notifications from './Notifications/Notifications';
import MeetingDrawer from './MeetingDrawer/MeetingDrawer';
import Democratic from './MeetingViews/Democratic';
import Filmstrip from './MeetingViews/Filmstrip';
import Me from './Containers/Me';
import AudioPeers from './PeerAudio/AudioPeers';
import FullScreenView from './VideoContainers/FullScreenView';
@ -123,9 +124,10 @@ class Room extends Component
this.fullscreen = new FullScreen(document);
this.state = {
drawerOpen : false,
fullscreen : false
this.state =
{
drawerOpen : false,
fullscreen : false
};
}
@ -202,12 +204,20 @@ class Room extends Component
theme
} = this.props;
const View =
{
filmstrip : Filmstrip,
democratic : Democratic
}[room.mode];
if (room.audioSuspended)
{
return (
<div className={classes.root}>
<Paper className={classes.message}>
<Typography>This webpage required sound and video to play, please click to allow.</Typography>
<Typography>
This webpage required sound and video to play, please click to allow.
</Typography>
<Button
variant='contained'
onClick={() =>
@ -302,7 +312,9 @@ class Room extends Component
<IconButton
aria-label='Account'
color='inherit'
onClick={() => me.loggedIn ? roomClient.logout() : roomClient.login() }
onClick={() => {
me.loggedIn ? roomClient.logout() : roomClient.login();
}}
>
<AccountCircle />
</IconButton>
@ -327,7 +339,9 @@ class Room extends Component
</SwipeableDrawer>
</Hidden>
</nav>
<Democratic advancedMode={room.advancedMode} />
<View advancedMode={room.advancedMode} />
<Draggable handle='.me-handle' bounds='body' cancel='.display-name'>
<div
className={classnames(classes.meContainer, 'me-handle', {

View File

@ -12,24 +12,53 @@ import Button from '@material-ui/core/Button';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
import Checkbox from '@material-ui/core/Checkbox';
const styles = (theme) =>
({
root :
{
},
device :
dialogPaper :
{
width : '30vw',
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
},
setting :
{
width : '20vw',
padding : theme.spacing.unit * 2
},
formControl :
{
display : 'flex',
display : 'flex'
}
});
const modes = [ {
value : 'democratic',
label : 'Democratic view'
}, {
value : 'filmstrip',
label : 'Filmstrip view'
} ];
const Settings = ({
roomClient,
room,
@ -59,13 +88,20 @@ const Settings = ({
className={classes.root}
open={room.settingsOpen}
onClose={() => handleCloseSettings({ settingsOpen: false })}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id="form-dialog-title">Settings</DialogTitle>
<form className={classes.device} autoComplete='off'>
<DialogTitle id='form-dialog-title'>Settings</DialogTitle>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={me.selectedWebcam || ''}
onChange={(event) => event.target.value ? roomClient.changeWebcam(event.target.value) : null }
onChange={(event) =>
{
if (event.target.value)
roomClient.changeWebcam(event.target.value);
}}
displayEmpty
name='Camera'
autoWidth
@ -85,11 +121,15 @@ const Settings = ({
</FormHelperText>
</FormControl>
</form>
<form className={classes.device} autoComplete='off'>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={me.selectedAudioDevice || ''}
onChange={(event) => event.target.value ? roomClient.changeAudioDevice(event.target.value) : null }
onChange={(event) =>
{
if (event.target.value)
roomClient.changeAudioDevice(event.target.value);
}}
displayEmpty
name='Audio device'
autoWidth
@ -113,11 +153,37 @@ const Settings = ({
</FormHelperText>
</FormControl>
</form>
<FormControlLabel
className={classes.setting}
control={<Checkbox checked={room.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
label='Advanced mode'
/>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={room.mode || ''}
onChange={(event) => handleChangeMode(event.target.value)}
name='Room mode'
autoWidth
className={classes.selectEmpty}
>
{ modes.map((mode, index) =>
{
return (
<MenuItem key={index} value={mode.value}>{mode.label}</MenuItem>
);
})}
</Select>
<FormHelperText>
Select room layout
</FormHelperText>
</FormControl>
</form>
<DialogActions>
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'>
Close
</Button>
</DialogActions>
</DialogActions>
</Dialog>
);
};
@ -129,6 +195,7 @@ Settings.propTypes =
room : appPropTypes.Room.isRequired,
onToggleAdvancedMode : PropTypes.func.isRequired,
handleChangeMode : PropTypes.func.isRequired,
handleCloseSettings : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};