Film strip view almost working again.

master
Håvar Aambø Fosstveit 2019-06-05 15:56:54 +02:00
parent 1eb115f547
commit c530876d0d
7 changed files with 406 additions and 160 deletions

View File

@ -568,10 +568,10 @@ export const loggedIn = () =>
type : 'LOGGED_IN' type : 'LOGGED_IN'
}); });
export const setSelectedPeer = (selectedpeerId) => export const setSelectedPeer = (selectedPeerId) =>
({ ({
type : 'SET_SELECTED_PEER', type : 'SET_SELECTED_PEER',
payload : { selectedpeerId } payload : { selectedPeerId }
}); });
export const setSpotlights = (spotlights) => export const setSpotlights = (spotlights) =>

View File

@ -14,7 +14,6 @@ const styles = () =>
root : root :
{ {
flexDirection : 'row', flexDirection : 'row',
margin : 6,
flex : '0 0 auto', flex : '0 0 auto',
boxShadow : 'var(--peer-shadow)', boxShadow : 'var(--peer-shadow)',
border : 'var(--peer-border)', border : 'var(--peer-border)',
@ -49,6 +48,7 @@ const Me = (props) =>
me, me,
settings, settings,
activeSpeaker, activeSpeaker,
spacing,
style, style,
advancedMode, advancedMode,
micProducer, micProducer,
@ -69,6 +69,11 @@ const Me = (props) =>
!screenProducer.remotelyPaused !screenProducer.remotelyPaused
); );
const spacingStyle =
{
'margin' : spacing
};
return ( return (
<React.Fragment> <React.Fragment>
<div <div
@ -78,6 +83,7 @@ const Me = (props) =>
activeSpeaker ? 'active-speaker' : null activeSpeaker ? 'active-speaker' : null
) )
} }
style={spacingStyle}
> >
<div className={classnames(classes.viewContainer, 'webcam')} style={style}> <div className={classnames(classes.viewContainer, 'webcam')} style={style}>
<VideoView <VideoView
@ -128,6 +134,7 @@ Me.propTypes =
micProducer : appPropTypes.Producer, micProducer : appPropTypes.Producer,
webcamProducer : appPropTypes.Producer, webcamProducer : appPropTypes.Producer,
screenProducer : appPropTypes.Producer, screenProducer : appPropTypes.Producer,
spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };

View File

@ -21,7 +21,6 @@ const styles = (theme) =>
root : root :
{ {
flex : '0 0 auto', flex : '0 0 auto',
margin : 6,
boxShadow : 'var(--peer-shadow)', boxShadow : 'var(--peer-shadow)',
border : 'var(--peer-border)', border : 'var(--peer-border)',
touchAction : 'none', touchAction : 'none',
@ -128,6 +127,7 @@ const Peer = (props) =>
screenConsumer, screenConsumer,
toggleConsumerFullscreen, toggleConsumerFullscreen,
toggleConsumerWindow, toggleConsumerWindow,
spacing,
style, style,
windowConsumer, windowConsumer,
classes, classes,
@ -164,6 +164,11 @@ const Peer = (props) =>
const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const spacingStyle =
{
'margin' : spacing
};
return ( return (
<React.Fragment> <React.Fragment>
<div <div
@ -194,6 +199,7 @@ const Peer = (props) =>
setHover(false); setHover(false);
}, 2000); }, 2000);
}} }}
style={spacingStyle}
> >
<div className={classnames(classes.viewContainer)} style={style}> <div className={classnames(classes.viewContainer)} style={style}>
{ !videoVisible ? { !videoVisible ?
@ -406,6 +412,7 @@ Peer.propTypes =
screenConsumer : appPropTypes.Consumer, screenConsumer : appPropTypes.Consumer,
windowConsumer : PropTypes.string, windowConsumer : PropTypes.string,
activeSpeaker : PropTypes.bool, activeSpeaker : PropTypes.bool,
spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
toggleConsumerFullscreen : PropTypes.func.isRequired, toggleConsumerFullscreen : PropTypes.func.isRequired,
toggleConsumerWindow : PropTypes.func.isRequired, toggleConsumerWindow : PropTypes.func.isRequired,

View File

@ -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 (
<React.Fragment>
<div
className={
classnames(
classes.root,
'webcam'
)
}
style={spacingStyle}
>
<div className={classnames(classes.viewContainer)} style={style}>
{ !videoVisible ?
<div className={classes.videoInfo}>
<p>this video is paused</p>
</div>
:null
}
<VideoView
advancedMode={advancedMode}
peer={peer}
displayName={peer.displayName}
showPeerInfo
videoTrack={webcamConsumer ? webcamConsumer.track : null}
videoVisible={videoVisible}
videoProfile={videoProfile}
audioCodec={micConsumer ? micConsumer.codec : null}
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
>
<Volume id={peer.id} />
</VideoView>
</div>
</div>
{ screenConsumer ?
<div
className={classnames(classes.root, 'screen')}
>
{ !screenVisible ?
<div className={classes.videoInfo} style={style}>
<p>this video is paused</p>
</div>
:null
}
{ screenVisible ?
<div className={classnames(classes.viewContainer)} style={style}>
<VideoView
advancedMode={advancedMode}
videoContain
videoTrack={screenConsumer ? screenConsumer.track : null}
videoVisible={screenVisible}
videoProfile={screenProfile}
videoCodec={screenConsumer ? screenConsumer.codec : null}
/>
</div>
:null
}
</div>
:null
}
</React.Fragment>
);
};
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));

