From 6a9ac30a8b449e86d8fd88b1c4edcce37b8f1b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 29 Mar 2019 15:28:03 +0100 Subject: [PATCH] Added filmstrip and settings back. Still some issues with layout if a peer shares screen in filmstrip. --- .gitignore | 1 + app/src/components/Controls/Sidebar.js | 2 +- app/src/components/MeetingViews/Filmstrip.js | 303 +++++++++++++++++++ app/src/components/Room.js | 26 +- app/src/components/Settings/Settings.js | 85 +++++- 5 files changed, 401 insertions(+), 16 deletions(-) create mode 100644 app/src/components/MeetingViews/Filmstrip.js diff --git a/.gitignore b/.gitignore index 98d7543..252868b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ +/app/build/ /app/public/config.js /server/config/ !/server/config/config.example.js diff --git a/app/src/components/Controls/Sidebar.js b/app/src/components/Controls/Sidebar.js index 1b70f40..bd55ff3 100644 --- a/app/src/components/Controls/Sidebar.js +++ b/app/src/components/Controls/Sidebar.js @@ -33,7 +33,7 @@ const styles = (theme) => }, fab : { - margin: theme.spacing.unit + margin : theme.spacing.unit } }); diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js new file mode 100644 index 0000000..9eda0a9 --- /dev/null +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -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 ( +
+
+ { peers[activePeerName] ? +
+ +
+ :null + } +
+ +
+
+ { Object.keys(peers).map((peerName) => + { + if (spotlights.find((spotlightsElement) => spotlightsElement === peerName)) + { + return ( +
roomClient.setSelectedPeer(peerName)} + className={classnames(classes.film, { + selected : this.props.selectedPeerName === peerName, + active : this.state.lastSpeaker === peerName + })} + > +
+ +
+
+ ); + } + else + { + return (''); + } + })} +
+
+
+ { spotlightsLength + :null + } +
+ +
+ ); + } +} + +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))); \ No newline at end of file diff --git a/app/src/components/Room.js b/app/src/components/Room.js index a98b2fd..caaed7b 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -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 (
- This webpage required sound and video to play, please click to allow. + + This webpage required sound and video to play, please click to allow. + - + ); }; @@ -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 };