From 52ce9a86ed2c78b94de2586cbf39fbcbb4d69fbd Mon Sep 17 00:00:00 2001 From: apetrus20 <108003462+apetrus20@users.noreply.github.com> Date: Fri, 5 Aug 2022 12:11:09 +0300 Subject: [PATCH] feat(large-video/web) Add screen share placeholder (#11971) * feat(large-video/web) new ScreenSharePlaceholder component --- lang/main.json | 4 + modules/UI/videolayout/VideoContainer.js | 29 +++--- modules/UI/videolayout/VideoLayout.js | 8 +- react/features/large-video/actionTypes.ts | 11 +++ react/features/large-video/actions.web.js | 18 +++- .../large-video/components/LargeVideo.web.js | 51 +++++++++- .../components/ScreenSharePlaceholder.web.tsx | 99 +++++++++++++++++++ react/features/large-video/reducer.js | 10 +- 8 files changed, 209 insertions(+), 21 deletions(-) create mode 100644 react/features/large-video/components/ScreenSharePlaceholder.web.tsx diff --git a/lang/main.json b/lang/main.json index 3fdfd08bdb..967715afda 100644 --- a/lang/main.json +++ b/lang/main.json @@ -518,6 +518,10 @@ "toggleShortcuts": "Show or hide keyboard shortcuts", "videoMute": "Start or stop your camera" }, + "largeVideo": { + "screenIsShared": "You are sharing your screen", + "showMeWhatImSharing": "Show me what I'm sharing" + }, "liveStreaming": { "busy": "We're working on freeing streaming resources. Please try again in a few minutes.", "busyTitle": "All streamers are currently busy", diff --git a/modules/UI/videolayout/VideoContainer.js b/modules/UI/videolayout/VideoContainer.js index 57cbfeaf81..05865f3a42 100644 --- a/modules/UI/videolayout/VideoContainer.js +++ b/modules/UI/videolayout/VideoContainer.js @@ -480,7 +480,7 @@ export class VideoContainer extends LargeContainer { */ setStream(userID, stream, videoType) { this.userId = userID; - if (this.stream === stream) { + if (this.stream === stream && !stream?.forceStreamToReattach) { // Handles the use case for the remote participants when the // videoType is received with delay after turning on/off the // desktop sharing. @@ -492,8 +492,12 @@ export class VideoContainer extends LargeContainer { return; } + if (stream?.forceStreamToReattach) { + delete stream.forceStreamToReattach; + } + // detach old stream - if (this.stream) { + if (this.stream && this.$video[0]) { this.stream.detach(this.$video[0]); } @@ -504,19 +508,20 @@ export class VideoContainer extends LargeContainer { return; } - stream.attach(this.$video[0]); + if (this.$video[0]) { + stream.attach(this.$video[0]); - // Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the - // case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions. - browser.isWebKitBased() && this.$video[0].play(); + // Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the + // case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions. + browser.isWebKitBased() && this.$video[0].play(); - const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing(); - - this.$video.css({ - transform: flipX ? 'scaleX(-1)' : 'none' - }); + const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing(); - this._updateBackground(); + this.$video.css({ + transform: flipX ? 'scaleX(-1)' : 'none' + }); + this._updateBackground(); + } } /** diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index eef3d93aee..f5c9e528f0 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -177,7 +177,7 @@ const VideoLayout = { return largeVideo && largeVideo.id === id; }, - updateLargeVideo(id, forceUpdate) { + updateLargeVideo(id, forceUpdate, forceStreamToReattach = false) { if (!largeVideo) { return; } @@ -198,6 +198,10 @@ const VideoLayout = { const videoStream = videoTrack?.jitsiTrack; + if (videoStream && forceStreamToReattach) { + videoStream.forceStreamToReattach = forceStreamToReattach; + } + if (isOnLarge && !forceUpdate && LargeVideoManager.isVideoContainer(currentContainerType) && videoStream) { @@ -330,7 +334,7 @@ const VideoLayout = { */ _updateLargeVideoIfDisplayed(participantId, force = false) { if (this.isCurrentlyOnLarge(participantId)) { - this.updateLargeVideo(participantId, force); + this.updateLargeVideo(participantId, force, false); } }, diff --git a/react/features/large-video/actionTypes.ts b/react/features/large-video/actionTypes.ts index 4f2ee2b959..e5a6322101 100644 --- a/react/features/large-video/actionTypes.ts +++ b/react/features/large-video/actionTypes.ts @@ -30,3 +30,14 @@ export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION */ export const UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT = 'UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT'; + +/** + * Action to set the redux store of the current show me what I'm sharing flag value. + * + * @returns {{ + * type: SET_SEE_WHAT_IS_BEING_SHARED, + * seeWhatIsBeingShared: boolean + * }} + */ +export const SET_SEE_WHAT_IS_BEING_SHARED + = 'SET_SEE_WHAT_IS_BEING_SHARED'; diff --git a/react/features/large-video/actions.web.js b/react/features/large-video/actions.web.js index 4fce094bd9..96fbca4d51 100644 --- a/react/features/large-video/actions.web.js +++ b/react/features/large-video/actions.web.js @@ -6,7 +6,7 @@ import VideoLayout from '../../../modules/UI/videolayout/VideoLayout'; import { MEDIA_TYPE } from '../base/media'; import { getTrackByMediaTypeAndParticipant } from '../base/tracks'; -import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT } from './actionTypes'; +import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT, SET_SEE_WHAT_IS_BEING_SHARED } from './actionTypes'; export * from './actions.any'; @@ -101,3 +101,19 @@ export function updateLastLargeVideoMediaEvent(name: String) { name }; } + +/** + * Updates the value used to display what is being shared. + * + * @param {boolean} seeWhatIsBeingShared - The current value. + * @returns {{ + * type: SET_SEE_WHAT_IS_BEING_SHARED, + * seeWhatIsBeingShared: boolean + * }} + */ +export function setSeeWhatIsBeingShared(seeWhatIsBeingShared: boolean) { + return { + type: SET_SEE_WHAT_IS_BEING_SHARED, + seeWhatIsBeingShared + }; +} diff --git a/react/features/large-video/components/LargeVideo.web.js b/react/features/large-video/components/LargeVideo.web.js index f30d5989a3..b5b024438c 100644 --- a/react/features/large-video/components/LargeVideo.web.js +++ b/react/features/large-video/components/LargeVideo.web.js @@ -3,15 +3,22 @@ import React, { Component } from 'react'; import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout'; +import { getLocalParticipant } from '../../base/participants'; import { Watermarks } from '../../base/react'; import { connect } from '../../base/redux'; +import { getVideoTrackByParticipant } from '../../base/tracks'; import { setColorAlpha } from '../../base/util'; import { StageParticipantNameLabel } from '../../display-name'; import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../filmstrip'; import { getVerticalViewMaxWidth } from '../../filmstrip/functions.web'; +import { getLargeVideoParticipant } from '../../large-video/functions'; import { SharedVideo } from '../../shared-video/components/web'; import { Captions } from '../../subtitles/'; import { setTileView } from '../../video-layout/actions'; +import { setSeeWhatIsBeingShared } from '../actions.web'; + +import ScreenSharePlaceholder from './ScreenSharePlaceholder.web'; + declare var interfaceConfig: Object; @@ -68,6 +75,21 @@ type Props = { */ _visibleFilmstrip: boolean, + /** + * The large video participant id. + */ + _largeVideoParticipantId: string, + + /** + * Whether or not the screen sharing is on. + */ + _isScreenSharing: boolean, + + /** + * Whether or not the screen sharing is visible. + */ + _seeWhatIsBeingShared: boolean, + /** * The Redux dispatch function. */ @@ -109,11 +131,19 @@ class LargeVideo extends Component { * @inheritdoc */ componentDidUpdate(prevProps: Props) { - const { _visibleFilmstrip } = this.props; + const { _visibleFilmstrip, _isScreenSharing, _seeWhatIsBeingShared, _largeVideoParticipantId } = this.props; if (prevProps._visibleFilmstrip !== _visibleFilmstrip) { this._updateLayout(); } + + if (prevProps._isScreenSharing !== _isScreenSharing && !_isScreenSharing) { + this.props.dispatch(setSeeWhatIsBeingShared(false)); + } + + if (_isScreenSharing && _seeWhatIsBeingShared) { + VideoLayout.updateLargeVideo(_largeVideoParticipantId, true, true); + } } /** @@ -126,7 +156,9 @@ class LargeVideo extends Component { const { _isChatOpen, _noAutoPlayVideo, - _showDominantSpeakerBadge + _showDominantSpeakerBadge, + _isScreenSharing, + _seeWhatIsBeingShared } = this.props; const style = this._getCustomSyles(); const className = `videocontainer${_isChatOpen ? ' shift-right' : ''}`; @@ -152,7 +184,6 @@ class LargeVideo extends Component {
- {/* * FIXME: the architecture of elements related to the large * video and the naming. The background is not part of @@ -166,11 +197,11 @@ class LargeVideo extends Component { onTouchEnd = { this._onDoubleTap } ref = { this._wrapperRef } role = 'figure' > -
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES @@ -292,13 +323,23 @@ function _mapStateToProps(state) { const { width: verticalFilmstripWidth, visible } = state['features/filmstrip']; const { hideDominantSpeakerBadge } = state['features/base/config']; + const tracks = state['features/base/tracks']; + const localParticipantId = getLocalParticipant(state)?.id; + const largeVideoParticipant = getLargeVideoParticipant(state); + const videoTrack = getVideoTrackByParticipant(tracks, largeVideoParticipant); + const localParticipantisSharingTheScreen = largeVideoParticipant?.id?.includes(localParticipantId); + const isScreenSharing = localParticipantisSharingTheScreen && videoTrack?.videoType === 'desktop'; + return { _backgroundAlpha: state['features/base/config'].backgroundAlpha, _customBackgroundColor: backgroundColor, _customBackgroundImageUrl: backgroundImageUrl, _isChatOpen: isChatOpen, + _isScreenSharing: isScreenSharing, + _largeVideoParticipantId: largeVideoParticipant?.id, _noAutoPlayVideo: testingConfig?.noAutoPlayVideo, _resizableFilmstrip: isFilmstripResizable(state), + _seeWhatIsBeingShared: state['features/large-video'].seeWhatIsBeingShared, _showDominantSpeakerBadge: !hideDominantSpeakerBadge, _verticalFilmstripWidth: verticalFilmstripWidth.current, _verticalViewMaxWidth: getVerticalViewMaxWidth(state), diff --git a/react/features/large-video/components/ScreenSharePlaceholder.web.tsx b/react/features/large-video/components/ScreenSharePlaceholder.web.tsx new file mode 100644 index 0000000000..1700ec491e --- /dev/null +++ b/react/features/large-video/components/ScreenSharePlaceholder.web.tsx @@ -0,0 +1,99 @@ +/* eslint-disable lines-around-comment */ +import { makeStyles, createStyles } from '@material-ui/core'; +import React, { useCallback } from 'react'; +import { useStore } from 'react-redux'; + +// @ts-ignore +import { translate } from '../../base/i18n'; +import { Theme } from '../../base/ui/types'; +// @ts-ignore +import { setSeeWhatIsBeingShared } from '../actions.web'; + +const useStyles = makeStyles((theme: Theme) => createStyles({ + overlayContainer: { + width: '100%', + height: '100%', + backgroundColor: theme.palette.ui02, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + position: 'absolute' + }, + content: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center' + }, + laptop: { + width: '88px', + height: '56px', + boxSizing: 'border-box', + border: '3px solid', + borderColor: theme.palette.text01, + borderRadius: '6px' + }, + laptopStand: { + width: '40px', + height: '4px', + backgroundColor: theme.palette.text01, + boxSizing: 'border-box', + borderRadius: '6px', + marginTop: '4px' + }, + sharingMessage: { + fontStyle: 'normal', + fontWeight: 600, + fontSize: '20px', + lineHeight: '28px', + marginTop: '24px', + letterSpacing: '-0.012em', + color: theme.palette.text01 + }, + showSharing: { + fontStyle: 'normal', + fontWeight: 600, + fontSize: '14px', + lineHeight: '20px', + height: '20px', + marginTop: '16px', + color: theme.palette.link01, + cursor: 'pointer', + + '&:hover': { + color: theme.palette.link01Hover + } + } +})); + +/** + * Component that displays a placehoder for when the screen is shared. + * * @param {Function} t - Function which translate strings. + * + * @returns {ReactElement} + */ +const ScreenSharePlaceholder: React.FC<{ t: Function }> = ({ t }) => { + const classes = useStyles(); + const store = useStore(); + + + const updateShowMeWhatImSharing = useCallback(() => { + store.dispatch(setSeeWhatIsBeingShared(true)); + }, []); + + return ( +
+
+
+
+ { t('largeVideo.screenIsShared') } + { t('largeVideo.showMeWhatImSharing') } +
+
+ ); +}; + +export default translate(ScreenSharePlaceholder); diff --git a/react/features/large-video/reducer.js b/react/features/large-video/reducer.js index a460592ea5..5266c97b20 100644 --- a/react/features/large-video/reducer.js +++ b/react/features/large-video/reducer.js @@ -5,7 +5,9 @@ import { ReducerRegistry } from '../base/redux'; import { SELECT_LARGE_VIDEO_PARTICIPANT, - UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT + UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, + UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT, + SET_SEE_WHAT_IS_BEING_SHARED } from './actionTypes'; ReducerRegistry.register('features/large-video', (state = {}, action) => { @@ -43,6 +45,12 @@ ReducerRegistry.register('features/large-video', (state = {}, action) => { lastMediaEvent: action.name }; + case SET_SEE_WHAT_IS_BEING_SHARED: + return { + ...state, + seeWhatIsBeingShared: action.seeWhatIsBeingShared + }; + } return state;