View File

@ -1,6 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as appPropTypes from '../../appPropTypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';

View File

@ -7,12 +7,10 @@ import {
spotlightsLengthSelector spotlightsLengthSelector
} from '../Selectors'; } from '../Selectors';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import Peer from '../Containers/Peer'; import Peer from '../Containers/Peer';
import Me from '../Containers/Me'; import Me from '../Containers/Me';
import HiddenPeers from '../Containers/HiddenPeers'; import HiddenPeers from '../Containers/HiddenPeers';
import ResizeObserver from 'resize-observer-polyfill';
const RATIO = 1.334; const RATIO = 1.334;
const PADDING_V = 50; const PADDING_V = 50;
@ -43,16 +41,17 @@ class Democratic extends React.PureComponent
{ {
super(props); super(props);
this.state = { this.state = {};
peerWidth : 400,
peerHeight : 300 this.resizeTimeout = null;
};
this.peersRef = React.createRef(); this.peersRef = React.createRef();
} }
updateDimensions = debounce(() => updateDimensions = () =>
{ {
console.log('updateDimensions');
if (!this.peersRef.current) if (!this.peersRef.current)
{ {
return; return;
@ -93,14 +92,21 @@ class Democratic extends React.PureComponent
peerHeight : 0.9 * y peerHeight : 0.9 * y
}); });
} }
}, 200); };
componentDidMount() componentDidMount()
{ {
window.addEventListener('resize', this.updateDimensions); // window.resize event listener
const observer = new ResizeObserver(this.updateDimensions); 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() componentWillUnmount()
@ -108,8 +114,9 @@ class Democratic extends React.PureComponent
window.removeEventListener('resize', this.updateDimensions); window.removeEventListener('resize', this.updateDimensions);
} }
componentDidUpdate() componentDidUpdate(prevProps)
{ {
if (prevProps !== this.props)
this.updateDimensions(); this.updateDimensions();
} }
@ -125,14 +132,15 @@ class Democratic extends React.PureComponent
const style = const style =
{ {
'width' : this.state.peerWidth, 'width' : this.state.peerWidth ? this.state.peerWidth : 0,
'height' : this.state.peerHeight 'height' : this.state.peerHeight ? this.state.peerHeight : 0
}; };
return ( return (
<div className={classes.root} ref={this.peersRef}> <div className={classes.root} ref={this.peersRef}>
<Me <Me
advancedMode={advancedMode} advancedMode={advancedMode}
spacing={6}
style={style} style={style}
/> />
{ spotlightsPeers.map((peer) => { spotlightsPeers.map((peer) =>
@ -142,6 +150,7 @@ class Democratic extends React.PureComponent
key={peer.id} key={peer.id}
advancedMode={advancedMode} advancedMode={advancedMode}
id={peer.id} id={peer.id}
spacing={6}
style={style} style={style}
/> />
); );

View File

@ -1,89 +1,53 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ResizeObserver from 'resize-observer-polyfill';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import debounce from 'lodash/debounce';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import classnames from 'classnames'; import classnames from 'classnames';
import {
spotlightsLengthSelector
} from '../Selectors';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import Me from '../Containers/Me';
import Peer from '../Containers/Peer'; import Peer from '../Containers/Peer';
import SpeakerPeer from '../Containers/SpeakerPeer';
import HiddenPeers from '../Containers/HiddenPeers'; import HiddenPeers from '../Containers/HiddenPeers';
import Grid from '@material-ui/core/Grid';
const styles = () => const styles = () =>
({ ({
root : root :
{ {
display : 'flex',
flexDirection : 'column',
alignItems : 'center',
height : '100%', height : '100%',
width : '100%'
},
activePeerContainer :
{
width : '100%', width : '100%',
height : '80vh', display : 'grid',
gridTemplateColumns : '1fr',
gridTemplateRows : '1.6fr minmax(0, 0.4fr)'
},
speaker :
{
gridArea : '1 / 1 / 2 / 2',
display : 'flex', display : 'flex',
justifyContent : 'center', justifyContent : 'center',
alignItems : 'center' alignItems : 'center',
}, paddingTop : 40
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 : filmStrip :
{
gridArea : '2 / 1 / 3 / 2'
},
filmItem :
{ {
display : 'flex', display : 'flex',
background : 'rgba(0, 0, 0 , 0.5)', marginLeft : '6px',
width : '100%', border : 'var(--peer-border)',
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' : '&.selected' :
{ {
borderColor : 'var(--selected-peer-border-color)' 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); super(props);
this.resizeTimeout = null;
this.activePeerContainer = React.createRef(); this.activePeerContainer = React.createRef();
} }
state = { state = {
lastSpeaker : null, lastSpeaker : null
width : 400
}; };
// Find the name of the peer which is currently speaking. This is either // 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. // person has spoken yet, the first peer in the list of peers.
getActivePeerId = () => getActivePeerId = () =>
{ {
if (this.props.selectedPeerId) const {
selectedPeerId,
peers
} = this.props;
const { lastSpeaker } = this.state;
if (selectedPeerId && peers[selectedPeerId])
{ {
return this.props.selectedPeerId; return this.props.selectedPeerId;
} }
if (this.state.lastSpeaker) if (lastSpeaker && peers[lastSpeaker])
{ {
return this.state.lastSpeaker; return this.state.lastSpeaker;
} }
const peerIds = Object.keys(this.props.peers); const peerIds = Object.keys(peers);
if (peerIds.length > 0) if (peerIds.length > 0)
{ {
@ -128,45 +100,47 @@ class Filmstrip extends React.PureComponent
this.props.peers[peerId].consumers.some((consumer) => this.props.peers[peerId].consumers.some((consumer) =>
this.props.consumers[consumer].source === 'screen'); this.props.consumers[consumer].source === 'screen');
getRatio = () => updateDimensions = () =>
{
let ratio = 4 / 3;
if (this.isSharingCamera(this.getActivePeerId()))
{
ratio *= 2;
}
return ratio;
};
updateDimensions = debounce(() =>
{ {
const container = this.activePeerContainer.current; const container = this.activePeerContainer.current;
if (container) 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({ this.setState({
width width,
height
}); });
} }
}, 200); };
componentDidMount() componentDidMount()
{ {
window.addEventListener('resize', this.updateDimensions); // window.resize event listener
const observer = new ResizeObserver(this.updateDimensions); 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(); this.updateDimensions();
} }
@ -175,19 +149,28 @@ class Filmstrip extends React.PureComponent
window.removeEventListener('resize', this.updateDimensions); 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) componentDidUpdate(prevProps)
{ {
if (prevProps !== this.props) if (prevProps !== this.props)
{ {
this.updateDimensions(); 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 { const {
roomClient, roomClient,
peers, peers,
myId,
advancedMode, advancedMode,
spotlights, spotlights,
spotlightsLength, spotlightsLength,
@ -204,48 +188,67 @@ class Filmstrip extends React.PureComponent
const activePeerId = this.getActivePeerId(); const activePeerId = this.getActivePeerId();
const speakerStyle =
{
width : this.state.width,
height : this.state.height
};
const peerStyle =
{
'width' : '24vmin',
'height' : '18vmin'
};
return ( return (
<div className={classes.root}> <div className={classes.root}>
<div className={classes.activePeerContainer} ref={this.activePeerContainer}> <div className={classes.speaker} ref={this.activePeerContainer}>
{ peers[activePeerId] ? { peers[activePeerId] ?
<div <SpeakerPeer
className={classes.activePeer}
style={{
width : this.state.width,
height : this.state.width / this.getRatio()
}}
>
<Peer
advancedMode={advancedMode} advancedMode={advancedMode}
name={activePeerId} id={activePeerId}
style={speakerStyle}
/> />
</div>
:null :null
} }
</div> </div>
<div className={classes.filmStrip}> <div className={classes.filmStrip}>
<div className={classes.filmStripContent}> <Grid container justify='center' spacing={0}>
<Grid item>
<div
className={classnames(classes.filmItem, {
active : myId === activePeerId
})}
>
<Me
advancedMode={advancedMode}
style={peerStyle}
/>
</div>
</Grid>
{ Object.keys(peers).map((peerId) => { Object.keys(peers).map((peerId) =>
{ {
if (spotlights.find((spotlightsElement) => spotlightsElement === peerId)) if (spotlights.find((spotlightsElement) => spotlightsElement === peerId))
{ {
return ( return (
<Grid key={peerId} item>
<div <div
key={peerId} key={peerId}
onClick={() => roomClient.setSelectedPeer(peerId)} onClick={() => roomClient.setSelectedPeer(peerId)}
className={classnames(classes.film, { className={classnames(classes.filmItem, {
selected : this.props.selectedPeerId === peerId, selected : this.props.selectedPeerId === peerId,
active : this.state.lastSpeaker === peerId active : peerId === activePeerId
})} })}
> >
<div className={classes.filmContent}>
<Peer <Peer
advancedMode={advancedMode} advancedMode={advancedMode}
name={peerId} id={peerId}
style={peerStyle}
/> />
</div> </div>
</div> </Grid>
); );
} }
else else
@ -253,7 +256,7 @@ class Filmstrip extends React.PureComponent
return (''); return ('');
} }
})} })}
</div> </Grid>
</div> </div>
<div className={classes.hiddenPeers}> <div className={classes.hiddenPeers}>
{ spotlightsLength<Object.keys(peers).length ? { spotlightsLength<Object.keys(peers).length ?
@ -263,7 +266,6 @@ class Filmstrip extends React.PureComponent
:null :null
} }
</div> </div>
</div> </div>
); );
} }
@ -271,11 +273,11 @@ class Filmstrip extends React.PureComponent
Filmstrip.propTypes = { Filmstrip.propTypes = {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
activeSpeakerName : PropTypes.string, activeSpeakerId : PropTypes.string,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
peers : PropTypes.object.isRequired, peers : PropTypes.object.isRequired,
consumers : PropTypes.object.isRequired, consumers : PropTypes.object.isRequired,
myName : PropTypes.string.isRequired, myId : PropTypes.string.isRequired,
selectedPeerId : PropTypes.string, selectedPeerId : PropTypes.string,
spotlightsLength : PropTypes.number, spotlightsLength : PropTypes.number,
spotlights : PropTypes.array.isRequired, spotlights : PropTypes.array.isRequired,
@ -284,20 +286,32 @@ Filmstrip.propTypes = {
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
const spotlightsLength = state.room.spotlights ? state.room.spotlights.length : 0;
return { return {
activeSpeakerName : state.room.activeSpeakerName, activeSpeakerId : state.room.activeSpeakerId,
selectedPeerId : state.room.selectedPeerId, selectedPeerId : state.room.selectedPeerId,
peers : state.peers, peers : state.peers,
consumers : state.consumers, consumers : state.consumers,
myName : state.me.name, myId : state.me.id,
spotlights : state.room.spotlights, spotlights : state.room.spotlights,
spotlightsLength spotlightsLength : spotlightsLengthSelector(state)
}; };
}; };
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, 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))); )(withStyles(styles)(Filmstrip)));