From a0efc1542249831b9751c602ca824afd35e6b39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 4 Mar 2018 13:38:38 +0100 Subject: [PATCH] Component to show screensharing --- app/lib/components/PeerScreenView.jsx | 160 ++++++++++++++++++++++ app/stylus/components/PeerScreenView.styl | 146 ++++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 app/lib/components/PeerScreenView.jsx create mode 100644 app/stylus/components/PeerScreenView.styl diff --git a/app/lib/components/PeerScreenView.jsx b/app/lib/components/PeerScreenView.jsx new file mode 100644 index 0000000..62672aa --- /dev/null +++ b/app/lib/components/PeerScreenView.jsx @@ -0,0 +1,160 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import Spinner from 'react-spinner'; + +export default class PeerScreenView extends React.Component +{ + constructor(props) + { + super(props); + + this.state = + { + videoWidth : null, + videoHeight : null + }; + + // Latest received video track. + // @type {MediaStreamTrack} + this._videoTrack = null; + + // Periodic timer for showing video resolution. + this._videoResolutionTimer = null; + } + + render() + { + const { + isMe, + videoVisible, + videoProfile, + videoCodec + } = this.props; + + const { + videoWidth, + videoHeight + } = this.state; + + return ( +
+
+
+
+ {videoCodec ? +

{videoCodec} {videoProfile}

+ :null + } + + {(videoVisible && videoWidth !== null) ? +

{videoWidth}x{videoHeight}

+ :null + } +
+
+
+ +
+ ); + } + + componentDidMount() + { + const { videoTrack } = this.props; + + this._setTracks(videoTrack); + } + + componentWillUnmount() + { + clearInterval(this._videoResolutionTimer); + } + + componentWillReceiveProps(nextProps) + { + const { videoTrack } = nextProps; + + this._setTracks(videoTrack); + } + + _setTracks(videoTrack) + { + if (this._videoTrack === videoTrack) + return; + + this._videoTrack = videoTrack; + + clearInterval(this._videoResolutionTimer); + this._hideVideoResolution(); + + const { video } = this.refs; + + if (videoTrack) + { + const stream = new MediaStream; + + 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 }); + } +} + +PeerScreenView.propTypes = +{ + isMe : PropTypes.bool, + videoTrack : PropTypes.any, + videoVisible : PropTypes.bool.isRequired, + videoProfile : PropTypes.string, + videoCodec : PropTypes.string +}; diff --git a/app/stylus/components/PeerScreenView.styl b/app/stylus/components/PeerScreenView.styl new file mode 100644 index 0000000..d83e108 --- /dev/null +++ b/app/stylus/components/PeerScreenView.styl @@ -0,0 +1,146 @@ +[data-component='PeerScreenView'] { + position: relative; + flex: 100 100 auto; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: rgba(#2a4b58, 0.9); + background-image: url('/resources/images/buddy.svg'); + background-position: bottom; + background-size: auto 85%; + background-repeat: no-repeat; + + > .info { + $backgroundTint = #000; + + position: absolute; + z-index: 5 + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + background: linear-gradient(to bottom, + rgba($backgroundTint, 0) 0%, + rgba($backgroundTint, 0) 60%, + rgba($backgroundTint, 0.1) 70%, + rgba($backgroundTint, 0.8) 100%); + + > .media { + flex: 0 0 auto; + display: flex; + flex-direction: row; + + > .box { + margin: 4px; + padding: 2px 4px; + border-radius: 2px; + background-color: rgba(#000, 0.25); + + > p { + user-select: none; + pointer-events: none; + margin-bottom: 2px; + color: rgba(#fff, 0.7); + font-size: 10px; + + &:last-child { + margin-bottom: 0; + } + } + } + } + + > .peer { + flex: 0 0 auto; + display: flex; + flex-direction: column; + justify-content: flex-end; + + +desktop() { + &.is-me { + padding: 10px; + align-items: flex-start; + } + + &:not(.is-me) { + padding: 20px; + align-items: flex-start; + } + } + + +mobile() { + &.is-me { + padding: 10px; + align-items: flex-start; + } + + &:not(.is-me) { + padding: 10px; + align-items: flex-end; + } + } + } + } + + > video { + flex: 100 100 auto; + height: 100%; + width: 100%; + object-fit: cover; + user-select: none; + transition-property: opacity; + transition-duration: .15s; + background-color: rgba(#000, 0.75); + + &.is-me { + transform: scaleX(-1); + } + + &.hidden { + opacity: 0; + transition-duration: 0s; + } + + &.loading { + filter: blur(5px); + } + } + + > .spinner-container { + position: absolute; + top: 0 + bottom: 0; + left: 0; + right: 0; + background-color: rgba(#000, 0.75); + + .react-spinner { + position: relative; + width: 48px; + height: 48px; + top: 50%; + left: 50%; + + .react-spinner_bar { + position: absolute; + width: 20%; + height: 7.8%; + top: -3.9%; + left: -10%; + animation: PeerView-spinner 1.2s linear infinite; + border-radius: 5px; + background-color: rgba(#fff, 0.5); + } + } + } +} + +@keyframes PeerScreenView-spinner { + 0% { opacity: 1; } + 100% { opacity: 0.15; } +}