From 66a2becf63753a8e93ddd64898c365ff2e61d33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 15 Oct 2019 10:52:14 +0200 Subject: [PATCH] Inital work on lobby. --- app/src/RoomClient.js | 40 +++ app/src/actions/stateActions.js | 4 +- .../ParticipantList/ListLobbyPeer.js | 231 ++++++++++++++++++ .../ParticipantList/ParticipantList.js | 28 ++- app/src/components/Selectors.js | 6 + app/src/reducers/lobbyPeers.js | 4 +- server/lib/Room.js | 48 +++- 7 files changed, 352 insertions(+), 9 deletions(-) create mode 100644 app/src/components/MeetingDrawer/ParticipantList/ListLobbyPeer.js diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index aea8f94..0f77197 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1283,6 +1283,46 @@ export default class RoomClient break; } + case 'parkedPeer': + { + const { peerId } = notification.data; + + store.dispatch( + stateActions.addLobbyPeer(peerId)); + + store.dispatch(requestActions.notify( + { + text : 'New participant entered the lobby.' + })); + + break; + } + + case 'promotedPeer': + { + const { peerId } = notification.data; + + store.dispatch( + stateActions.removeLobbyPeer(peerId)); + + break; + } + + case 'parkedPeerDisplayName': + { + const { peerId, displayName } = notification.data; + + store.dispatch( + stateActions.setLobbyPeerDisplayName(displayName, peerId)); + + store.dispatch(requestActions.notify( + { + text : `Participant in lobby changed name to ${displayName}.` + })); + + break; + } + case 'activeSpeaker': { const { peerId } = notification.data; diff --git a/app/src/actions/stateActions.js b/app/src/actions/stateActions.js index 13df082..7908b99 100644 --- a/app/src/actions/stateActions.js +++ b/app/src/actions/stateActions.js @@ -391,11 +391,11 @@ export const setPeerVolume = (peerId, volume) => }; }; -export const addLobbyPeer = (lobbyPeer) => +export const addLobbyPeer = (peerId) => { return { type : 'ADD_LOBBY_PEER', - payload : { lobbyPeer } + payload : { peerId } }; }; diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListLobbyPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListLobbyPeer.js new file mode 100644 index 0000000..7bc6178 --- /dev/null +++ b/app/src/components/MeetingDrawer/ParticipantList/ListLobbyPeer.js @@ -0,0 +1,231 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import * as appPropTypes from '../../appPropTypes'; +import { withRoomContext } from '../../../RoomContext'; +import EmptyAvatar from '../../../images/avatar-empty.jpeg'; +import HandIcon from '../../../images/icon-hand-white.svg'; + +const styles = (theme) => + ({ + root : + { + padding : theme.spacing(1), + width : '100%', + overflow : 'hidden', + cursor : 'auto', + display : 'flex' + }, + listPeer : + { + display : 'flex' + }, + avatar : + { + borderRadius : '50%', + height : '2rem' + }, + peerInfo : + { + fontSize : '1rem', + border : 'none', + display : 'flex', + paddingLeft : theme.spacing(1), + flexGrow : 1, + alignItems : 'center' + }, + indicators : + { + left : 0, + top : 0, + display : 'flex', + flexDirection : 'row', + justifyContent : 'flex-start', + alignItems : 'center', + transition : 'opacity 0.3s' + }, + icon : + { + flex : '0 0 auto', + margin : '0.3rem', + borderRadius : 2, + backgroundPosition : 'center', + backgroundSize : '75%', + backgroundRepeat : 'no-repeat', + backgroundColor : 'rgba(0, 0, 0, 0.5)', + transitionProperty : 'opacity, background-color', + transitionDuration : '0.15s', + width : 'var(--media-control-button-size)', + height : 'var(--media-control-button-size)', + opacity : 0.85, + '&:hover' : + { + opacity : 1 + }, + '&.on' : + { + opacity : 1 + }, + '&.off' : + { + opacity : 0.2 + }, + '&.raise-hand' : + { + backgroundImage : `url(${HandIcon})` + } + }, + controls : + { + float : 'right', + display : 'flex', + flexDirection : 'row', + justifyContent : 'flex-start', + alignItems : 'center' + }, + button : + { + flex : '0 0 auto', + margin : '0.3rem', + borderRadius : 2, + backgroundColor : 'rgba(0, 0, 0, 0.5)', + cursor : 'pointer', + transitionProperty : 'opacity, background-color', + transitionDuration : '0.15s', + width : 'var(--media-control-button-size)', + height : 'var(--media-control-button-size)', + opacity : 0.85, + '&:hover' : + { + opacity : 1 + }, + '&.unsupported' : + { + pointerEvents : 'none' + }, + '&.disabled' : + { + pointerEvents : 'none', + backgroundColor : 'var(--media-control-botton-disabled)' + }, + '&.on' : + { + backgroundColor : 'var(--media-control-botton-on)' + }, + '&.off' : + { + backgroundColor : 'var(--media-control-botton-off)' + } + } + }); + +const ListLobbyPeer = (props) => +{ + const { + // roomClient, + peer, + classes + } = props; + + const picture = peer.picture || EmptyAvatar; + + return ( +
+ Peer avatar + +
+ {peer.displayName} +
+
+ { /* peer.raiseHandState ? +
+ :null + */ } +
+
+ {/* { screenConsumer ? +
+ { + e.stopPropagation(); + screenVisible ? + roomClient.modifyPeerConsumer(peer.id, 'screen', true) : + roomClient.modifyPeerConsumer(peer.id, 'screen', false); + }} + > + { screenVisible ? + + : + + } +
+ :null + } +
+ { + e.stopPropagation(); + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } +
*/} +
+
+ ); +}; + +ListLobbyPeer.propTypes = +{ + roomClient : PropTypes.any.isRequired, + advancedMode : PropTypes.bool, + peer : appPropTypes.Peer.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state, { id }) => +{ + return { + peer : state.lobbyPeers[id] + }; +}; + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.lobbyPeers === next.lobbyPeers + ); + } + } +)(withStyles(styles)(ListLobbyPeer))); \ No newline at end of file diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index a4f66ed..f8c98e4 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -2,13 +2,15 @@ import React from 'react'; import { connect } from 'react-redux'; import { passivePeersSelector, - spotlightPeersSelector + spotlightPeersSelector, + lobbyPeersKeySelector } from '../../Selectors'; import classNames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import PropTypes from 'prop-types'; import ListPeer from './ListPeer'; +import ListLobbyPeer from './ListLobbyPeer'; import ListMe from './ListMe'; import Volume from '../../Containers/Volume'; @@ -78,6 +80,7 @@ class ParticipantList extends React.PureComponent passivePeers, selectedPeerId, spotlightPeers, + lobbyPeers, classes } = this.props; @@ -88,6 +91,22 @@ class ParticipantList extends React.PureComponent
+ + { lobbyPeers ? +
    +
  • Participants in Spotlight:
  • + { lobbyPeers.map((peerId) => ( +
  • + +
  • + ))} +
+ :null + } +
  • Participants in Spotlight:
  • { spotlightPeers.map((peer) => ( @@ -131,6 +150,7 @@ ParticipantList.propTypes = passivePeers : PropTypes.array, selectedPeerId : PropTypes.string, spotlightPeers : PropTypes.array, + lobbyPeers : PropTypes.array, classes : PropTypes.object.isRequired }; @@ -139,7 +159,8 @@ const mapStateToProps = (state) => return { passivePeers : passivePeersSelector(state), selectedPeerId : state.room.selectedPeerId, - spotlightPeers : spotlightPeersSelector(state) + spotlightPeers : spotlightPeersSelector(state), + lobbyPeers : lobbyPeersKeySelector(state) }; }; @@ -153,7 +174,8 @@ const ParticipantListContainer = withRoomContext(connect( return ( prev.peers === next.peers && prev.room.spotlights === next.room.spotlights && - prev.room.selectedPeerId === next.room.selectedPeerId + prev.room.selectedPeerId === next.room.selectedPeerId && + prev.lobbyPeers === next.lobbyPeers ); } } diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index 95291f4..d3e293d 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -4,6 +4,7 @@ const producersSelect = (state) => state.producers; const consumersSelect = (state) => state.consumers; const spotlightsSelector = (state) => state.room.spotlights; const peersSelector = (state) => state.peers; +const lobbyPeersSelector = (state) => state.lobbyPeers; const getPeerConsumers = (state, props) => (state.peers[props.id] ? state.peers[props.id].consumers : null); const getAllConsumers = (state) => state.consumers; @@ -12,6 +13,11 @@ const peersKeySelector = createSelector( (peers) => Object.keys(peers) ); +export const lobbyPeersKeySelector = createSelector( + lobbyPeersSelector, + (lobbyPeers) => Object.keys(lobbyPeers) +); + export const micProducersSelector = createSelector( producersSelect, (producers) => Object.values(producers).filter((producer) => producer.source === 'mic') diff --git a/app/src/reducers/lobbyPeers.js b/app/src/reducers/lobbyPeers.js index ad538f7..391d595 100644 --- a/app/src/reducers/lobbyPeers.js +++ b/app/src/reducers/lobbyPeers.js @@ -3,7 +3,7 @@ const lobbyPeer = (state = {}, action) => switch (action.type) { case 'ADD_LOBBY_PEER': - return action.payload.lobbyPeer; + return { peerId: action.payload.peerId }; case 'SET_LOBBY_PEER_DISPLAY_NAME': return { ...state, displayName: action.payload.displayName }; @@ -19,7 +19,7 @@ const lobbyPeers = (state = {}, action) => { case 'ADD_LOBBY_PEER': { - return { ...state, [action.payload.lobbyPeer.id]: lobbyPeer(undefined, action) }; + return { ...state, [action.payload.peerId]: lobbyPeer(undefined, action) }; } case 'REMOVE_LOBBY_PEER': diff --git a/server/lib/Room.js b/server/lib/Room.js index 8c7f8d6..e51da1f 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -2,6 +2,7 @@ const EventEmitter = require('events').EventEmitter; const Logger = require('./Logger'); +const Lobby = require('./Lobby'); const config = require('../config/config'); const logger = new Logger('Room'); @@ -54,6 +55,15 @@ class Room extends EventEmitter // Locked flag. this._locked = false; + this._lobby = new Lobby(); + + this._lobby.on('promotePeer', (peer) => + { + logger.info('promotePeer() [peer:"%o"]', peer); + + this._peerJoining({ ...peer }); + }); + this._chatHistory = []; this._fileHistory = []; @@ -118,6 +128,8 @@ class Room extends EventEmitter this._closed = true; + this._lobby.close(); + // Close the peers if (this._peers) { @@ -165,11 +177,21 @@ class Room extends EventEmitter } else if (this._locked) // Don't allow connections to a locked room { - this._notification(socket, 'roomLocked'); - socket.disconnect(true); + this._lobby.parkPeer({ peerId, consume, socket }); + + this._peers.forEach((peer) => + { + this._notification(peer.socket, 'parkedPeer', { peerId }); + }); + return; } + this._peerJoining({ peerId, consume, socket }); + } + + _peerJoining({ peerId, consume, socket }) + { socket.join(this._roomId); const peer = { id : peerId, socket : socket }; @@ -808,6 +830,28 @@ class Room extends EventEmitter break; } + case 'promotePeer': + { + const { peerId } = request.data; + + this._lobby.promotePeer(peerId); + + // Return no error + cb(); + + break; + } + + case 'promoteAllPeers': + { + this._lobby.promoteAllPeers(); + + // Return no error + cb(); + + break; + } + case 'sendFile': { const { magnetUri } = request.data;