Inital work on lobby.

master
Håvar Aambø Fosstveit 2019-10-15 10:52:14 +02:00
parent 937f142c6e
commit 66a2becf63
7 changed files with 352 additions and 9 deletions

View File

@ -1283,6 +1283,46 @@ export default class RoomClient
break; 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': case 'activeSpeaker':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;

View File

@ -391,11 +391,11 @@ export const setPeerVolume = (peerId, volume) =>
}; };
}; };
export const addLobbyPeer = (lobbyPeer) => export const addLobbyPeer = (peerId) =>
{ {
return { return {
type : 'ADD_LOBBY_PEER', type : 'ADD_LOBBY_PEER',
payload : { lobbyPeer } payload : { peerId }
}; };
}; };

View File

@ -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 (
<div className={classes.root}>
<img alt='Peer avatar' className={classes.avatar} src={picture} />
<div className={classes.peerInfo}>
{peer.displayName}
</div>
<div className={classes.indicators}>
{ /* peer.raiseHandState ?
<div className={
classnames(
classes.icon, 'raise-hand', {
on : peer.raiseHandState,
off : !peer.raiseHandState
}
)
}
/>
:null
*/ }
</div>
<div className={classes.controls}>
{/* { screenConsumer ?
<div
className={classnames(classes.button, 'screen', {
on : screenVisible,
off : !screenVisible,
disabled : peer.peerScreenInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
screenVisible ?
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
}}
>
{ screenVisible ?
<ScreenIcon />
:
<ScreenOffIcon />
}
</div>
:null
}
<div
className={classnames(classes.button, 'mic', {
on : micEnabled,
off : !micEnabled,
disabled : peer.peerAudioInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}}
>
{ micEnabled ?
<MicIcon />
:
<MicOffIcon />
}
</div> */}
</div>
</div>
);
};
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)));

View File

