diff --git a/app/src/actions/stateActions.js b/app/src/actions/stateActions.js index 23639f0..52ba00d 100644 --- a/app/src/actions/stateActions.js +++ b/app/src/actions/stateActions.js @@ -568,10 +568,10 @@ export const loggedIn = () => type : 'LOGGED_IN' }); -export const setSelectedPeer = (selectedpeerId) => +export const setSelectedPeer = (selectedPeerId) => ({ type : 'SET_SELECTED_PEER', - payload : { selectedpeerId } + payload : { selectedPeerId } }); export const setSpotlights = (spotlights) => diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 60d30e8..553f2eb 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -14,7 +14,6 @@ const styles = () => root : { flexDirection : 'row', - margin : 6, flex : '0 0 auto', boxShadow : 'var(--peer-shadow)', border : 'var(--peer-border)', @@ -49,6 +48,7 @@ const Me = (props) => me, settings, activeSpeaker, + spacing, style, advancedMode, micProducer, @@ -69,6 +69,11 @@ const Me = (props) => !screenProducer.remotelyPaused ); + const spacingStyle = + { + 'margin' : spacing + }; + return (
activeSpeaker ? 'active-speaker' : null ) } + style={spacingStyle} >
root : { flex : '0 0 auto', - margin : 6, boxShadow : 'var(--peer-shadow)', border : 'var(--peer-border)', touchAction : 'none', @@ -128,6 +127,7 @@ const Peer = (props) => screenConsumer, toggleConsumerFullscreen, toggleConsumerWindow, + spacing, style, windowConsumer, classes, @@ -164,6 +164,11 @@ const Peer = (props) => const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const spacingStyle = + { + 'margin' : spacing + }; + return (
setHover(false); }, 2000); }} + style={spacingStyle} >
{ !videoVisible ? @@ -406,6 +412,7 @@ Peer.propTypes = screenConsumer : appPropTypes.Consumer, windowConsumer : PropTypes.string, activeSpeaker : PropTypes.bool, + spacing : PropTypes.number, style : PropTypes.object, toggleConsumerFullscreen : PropTypes.func.isRequired, toggleConsumerWindow : PropTypes.func.isRequired, diff --git a/app/src/components/Containers/SpeakerPeer.js b/app/src/components/Containers/SpeakerPeer.js new file mode 100644 index 0000000..efa576f --- /dev/null +++ b/app/src/components/Containers/SpeakerPeer.js @@ -0,0 +1,210 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { makePeerConsumerSelector } from '../Selectors'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import * as appPropTypes from '../appPropTypes'; +import { withStyles } from '@material-ui/core/styles'; +import VideoView from '../VideoContainers/VideoView'; +import Volume from './Volume'; + +const styles = () => + ({ + root : + { + flex : '0 0 auto', + boxShadow : 'var(--peer-shadow)', + border : 'var(--peer-border)', + touchAction : 'none', + backgroundColor : 'var(--peer-bg-color)', + backgroundImage : 'var(--peer-empty-avatar)', + backgroundPosition : 'bottom', + backgroundSize : 'auto 85%', + backgroundRepeat : 'no-repeat', + '&.webcam' : + { + order : 2 + }, + '&.screen' : + { + order : 1 + } + }, + viewContainer : + { + position : 'relative', + '&.webcam' : + { + order : 2 + }, + '&.screen' : + { + order : 1 + } + }, + videoInfo : + { + position : 'absolute', + width : '100%', + height : '100%', + backgroundColor : 'rgba(0, 0, 0, 0.3)', + display : 'flex', + justifyContent : 'center', + alignItems : 'center', + padding : '0.4vmin', + zIndex : 21, + '& p' : + { + padding : '6px 12px', + borderRadius : 6, + userSelect : 'none', + pointerEvents : 'none', + fontSize : 20, + color : 'rgba(255, 255, 255, 0.55)' + } + } + }); + +const SpeakerPeer = (props) => +{ + const { + advancedMode, + peer, + micConsumer, + webcamConsumer, + screenConsumer, + spacing, + style, + classes + } = props; + + const videoVisible = ( + Boolean(webcamConsumer) && + !webcamConsumer.locallyPaused && + !webcamConsumer.remotelyPaused + ); + + const screenVisible = ( + Boolean(screenConsumer) && + !screenConsumer.locallyPaused && + !screenConsumer.remotelyPaused + ); + + let videoProfile; + + if (webcamConsumer) + videoProfile = webcamConsumer.profile; + + let screenProfile; + + if (screenConsumer) + screenProfile = screenConsumer.profile; + + const spacingStyle = + { + 'margin' : spacing + }; + + return ( + +
+
+ { !videoVisible ? +
+

this video is paused

+
+ :null + } + + + + +
+
+ + { screenConsumer ? +
+ { !screenVisible ? +
+

this video is paused

+
+ :null + } + + { screenVisible ? +
+ +
+ :null + } +
+ :null + } +
+ ); +}; + +SpeakerPeer.propTypes = +{ + advancedMode : PropTypes.bool, + peer : appPropTypes.Peer, + micConsumer : appPropTypes.Consumer, + webcamConsumer : appPropTypes.Consumer, + screenConsumer : appPropTypes.Consumer, + spacing : PropTypes.number, + style : PropTypes.object, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state, props) => +{ + const getPeerConsumers = makePeerConsumerSelector(); + + return { + peer : state.peers[props.id], + ...getPeerConsumers(state, props) + }; +}; + +export default connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.peers === next.peers && + prev.consumers === next.consumers && + prev.room.activeSpeakerId === next.room.activeSpeakerId + ); + } + } +)(withStyles(styles, { withTheme: true })(SpeakerPeer)); diff --git a/app/src/components/MeetingDrawer/FileSharing/File.js b/app/src/components/MeetingDrawer/FileSharing/File.js index 72e6fbc..5981668 100644 --- a/app/src/components/MeetingDrawer/FileSharing/File.js +++ b/app/src/components/MeetingDrawer/FileSharing/File.js @@ -1,6 +1,5 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import * as appPropTypes from '../../appPropTypes'; import { connect } from 'react-redux'; import { withRoomContext } from '../../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; diff --git a/app/src/components/MeetingViews/Democratic.js b/app/src/components/MeetingViews/Democratic.js index 828db9b..290fb9e 100644 --- a/app/src/components/MeetingViews/Democratic.js +++ b/app/src/components/MeetingViews/Democratic.js @@ -7,12 +7,10 @@ import { spotlightsLengthSelector } from '../Selectors'; import PropTypes from 'prop-types'; -import debounce from 'lodash/debounce'; import { withStyles } from '@material-ui/core/styles'; import Peer from '../Containers/Peer'; import Me from '../Containers/Me'; import HiddenPeers from '../Containers/HiddenPeers'; -import ResizeObserver from 'resize-observer-polyfill'; const RATIO = 1.334; const PADDING_V = 50; @@ -43,16 +41,17 @@ class Democratic extends React.PureComponent { super(props); - this.state = { - peerWidth : 400, - peerHeight : 300 - }; + this.state = {}; + + this.resizeTimeout = null; this.peersRef = React.createRef(); } - updateDimensions = debounce(() => + updateDimensions = () => { + console.log('updateDimensions'); + if (!this.peersRef.current) { return; @@ -93,14 +92,21 @@ class Democratic extends React.PureComponent peerHeight : 0.9 * y }); } - }, 200); + }; componentDidMount() { - window.addEventListener('resize', this.updateDimensions); - const observer = new ResizeObserver(this.updateDimensions); + // window.resize event listener + window.addEventListener('resize', () => + { + // clear the timeout + clearTimeout(this.resizeTimeout); - observer.observe(this.peersRef.current); + // start timing for event "completion" + this.resizeTimeout = setTimeout(() => this.updateDimensions(), 250); + }); + + this.updateDimensions(); } componentWillUnmount() @@ -108,9 +114,10 @@ class Democratic extends React.PureComponent window.removeEventListener('resize', this.updateDimensions); } - componentDidUpdate() + componentDidUpdate(prevProps) { - this.updateDimensions(); + if (prevProps !== this.props) + this.updateDimensions(); } render() @@ -125,14 +132,15 @@ class Democratic extends React.PureComponent const style = { - 'width' : this.state.peerWidth, - 'height' : this.state.peerHeight + 'width' : this.state.peerWidth ? this.state.peerWidth : 0, + 'height' : this.state.peerHeight ? this.state.peerHeight : 0 }; return (
{ spotlightsPeers.map((peer) => @@ -142,6 +150,7 @@ class Democratic extends React.PureComponent key={peer.id} advancedMode={advancedMode} id={peer.id} + spacing={6} style={style} /> ); diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js index 91884a2..3a75998 100644 --- a/app/src/components/MeetingViews/Filmstrip.js +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -1,89 +1,53 @@ import React 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 { + spotlightsLengthSelector +} from '../Selectors'; import { withRoomContext } from '../../RoomContext'; +import Me from '../Containers/Me'; import Peer from '../Containers/Peer'; +import SpeakerPeer from '../Containers/SpeakerPeer'; import HiddenPeers from '../Containers/HiddenPeers'; +import Grid from '@material-ui/core/Grid'; const styles = () => ({ root : { - display : 'flex', - flexDirection : 'column', - alignItems : 'center', - height : '100%', - width : '100%' + height : '100%', + width : '100%', + display : 'grid', + gridTemplateColumns : '1fr', + gridTemplateRows : '1.6fr minmax(0, 0.4fr)' }, - activePeerContainer : + speaker : { - width : '100%', - height : '80vh', + gridArea : '1 / 1 / 2 / 2', 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 + alignItems : 'center', + paddingTop : 40 }, filmStrip : { - display : 'flex', - background : 'rgba(0, 0, 0 , 0.5)', - width : '100%', - overflowX : 'auto', - height : '20vh', - alignItems : 'center' + gridArea : '2 / 1 / 3 / 2' }, - filmStripContent : + filmItem : { - margin : '0 auto', - display : 'flex', - height : '100%', - alignItems : 'center' - }, - film : - { - height : '18vh', - flexShrink : 0, - paddingLeft : '1vh', - '& .active' : - { - borderColor : 'var(--active-speaker-border-color)' - }, + display : 'flex', + marginLeft : '6px', + border : 'var(--peer-border)', '&.selected' : { borderColor : 'var(--selected-peer-border-color)' }, - '&:last-child' : + '&.active' : { - paddingRight : '1vh' + opacity : '0.6' } - }, - 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 : - { - } }); @@ -93,12 +57,13 @@ class Filmstrip extends React.PureComponent { super(props); + this.resizeTimeout = null; + this.activePeerContainer = React.createRef(); } state = { - lastSpeaker : null, - width : 400 + lastSpeaker : null }; // Find the name of the peer which is currently speaking. This is either @@ -106,17 +71,24 @@ class Filmstrip extends React.PureComponent // person has spoken yet, the first peer in the list of peers. getActivePeerId = () => { - if (this.props.selectedPeerId) + const { + selectedPeerId, + peers + } = this.props; + + const { lastSpeaker } = this.state; + + if (selectedPeerId && peers[selectedPeerId]) { return this.props.selectedPeerId; } - if (this.state.lastSpeaker) + if (lastSpeaker && peers[lastSpeaker]) { return this.state.lastSpeaker; } - const peerIds = Object.keys(this.props.peers); + const peerIds = Object.keys(peers); if (peerIds.length > 0) { @@ -128,45 +100,47 @@ class Filmstrip extends React.PureComponent this.props.peers[peerId].consumers.some((consumer) => this.props.consumers[consumer].source === 'screen'); - getRatio = () => - { - let ratio = 4 / 3; - - if (this.isSharingCamera(this.getActivePeerId())) - { - ratio *= 2; - } - - return ratio; - }; - - updateDimensions = debounce(() => + updateDimensions = () => { const container = this.activePeerContainer.current; if (container) { - const ratio = this.getRatio(); + let width = (container.clientWidth - 100); - let width = container.clientWidth; + let height = (width / 4) * 3; - if (width / ratio > (container.clientHeight - 100)) + if (this.isSharingCamera(this.getActivePeerId())) { - width = (container.clientHeight - 100) * ratio; + width /= 2; + height = (width / 4) * 3; + } + + if (height > (container.clientHeight - 60)) + { + height = (container.clientHeight - 60); + width = (height / 3) * 4; } this.setState({ - width + width, + height }); } - }, 200); + }; componentDidMount() { - window.addEventListener('resize', this.updateDimensions); - const observer = new ResizeObserver(this.updateDimensions); + // window.resize event listener + window.addEventListener('resize', () => + { + // clear the timeout + clearTimeout(this.resizeTimeout); + + // start timing for event "completion" + this.resizeTimeout = setTimeout(() => this.updateDimensions(), 250); + }); - observer.observe(this.activePeerContainer.current); this.updateDimensions(); } @@ -175,19 +149,28 @@ class Filmstrip extends React.PureComponent window.removeEventListener('resize', this.updateDimensions); } + componentWillUpdate(nextProps) + { + if (nextProps !== this.props) + { + if ( + nextProps.activeSpeakerId != null && + nextProps.activeSpeakerId !== this.props.myId + ) + { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ + lastSpeaker : nextProps.activeSpeakerId + }); + } + } + } + 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 - }); - } } } @@ -196,6 +179,7 @@ class Filmstrip extends React.PureComponent const { roomClient, peers, + myId, advancedMode, spotlights, spotlightsLength, @@ -204,48 +188,67 @@ class Filmstrip extends React.PureComponent const activePeerId = this.getActivePeerId(); + const speakerStyle = + { + width : this.state.width, + height : this.state.height + }; + + const peerStyle = + { + 'width' : '24vmin', + 'height' : '18vmin' + }; + return (
-
+
{ peers[activePeerId] ? -
- -
+ :null }
-
+ + +
+ +
+
+ { Object.keys(peers).map((peerId) => { if (spotlights.find((spotlightsElement) => spotlightsElement === peerId)) { return ( -
roomClient.setSelectedPeer(peerId)} - className={classnames(classes.film, { - selected : this.props.selectedPeerId === peerId, - active : this.state.lastSpeaker === peerId - })} - > -
+ +
roomClient.setSelectedPeer(peerId)} + className={classnames(classes.filmItem, { + selected : this.props.selectedPeerId === peerId, + active : peerId === activePeerId + })} + >
-
+ ); } else @@ -253,7 +256,7 @@ class Filmstrip extends React.PureComponent return (''); } })} -
+
{ spotlightsLength -
); } } Filmstrip.propTypes = { - roomClient : PropTypes.any.isRequired, - activeSpeakerName : PropTypes.string, - advancedMode : PropTypes.bool, - peers : PropTypes.object.isRequired, - consumers : PropTypes.object.isRequired, - myName : PropTypes.string.isRequired, - selectedPeerId : PropTypes.string, - spotlightsLength : PropTypes.number, - spotlights : PropTypes.array.isRequired, - classes : PropTypes.object.isRequired + roomClient : PropTypes.any.isRequired, + activeSpeakerId : PropTypes.string, + advancedMode : PropTypes.bool, + peers : PropTypes.object.isRequired, + consumers : PropTypes.object.isRequired, + myId : PropTypes.string.isRequired, + selectedPeerId : 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, - selectedPeerId : state.room.selectedPeerId, - peers : state.peers, - consumers : state.consumers, - myName : state.me.name, - spotlights : state.room.spotlights, - spotlightsLength + activeSpeakerId : state.room.activeSpeakerId, + selectedPeerId : state.room.selectedPeerId, + peers : state.peers, + consumers : state.consumers, + myId : state.me.id, + spotlights : state.room.spotlights, + spotlightsLength : spotlightsLengthSelector(state) }; }; export default withRoomContext(connect( mapStateToProps, - undefined + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room.activeSpeakerId === next.room.activeSpeakerId && + prev.room.selectedPeerId === next.room.selectedPeerId && + prev.peers === next.peers && + prev.consumers === next.consumers && + prev.room.spotlights === next.room.spotlights && + prev.me.id === next.me.id + ); + } + } )(withStyles(styles)(Filmstrip))); \ No newline at end of file