First working version of lobby.

master
Håvar Aambø Fosstveit 2019-10-16 14:09:29 +02:00
parent 66a2becf63
commit 513f0efa0b
9 changed files with 207 additions and 118 deletions

View File

@ -934,6 +934,26 @@ export default class RoomClient
stateActions.setSelectedPeer(peerId)); stateActions.setSelectedPeer(peerId));
} }
async promoteLobbyPeer(peerId)
{
logger.debug('promoteLobbyPeer() [peerId:"%s"]', peerId);
store.dispatch(
stateActions.setLobbyPeerPromotionInProgress(peerId, true));
try
{
await this.sendRequest('promotePeer', { peerId });
}
catch (error)
{
logger.error('promoteLobbyPeer() failed: %o', error);
}
store.dispatch(
stateActions.setLobbyPeerPromotionInProgress(peerId, false));
}
// type: mic/webcam/screen // type: mic/webcam/screen
// mute: true/false // mute: true/false
async modifyPeerConsumer(peerId, type, mute) async modifyPeerConsumer(peerId, type, mute)
@ -1243,6 +1263,14 @@ export default class RoomClient
switch (notification.method) switch (notification.method)
{ {
case 'enteredLobby':
{
const { displayName } = store.getState().settings;
await this.sendRequest('changeDisplayName', { displayName });
break;
}
case 'roomReady': case 'roomReady':
{ {
await this._joinRoom({ joinVideo }); await this._joinRoom({ joinVideo });
@ -1298,6 +1326,21 @@ export default class RoomClient
break; break;
} }
case 'lobbyPeerClosed':
{
const { peerId } = notification.data;
store.dispatch(
stateActions.removeLobbyPeer(peerId));
store.dispatch(requestActions.notify(
{
text : 'Participant in lobby left.'
}));
break;
}
case 'promotedPeer': case 'promotedPeer':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
@ -1308,7 +1351,7 @@ export default class RoomClient
break; break;
} }
case 'parkedPeerDisplayName': case 'lobbyPeerDisplayNameChanged':
{ {
const { peerId, displayName } = notification.data; const { peerId, displayName } = notification.data;

View File

@ -415,6 +415,14 @@ export const setLobbyPeerDisplayName = (displayName, peerId) =>
}; };
}; };
export const setLobbyPeerPromotionInProgress = (peerId, flag) =>
{
return {
type : 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS',
payload : { peerId, flag }
};
};
export const addNotification = (notification) => export const addNotification = (notification) =>
{ {
return { return {

View File

@ -1,11 +1,14 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext'; import { withRoomContext } from '../RoomContext';
import * as stateActions from '../actions/stateActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -41,6 +44,8 @@ const styles = (theme) =>
const JoinDialog = ({ const JoinDialog = ({
roomClient, roomClient,
displayName,
changeDisplayName,
classes classes
}) => }) =>
{ {
@ -76,6 +81,19 @@ const JoinDialog = ({
> >
Audio and Video Audio and Video
</Button> </Button>
<TextField
id='displayname'
label='Name'
className={classes.textField}
value={displayName}
onChange={(event) =>
{
const { value } = event.target;
changeDisplayName(value);
}}
margin='normal'
/>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );
@ -83,8 +101,39 @@ const JoinDialog = ({
JoinDialog.propTypes = JoinDialog.propTypes =
{ {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
classes : PropTypes.object.isRequired displayName : PropTypes.string.isRequired,
changeDisplayName : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
}; };
export default withRoomContext(withStyles(styles)(JoinDialog)); const mapStateToProps = (state) =>
{
return {
displayName : state.settings.displayName
};
};
const mapDispatchToProps = (dispatch) =>
{
return {
changeDisplayName : (displayName) =>
{
dispatch(stateActions.setDisplayName(displayName));
}
};
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.settings.displayName === next.settings.displayName
);
}
}
)(withStyles(styles)(JoinDialog)));

View File

@ -3,10 +3,9 @@ import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import HandIcon from '../../../images/icon-hand-white.svg'; import PromoteIcon from '@material-ui/icons/OpenInBrowser';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -18,10 +17,6 @@ const styles = (theme) =>
cursor : 'auto', cursor : 'auto',
display : 'flex' display : 'flex'
}, },
listPeer :
{
display : 'flex'
},
avatar : avatar :
{ {
borderRadius : '50%', borderRadius : '50%',
@ -36,47 +31,6 @@ const styles = (theme) =>
flexGrow : 1, flexGrow : 1,
alignItems : 'center' 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 : controls :
{ {
float : 'right', float : 'right',
@ -101,22 +55,14 @@ const styles = (theme) =>
{ {
opacity : 1 opacity : 1
}, },
'&.unsupported' :
{
pointerEvents : 'none'
},
'&.disabled' : '&.disabled' :
{ {
pointerEvents : 'none', pointerEvents : 'none',
backgroundColor : 'var(--media-control-botton-disabled)' backgroundColor : 'var(--media-control-botton-disabled)'
}, },
'&.on' : '&.promote' :
{ {
backgroundColor : 'var(--media-control-botton-on)' backgroundColor : 'var(--media-control-botton-on)'
},
'&.off' :
{
backgroundColor : 'var(--media-control-botton-off)'
} }
} }
}); });
@ -124,7 +70,7 @@ const styles = (theme) =>
const ListLobbyPeer = (props) => const ListLobbyPeer = (props) =>
{ {
const { const {
// roomClient, roomClient,
peer, peer,
classes classes
} = props; } = props;
@ -138,64 +84,19 @@ const ListLobbyPeer = (props) =>
<div className={classes.peerInfo}> <div className={classes.peerInfo}>
{peer.displayName} {peer.displayName}
</div> </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}> <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 <div
className={classnames(classes.button, 'mic', { className={classnames(classes.button, 'promote', {
on : micEnabled, disabled : peer.promotionInProgress
off : !micEnabled,
disabled : peer.peerAudioInProgress
})} })}
onClick={(e) => onClick={(e) =>
{ {
e.stopPropagation(); e.stopPropagation();
micEnabled ? roomClient.promoteLobbyPeer(peer.id);
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}} }}
> >
{ micEnabled ? <PromoteIcon />
<MicIcon /> </div>
:
<MicOffIcon />
}
</div> */}
</div> </div>
</div> </div>
); );
@ -205,7 +106,7 @@ ListLobbyPeer.propTypes =
{ {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
peer : appPropTypes.Peer.isRequired, peer : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };

View File

@ -92,9 +92,9 @@ class ParticipantList extends React.PureComponent
</ul> </ul>
<br /> <br />
{ lobbyPeers ? { lobbyPeers.length > 0 ?
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}>Participants in Spotlight:</li> <li className={classes.listheader}>Participants in Lobby:</li>
{ lobbyPeers.map((peerId) => ( { lobbyPeers.map((peerId) => (
<li <li
key={peerId} key={peerId}
@ -107,6 +107,7 @@ class ParticipantList extends React.PureComponent
:null :null
} }
<br />
<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) => (

View File

@ -3,11 +3,14 @@ const lobbyPeer = (state = {}, action) =>
switch (action.type) switch (action.type)
{ {
case 'ADD_LOBBY_PEER': case 'ADD_LOBBY_PEER':
return { peerId: action.payload.peerId }; return { id: 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 };
case 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS':
return { ...state, promotionInProgress: action.payload.flag };
default: default:
return state; return state;
} }
@ -33,12 +36,14 @@ const lobbyPeers = (state = {}, action) =>
} }
case 'SET_LOBBY_PEER_DISPLAY_NAME': case 'SET_LOBBY_PEER_DISPLAY_NAME':
case 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS':
{ {
const oldLobbyPeer = state[action.payload.peerId]; const oldLobbyPeer = state[action.payload.peerId];
if (!oldLobbyPeer) if (!oldLobbyPeer)
{ {
throw new Error('no Peer found'); // Tried to update non-existant lobbyPeer. Has probably been promoted, or left.
return state;
} }
return { ...state, [oldLobbyPeer.id]: lobbyPeer(oldLobbyPeer, action) }; return { ...state, [oldLobbyPeer.id]: lobbyPeer(oldLobbyPeer, action) };

View File

@ -3,6 +3,7 @@ import room from './room';
import me from './me'; import me from './me';
import producers from './producers'; import producers from './producers';
import peers from './peers'; import peers from './peers';
import lobbyPeers from './lobbyPeers';
import consumers from './consumers'; import consumers from './consumers';
import peerVolumes from './peerVolumes'; import peerVolumes from './peerVolumes';
import notifications from './notifications'; import notifications from './notifications';
@ -16,6 +17,7 @@ export default combineReducers({
me, me,
producers, producers,
peers, peers,
lobbyPeers,
consumers, consumers,
peerVolumes, peerVolumes,
notifications, notifications,

View File

@ -13,6 +13,9 @@ class Lobby extends EventEmitter
super(); super();
// Closed flag.
this._closed = false;
this._peers = new Map(); this._peers = new Map();
} }
@ -20,6 +23,8 @@ class Lobby extends EventEmitter
{ {
logger.info('close()'); logger.info('close()');
this._closed = true;
// Close the peers // Close the peers
if (this._peers) if (this._peers)
{ {
@ -60,9 +65,9 @@ class Lobby extends EventEmitter
const peer = this._peers.get(peerId); const peer = this._peers.get(peerId);
this._peers.delete(peerId);
this.emit('promotePeer', peer); this.emit('promotePeer', peer);
this._peers.delete(peerId);
} }
parkPeer({ peerId, consume, socket }) parkPeer({ peerId, consume, socket })
@ -74,6 +79,52 @@ class Lobby extends EventEmitter
socket.emit('notification', { method: 'enteredLobby', data: {} }); socket.emit('notification', { method: 'enteredLobby', data: {} });
this._peers.set(peerId, peer); this._peers.set(peerId, peer);
socket.on('request', (request, cb) =>
{
logger.debug(
'Peer "request" event [method:%s, peerId:%s]',
request.method, peer.peerId);
this._handleSocketRequest(peer, request, cb)
.catch((error) =>
{
logger.error('request failed:%o', error);
cb(error);
});
});
socket.on('disconnect', () =>
{
if (this._closed)
return;
logger.debug('Peer "close" event [peerId:%s]', peer.peerId);
this.emit('peerClosed', peer);
this._peers.delete(peer.peerId);
});
}
async _handleSocketRequest(peer, request, cb)
{
switch (request.method)
{
case 'changeDisplayName':
{
const { displayName } = request.data;
peer.displayName = displayName;
this.emit('lobbyPeerDisplayNameChanged', peer);
cb();
break;
}
}
} }
} }

View File

@ -61,7 +61,36 @@ class Room extends EventEmitter
{ {
logger.info('promotePeer() [peer:"%o"]', peer); logger.info('promotePeer() [peer:"%o"]', peer);
const { peerId } = peer;
this._peerJoining({ ...peer }); this._peerJoining({ ...peer });
this._peers.forEach((peer) =>
{
this._notification(peer.socket, 'promotedPeer', { peerId });
});
});
this._lobby.on('lobbyPeerDisplayNameChanged', (peer) =>
{
const { peerId, displayName } = peer;
this._peers.forEach((peer) =>
{
this._notification(peer.socket, 'lobbyPeerDisplayNameChanged', { peerId, displayName });
});
});
this._lobby.on('peerClosed', (peer) =>
{
logger.info('peerClosed() [peer:"%o"]', peer);
const { peerId } = peer;
this._peers.forEach((peer) =>
{
this._notification(peer.socket, 'lobbyPeerClosed', { peerId });
});
}); });
this._chatHistory = []; this._chatHistory = [];