@ -2,13 +2,15 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
passivePeersSelector, passivePeersSelector,
spotlightPeersSelector spotlightPeersSelector,
lobbyPeersKeySelector
} from '../../Selectors'; } from '../../Selectors';
import classNames from 'classnames'; import classNames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ListPeer from './ListPeer'; import ListPeer from './ListPeer';
import ListLobbyPeer from './ListLobbyPeer';
import ListMe from './ListMe'; import ListMe from './ListMe';
import Volume from '../../Containers/Volume'; import Volume from '../../Containers/Volume';
@ -78,6 +80,7 @@ class ParticipantList extends React.PureComponent
passivePeers, passivePeers,
selectedPeerId, selectedPeerId,
spotlightPeers, spotlightPeers,
lobbyPeers,
classes classes
} = this.props; } = this.props;
@ -88,6 +91,22 @@ class ParticipantList extends React.PureComponent
<ListMe /> <ListMe />
</ul> </ul>
<br /> <br />
{ lobbyPeers ?
<ul className={classes.list}>
<li className={classes.listheader}>Participants in Spotlight:</li>
{ lobbyPeers.map((peerId) => (
<li
key={peerId}
className={classes.listItem}
>
<ListLobbyPeer id={peerId} advancedMode={advancedMode} />
</li>
))}
</ul>
:null
}
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}>Participants in Spotlight:</li> <li className={classes.listheader}>Participants in Spotlight:</li>
{ spotlightPeers.map((peer) => ( { spotlightPeers.map((peer) => (
@ -131,6 +150,7 @@ ParticipantList.propTypes =
passivePeers : PropTypes.array, passivePeers : PropTypes.array,
selectedPeerId : PropTypes.string, selectedPeerId : PropTypes.string,
spotlightPeers : PropTypes.array, spotlightPeers : PropTypes.array,
lobbyPeers : PropTypes.array,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
@ -139,7 +159,8 @@ const mapStateToProps = (state) =>
return { return {
passivePeers : passivePeersSelector(state), passivePeers : passivePeersSelector(state),
selectedPeerId : state.room.selectedPeerId, selectedPeerId : state.room.selectedPeerId,
spotlightPeers : spotlightPeersSelector(state) spotlightPeers : spotlightPeersSelector(state),
lobbyPeers : lobbyPeersKeySelector(state)
}; };
}; };
@ -153,7 +174,8 @@ const ParticipantListContainer = withRoomContext(connect(
return ( return (
prev.peers === next.peers && prev.peers === next.peers &&
prev.room.spotlights === next.room.spotlights && prev.room.spotlights === next.room.spotlights &&
prev.room.selectedPeerId === next.room.selectedPeerId prev.room.selectedPeerId === next.room.selectedPeerId &&
prev.lobbyPeers === next.lobbyPeers
); );
} }
} }

View File

@ -4,6 +4,7 @@ const producersSelect = (state) => state.producers;
const consumersSelect = (state) => state.consumers; const consumersSelect = (state) => state.consumers;
const spotlightsSelector = (state) => state.room.spotlights; const spotlightsSelector = (state) => state.room.spotlights;
const peersSelector = (state) => state.peers; const peersSelector = (state) => state.peers;
const lobbyPeersSelector = (state) => state.lobbyPeers;
const getPeerConsumers = (state, props) => const getPeerConsumers = (state, props) =>
(state.peers[props.id] ? state.peers[props.id].consumers : null); (state.peers[props.id] ? state.peers[props.id].consumers : null);
const getAllConsumers = (state) => state.consumers; const getAllConsumers = (state) => state.consumers;
@ -12,6 +13,11 @@ const peersKeySelector = createSelector(
(peers) => Object.keys(peers) (peers) => Object.keys(peers)
); );
export const lobbyPeersKeySelector = createSelector(
lobbyPeersSelector,
(lobbyPeers) => Object.keys(lobbyPeers)
);
export const micProducersSelector = createSelector( export const micProducersSelector = createSelector(
producersSelect, producersSelect,
(producers) => Object.values(producers).filter((producer) => producer.source === 'mic') (producers) => Object.values(producers).filter((producer) => producer.source === 'mic')

View File

@ -3,7 +3,7 @@ const lobbyPeer = (state = {}, action) =>
switch (action.type) switch (action.type)
{ {
case 'ADD_LOBBY_PEER': case 'ADD_LOBBY_PEER':
return action.payload.lobbyPeer; return { peerId: action.payload.peerId };
case 'SET_LOBBY_PEER_DISPLAY_NAME': case 'SET_LOBBY_PEER_DISPLAY_NAME':
return { ...state, displayName: action.payload.displayName }; return { ...state, displayName: action.payload.displayName };
@ -19,7 +19,7 @@ const lobbyPeers = (state = {}, action) =>
{ {
case 'ADD_LOBBY_PEER': 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': case 'REMOVE_LOBBY_PEER':

View File

@ -2,6 +2,7 @@
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
const Logger = require('./Logger'); const Logger = require('./Logger');
const Lobby = require('./Lobby');
const config = require('../config/config'); const config = require('../config/config');
const logger = new Logger('Room'); const logger = new Logger('Room');
@ -54,6 +55,15 @@ class Room extends EventEmitter
// Locked flag. // Locked flag.
this._locked = false; this._locked = false;
this._lobby = new Lobby();
this._lobby.on('promotePeer', (peer) =>
{
logger.info('promotePeer() [peer:"%o"]', peer);
this._peerJoining({ ...peer });
});
this._chatHistory = []; this._chatHistory = [];
this._fileHistory = []; this._fileHistory = [];
@ -118,6 +128,8 @@ class Room extends EventEmitter
this._closed = true; this._closed = true;
this._lobby.close();
// Close the peers // Close the peers
if (this._peers) if (this._peers)
{ {
@ -165,11 +177,21 @@ class Room extends EventEmitter
} }
else if (this._locked) // Don't allow connections to a locked room else if (this._locked) // Don't allow connections to a locked room
{ {
this._notification(socket, 'roomLocked'); this._lobby.parkPeer({ peerId, consume, socket });
socket.disconnect(true);
this._peers.forEach((peer) =>
{
this._notification(peer.socket, 'parkedPeer', { peerId });
});
return; return;
} }
this._peerJoining({ peerId, consume, socket });
}
_peerJoining({ peerId, consume, socket })
{
socket.join(this._roomId); socket.join(this._roomId);
const peer = { id : peerId, socket : socket }; const peer = { id : peerId, socket : socket };
@ -808,6 +830,28 @@ class Room extends EventEmitter
break; 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': case 'sendFile':
{ {
const { magnetUri } = request.data; const { magnetUri } = request.data;