Massive change. Changed to react-scripts (webpack) for building. Changed to material-ui where applicable. Changed to CSS-in-JS.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import FullView from './FullView';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
|
||||
const styles = () =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
position : 'absolute',
|
||||
top : 0,
|
||||
left : 0,
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
zIndex : 20000
|
||||
},
|
||||
controls :
|
||||
{
|
||||
position : 'absolute',
|
||||
zIndex : 20020,
|
||||
right : 0,
|
||||
top : 0,
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center',
|
||||
padding : '0.4vmin'
|
||||
},
|
||||
button :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
margin : '0.2vmin',
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(255, 255, 255, 0.7)',
|
||||
cursor : 'pointer',
|
||||
transitionProperty : 'opacity, background-color',
|
||||
transitionDuration : '0.15s',
|
||||
width : '5vmin',
|
||||
height : '5vmin'
|
||||
},
|
||||
icon :
|
||||
{
|
||||
fontSize : '5vmin'
|
||||
},
|
||||
incompatibleVideo :
|
||||
{
|
||||
position : 'absolute',
|
||||
zIndex : 20010,
|
||||
top : 0,
|
||||
bottom : 0,
|
||||
left : 0,
|
||||
right : 0,
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center',
|
||||
'& p' :
|
||||
{
|
||||
padding : '6px 12px',
|
||||
borderRadius : 6,
|
||||
userSelect : 'none',
|
||||
pointerEvents : 'none',
|
||||
fontSize : 15,
|
||||
color : 'rgba(255, 255, 255, 0.55)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const FullScreenView = (props) =>
|
||||
{
|
||||
const {
|
||||
advancedMode,
|
||||
consumer,
|
||||
toggleConsumerFullscreen,
|
||||
toolbarsVisible,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
if (!consumer)
|
||||
return null;
|
||||
|
||||
const consumerVisible = (
|
||||
Boolean(consumer) &&
|
||||
!consumer.locallyPaused &&
|
||||
!consumer.remotelyPaused
|
||||
);
|
||||
|
||||
let consumerProfile;
|
||||
|
||||
if (consumer)
|
||||
consumerProfile = consumer.profile;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{ consumerVisible && !consumer.supported ?
|
||||
<div className={classes.incompatibleVideo}>
|
||||
<p>incompatible video</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
<div className={classes.controls}>
|
||||
<div
|
||||
className={classnames(classes.button, 'room-controls', {
|
||||
visible : toolbarsVisible
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
toggleConsumerFullscreen(consumer);
|
||||
}}
|
||||
>
|
||||
<FullScreenExitIcon className={classes.icon} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FullView
|
||||
advancedMode={advancedMode}
|
||||
videoTrack={consumer ? consumer.track : null}
|
||||
videoVisible={consumerVisible}
|
||||
videoProfile={consumerProfile}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FullScreenView.propTypes =
|
||||
{
|
||||
advancedMode : PropTypes.bool,
|
||||
consumer : appPropTypes.Consumer,
|
||||
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
||||
toolbarsVisible : PropTypes.bool,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
consumer : state.consumers[state.room.fullScreenConsumer],
|
||||
toolbarsVisible : state.room.toolbarsVisible
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
toggleConsumerFullscreen : (consumer) =>
|
||||
{
|
||||
if (consumer)
|
||||
dispatch(stateActions.toggleConsumerFullscreen(consumer.id));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withStyles(styles)(FullScreenView));
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = () =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
position : 'relative',
|
||||
flex : '100 100 auto',
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
overflow : 'hidden'
|
||||
},
|
||||
video :
|
||||
{
|
||||
flex : '100 100 auto',
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
objectFit : 'contain',
|
||||
userSelect : 'none',
|
||||
transitionProperty : 'opacity',
|
||||
transitionDuration : '.15s',
|
||||
backgroundColor : 'rgba(0, 0, 0, 1)',
|
||||
'&.hidden' :
|
||||
{
|
||||
opacity : 0,
|
||||
transitionDuration : '0s'
|
||||
},
|
||||
'&.loading' :
|
||||
{
|
||||
filter : 'blur(5px)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class FullView extends React.Component
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
|
||||
// Latest received video track.
|
||||
// @type {MediaStreamTrack}
|
||||
this._videoTrack = null;
|
||||
|
||||
this.video = React.createRef();
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
videoVisible,
|
||||
videoProfile,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<video
|
||||
ref={this.video}
|
||||
className={classnames(classes.video, {
|
||||
hidden : !videoVisible,
|
||||
loading : videoProfile === 'none'
|
||||
})}
|
||||
autoPlay
|
||||
playsInline
|
||||
muted={Boolean(true)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
const { videoTrack } = this.props;
|
||||
|
||||
this._setTracks(videoTrack);
|
||||
}
|
||||
|
||||
componentDidUpdate()
|
||||
{
|
||||
const { videoTrack } = this.props;
|
||||
|
||||
this._setTracks(videoTrack);
|
||||
}
|
||||
|
||||
_setTracks(videoTrack)
|
||||
{
|
||||
if (this._videoTrack === videoTrack)
|
||||
return;
|
||||
|
||||
this._videoTrack = videoTrack;
|
||||
|
||||
const video = this.video.current;
|
||||
|
||||
if (videoTrack)
|
||||
{
|
||||
const stream = new MediaStream();
|
||||
|
||||
stream.addTrack(videoTrack);
|
||||
video.srcObject = stream;
|
||||
}
|
||||
else
|
||||
{
|
||||
video.srcObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FullView.propTypes =
|
||||
{
|
||||
videoTrack : PropTypes.any,
|
||||
videoVisible : PropTypes.bool,
|
||||
videoProfile : PropTypes.string,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(FullView);
|
||||
@@ -0,0 +1,431 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import EditableInput from '../Controls/EditableInput';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root:
|
||||
{
|
||||
position : 'relative',
|
||||
flex : '100 100 auto',
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
overflow : 'hidden',
|
||||
backgroundColor : 'var(--peer-bg-color)',
|
||||
backgroundImage : 'var(--peer-empty-avatar)',
|
||||
backgroundPosition : 'bottom',
|
||||
backgroundSize : 'auto 85%',
|
||||
backgroundRepeat : 'no-repeat'
|
||||
},
|
||||
video :
|
||||
{
|
||||
flex : '100 100 auto',
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
objectFit : 'cover',
|
||||
userSelect : 'none',
|
||||
transitionProperty : 'opacity',
|
||||
transitionDuration : '.15s',
|
||||
backgroundColor : 'var(--peer-video-bg-color)',
|
||||
'&.is-me' :
|
||||
{
|
||||
transform : 'scaleX(-1)'
|
||||
},
|
||||
'&.hidden' :
|
||||
{
|
||||
opacity : 0,
|
||||
transitionDuration : '0s'
|
||||
},
|
||||
'&.loading' :
|
||||
{
|
||||
filter : 'blur(5px)'
|
||||
}
|
||||
},
|
||||
info :
|
||||
{
|
||||
position : 'absolute',
|
||||
zIndex : 10,
|
||||
top : '0.6vmin',
|
||||
left : '0.6vmin',
|
||||
bottom : 0,
|
||||
right : 0,
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
justifyContent : 'space-between'
|
||||
},
|
||||
media :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
display : 'flex',
|
||||
flexDirection : 'row'
|
||||
},
|
||||
box :
|
||||
{
|
||||
padding : '0.4vmin',
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
||||
'& p' :
|
||||
{
|
||||
userSelect : 'none',
|
||||
pointerEvents : 'none',
|
||||
margin : 0,
|
||||
color : 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize : 10,
|
||||
|
||||
'&:last-child' :
|
||||
{
|
||||
marginBottom : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
peer :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
justifyContent : 'flex-end',
|
||||
position : 'absolute',
|
||||
bottom : '0.6vmin',
|
||||
left : 0,
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
||||
padding : '0.5vmin',
|
||||
alignItems : 'flex-start'
|
||||
},
|
||||
displayNameEdit :
|
||||
{
|
||||
fontSize : 14,
|
||||
fontWeight : 400,
|
||||
color : 'rgba(255, 255, 255, 0.85)',
|
||||
border : 'none',
|
||||
borderBottom : '1px solid #aeff00',
|
||||
backgroundColor : 'transparent'
|
||||
},
|
||||
displayNameStatic :
|
||||
{
|
||||
userSelect : 'none',
|
||||
cursor : 'text',
|
||||
fontSize : 14,
|
||||
fontWeight : 400,
|
||||
color : 'rgba(255, 255, 255, 0.85)',
|
||||
'&:hover' :
|
||||
{
|
||||
backgroundColor: 'rgb(174, 255, 0, 0.25)'
|
||||
}
|
||||
},
|
||||
deviceInfo :
|
||||
{
|
||||
marginTop : '0.4vmin',
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'flex-end',
|
||||
'& span' :
|
||||
{
|
||||
userSelect : 'none',
|
||||
pointerEvents : 'none',
|
||||
fontSize : 11,
|
||||
color : 'rgba(255, 255, 255, 0.55)'
|
||||
}
|
||||
},
|
||||
volume :
|
||||
{
|
||||
position : 'absolute',
|
||||
top : 0,
|
||||
bottom : 0,
|
||||
right : 2,
|
||||
width : 10,
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center',
|
||||
},
|
||||
volumeBar :
|
||||
{
|
||||
width : 6,
|
||||
borderRadius : 6,
|
||||
background : 'rgba(yellow, 0.65)',
|
||||
transitionProperty : 'height background-color',
|
||||
transitionDuration : '0.25s',
|
||||
'&.level0' :
|
||||
{
|
||||
height : 0,
|
||||
backgroundColor : 'rgba(255, 255, 0, 0.65)'
|
||||
},
|
||||
'&.level1' :
|
||||
{
|
||||
height : '10%',
|
||||
backgroundColor : 'rgba(255, 255, 0, 0.65)'
|
||||
},
|
||||
'&.level2' :
|
||||
{
|
||||
height : '20%',
|
||||
backgroundColor : 'rgba(255, 255, 0, 0.65)'
|
||||
},
|
||||
'&.level3' :
|
||||
{
|
||||
height : '30%',
|
||||
backgroundColor : 'rgba(255, 255, 0, 0.65)'
|
||||
},
|
||||
'&.level4' :
|
||||
{
|
||||
height : '40%',
|
||||
backgroundColor : 'rgba(255, 165, 0, 0.65)'
|
||||
},
|
||||
'&.level5' :
|
||||
{
|
||||
height : '50%',
|
||||
backgroundColor : 'rgba(255, 165, 0, 0.65)'
|
||||
},
|
||||
'&.level6' :
|
||||
{
|
||||
height : '60%',
|
||||
backgroundColor : 'rgba(255, 0, 0, 0.65)'
|
||||
},
|
||||
'&.level7' :
|
||||
{
|
||||
height : '70%',
|
||||
backgroundColor : 'rgba(255, 0, 0, 0.65)'
|
||||
},
|
||||
'&.level8' :
|
||||
{
|
||||
height : '80%',
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.65)'
|
||||
},
|
||||
'&.level9' :
|
||||
{
|
||||
height : '90%',
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.65)'
|
||||
},
|
||||
'&.level10' :
|
||||
{
|
||||
height : '100%',
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.65)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class PeerView extends React.Component
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
|
||||
this.state =
|
||||
{
|
||||
volume : 0, // Integer from 0 to 10.,
|
||||
videoWidth : null,
|
||||
videoHeight : null
|
||||
};
|
||||
|
||||
// Latest received video track.
|
||||
// @type {MediaStreamTrack}
|
||||
this._audioTrack = null;
|
||||
|
||||
// Latest received video track.
|
||||
// @type {MediaStreamTrack}
|
||||
this._videoTrack = null;
|
||||
|
||||
// Periodic timer for showing video resolution.
|
||||
this._videoResolutionTimer = null;
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
isMe,
|
||||
peer,
|
||||
volume,
|
||||
advancedMode,
|
||||
videoVisible,
|
||||
videoProfile,
|
||||
audioCodec,
|
||||
videoCodec,
|
||||
onChangeDisplayName,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
videoWidth,
|
||||
videoHeight
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.info}>
|
||||
{ advancedMode ?
|
||||
<div className={classes.media}>
|
||||
<div className={classes.box}>
|
||||
{ audioCodec ?
|
||||
<p>{audioCodec}</p>
|
||||
:null
|
||||
}
|
||||
|
||||
{ videoCodec ?
|
||||
<p>{videoCodec} {videoProfile}</p>
|
||||
:null
|
||||
}
|
||||
|
||||
{ (videoVisible && videoWidth !== null) ?
|
||||
<p>{videoWidth}x{videoHeight}</p>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
<div className={classes.peer}>
|
||||
{ isMe ?
|
||||
<EditableInput
|
||||
value={peer.displayName}
|
||||
propName='displayName'
|
||||
className={classnames(classes.displayNameEdit, 'display-name')}
|
||||
classLoading='loading'
|
||||
classInvalid='invalid'
|
||||
shouldBlockWhileLoading
|
||||
editProps={{
|
||||
maxLength : 30,
|
||||
autoCorrect : false,
|
||||
spellCheck : false
|
||||
}}
|
||||
onChange={({ displayName }) => onChangeDisplayName(displayName)}
|
||||
/>
|
||||
:
|
||||
<span className={classes.displayNameStatic}>
|
||||
{peer.displayName}
|
||||
</span>
|
||||
}
|
||||
|
||||
{ advancedMode ?
|
||||
<div className={classes.deviceInfo}>
|
||||
<span>
|
||||
{peer.device.name} {Math.floor(peer.device.version) || null}
|
||||
</span>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<video
|
||||
ref='video'
|
||||
className={classnames(classes.video, {
|
||||
hidden : !videoVisible,
|
||||
'is-me' : isMe,
|
||||
loading : videoProfile === 'none'
|
||||
})}
|
||||
autoPlay
|
||||
playsInline
|
||||
muted={isMe}
|
||||
/>
|
||||
|
||||
<div className={classes.volume}>
|
||||
<div className={classnames(classes.volumeBar, `level${volume}`)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
const { audioTrack, videoTrack } = this.props;
|
||||
|
||||
this._setTracks(audioTrack, videoTrack);
|
||||
}
|
||||
|
||||
componentWillUnmount()
|
||||
{
|
||||
clearInterval(this._videoResolutionTimer);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
{
|
||||
const { audioTrack, videoTrack } = nextProps;
|
||||
|
||||
this._setTracks(audioTrack, videoTrack);
|
||||
|
||||
}
|
||||
|
||||
_setTracks(audioTrack, videoTrack)
|
||||
{
|
||||
if (this._audioTrack === audioTrack && this._videoTrack === videoTrack)
|
||||
return;
|
||||
|
||||
this._audioTrack = audioTrack;
|
||||
this._videoTrack = videoTrack;
|
||||
|
||||
clearInterval(this._videoResolutionTimer);
|
||||
this._hideVideoResolution();
|
||||
|
||||
const { video } = this.refs;
|
||||
|
||||
if (audioTrack || videoTrack)
|
||||
{
|
||||
const stream = new MediaStream();
|
||||
|
||||
if (audioTrack)
|
||||
stream.addTrack(audioTrack);
|
||||
|
||||
if (videoTrack)
|
||||
stream.addTrack(videoTrack);
|
||||
|
||||
video.srcObject = stream;
|
||||
|
||||
if (videoTrack)
|
||||
this._showVideoResolution();
|
||||
}
|
||||
else
|
||||
{
|
||||
video.srcObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
_showVideoResolution()
|
||||
{
|
||||
this._videoResolutionTimer = setInterval(() =>
|
||||
{
|
||||
const { videoWidth, videoHeight } = this.state;
|
||||
const { video } = this.refs;
|
||||
|
||||
// Don't re-render if nothing changed.
|
||||
if (video.videoWidth === videoWidth && video.videoHeight === videoHeight)
|
||||
return;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
videoWidth : video.videoWidth,
|
||||
videoHeight : video.videoHeight
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
_hideVideoResolution()
|
||||
{
|
||||
this.setState({ videoWidth: null, videoHeight: null });
|
||||
}
|
||||
}
|
||||
|
||||
PeerView.propTypes =
|
||||
{
|
||||
isMe : PropTypes.bool,
|
||||
peer : PropTypes.oneOfType(
|
||||
[ appPropTypes.Me, appPropTypes.Peer ]).isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
audioTrack : PropTypes.any,
|
||||
volume : PropTypes.number,
|
||||
videoTrack : PropTypes.any,
|
||||
videoVisible : PropTypes.bool.isRequired,
|
||||
videoProfile : PropTypes.string,
|
||||
audioCodec : PropTypes.string,
|
||||
videoCodec : PropTypes.string,
|
||||
onChangeDisplayName : PropTypes.func,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(PeerView);
|
||||
@@ -0,0 +1,246 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = () =>
|
||||
({
|
||||
root:
|
||||
{
|
||||
position : 'relative',
|
||||
flex : '100 100 auto',
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
overflow : 'hidden',
|
||||
backgroundColor : 'var(--peer-bg-color)',
|
||||
backgroundImage : 'var(--peer-empty-avatar)',
|
||||
backgroundPosition : 'bottom',
|
||||
backgroundSize : 'auto 85%',
|
||||
backgroundRepeat : 'no-repeat'
|
||||
},
|
||||
video :
|
||||
{
|
||||
flex : '100 100 auto',
|
||||
height : '100%',
|
||||
width : '100%',
|
||||
objectFit : 'cover',
|
||||
userSelect : 'none',
|
||||
transitionProperty : 'opacity',
|
||||
transitionDuration : '.15s',
|
||||
backgroundColor : 'var(--peer-video-bg-color)',
|
||||
'&.is-me' :
|
||||
{
|
||||
transform : 'scaleX(-1)'
|
||||
},
|
||||
'&.hidden' :
|
||||
{
|
||||
opacity : 0,
|
||||
transitionDuration : '0s'
|
||||
},
|
||||
'&.loading' :
|
||||
{
|
||||
filter : 'blur(5px)'
|
||||
}
|
||||
},
|
||||
info :
|
||||
{
|
||||
position : 'absolute',
|
||||
zIndex : 10,
|
||||
top : '0.6vmin',
|
||||
left : '0.6vmin',
|
||||
bottom : 0,
|
||||
right : 0,
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
justifyContent : 'space-between'
|
||||
},
|
||||
media :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
display : 'flex',
|
||||
flexDirection : 'row'
|
||||
},
|
||||
box :
|
||||
{
|
||||
padding : '0.4vmin',
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
||||
'& p' :
|
||||
{
|
||||
userSelect : 'none',
|
||||
pointerEvents : 'none',
|
||||
margin : 0,
|
||||
color : 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize : 10,
|
||||
|
||||
'&:last-child' :
|
||||
{
|
||||
marginBottom : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class ScreenView extends React.Component
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
|
||||
this.state =
|
||||
{
|
||||
screenWidth : null,
|
||||
screenHeight : null
|
||||
};
|
||||
|
||||
// Latest received screen track.
|
||||
// @type {MediaStreamTrack}
|
||||
this._screenTrack = null;
|
||||
|
||||
// Periodic timer for showing video resolution.
|
||||
this._screenResolutionTimer = null;
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
isMe,
|
||||
advancedMode,
|
||||
screenVisible,
|
||||
screenProfile,
|
||||
screenCodec,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
screenWidth,
|
||||
screenHeight
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.info}>
|
||||
{ advancedMode ?
|
||||
<div className={classnames(classes.media, { 'is-me': isMe })}>
|
||||
{ screenVisible ?
|
||||
<div className={classes.box}>
|
||||
{ screenCodec ?
|
||||
<p>{screenCodec} {screenProfile}</p>
|
||||
:null
|
||||
}
|
||||
|
||||
{ (screenVisible && screenWidth !== null) ?
|
||||
<p>{screenWidth}x{screenHeight}</p>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
|
||||
<video
|
||||
ref='video'
|
||||
className={classnames(classes.video, {
|
||||
hidden : !screenVisible,
|
||||
'is-me' : isMe,
|
||||
loading : screenProfile === 'none'
|
||||
})}
|
||||
autoPlay
|
||||
playsInline
|
||||
muted={Boolean(true)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
const { screenTrack } = this.props;
|
||||
|
||||
this._setTracks(screenTrack);
|
||||
}
|
||||
|
||||
componentWillUnmount()
|
||||
{
|
||||
clearInterval(this._screenResolutionTimer);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
{
|
||||
const { screenTrack } = nextProps;
|
||||
|
||||
this._setTracks(screenTrack);
|
||||
}
|
||||
|
||||
_setTracks(screenTrack)
|
||||
{
|
||||
if (this._screenTrack === screenTrack)
|
||||
return;
|
||||
|
||||
this._screenTrack = screenTrack;
|
||||
|
||||
clearInterval(this._screenResolutionTimer);
|
||||
this._hideScreenResolution();
|
||||
|
||||
const { video } = this.refs;
|
||||
|
||||
if (screenTrack)
|
||||
{
|
||||
const stream = new MediaStream();
|
||||
|
||||
if (screenTrack)
|
||||
stream.addTrack(screenTrack);
|
||||
|
||||
video.srcObject = stream;
|
||||
|
||||
if (screenTrack)
|
||||
this._showScreenResolution();
|
||||
}
|
||||
else
|
||||
{
|
||||
video.srcObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
_showScreenResolution()
|
||||
{
|
||||
this._screenResolutionTimer = setInterval(() =>
|
||||
{
|
||||
const { screenWidth, screenHeight } = this.state;
|
||||
const { video } = this.refs;
|
||||
|
||||
// Don't re-render if nothing changed.
|
||||
if (video.videoWidth === screenWidth && video.videoHeight === screenHeight)
|
||||
return;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
screenWidth : video.videoWidth,
|
||||
screenHeight : video.videoHeight
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
_hideScreenResolution()
|
||||
{
|
||||
this.setState({ screenWidth: null, screenHeight: null });
|
||||
}
|
||||
}
|
||||
|
||||
ScreenView.propTypes =
|
||||
{
|
||||
isMe : PropTypes.bool,
|
||||
advancedMode : PropTypes.bool,
|
||||
screenTrack : PropTypes.any,
|
||||
screenVisible : PropTypes.bool,
|
||||
screenProfile : PropTypes.string,
|
||||
screenCodec : PropTypes.string,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ScreenView);
|
||||
Reference in New Issue
Block a user