diff --git a/css/filmstrip/_vertical_filmstrip.scss b/css/filmstrip/_vertical_filmstrip.scss index 08813716cb..35b36eb13d 100644 --- a/css/filmstrip/_vertical_filmstrip.scss +++ b/css/filmstrip/_vertical_filmstrip.scss @@ -1,175 +1,177 @@ -.vertical-filmstrip span:not(.tile-view) .filmstrip { - &.hide-videos { - .remote-videos { - & > div { - opacity: 0; - pointer-events: none; +.vertical-filmstrip, .stage-filmstrip { + span:not(.tile-view) .filmstrip { + &.hide-videos { + .remote-videos { + & > div { + opacity: 0; + pointer-events: none; + } } } - } - /* - * Firefox sets flex items to min-height: auto and min-width: auto, - * preventing flex children from shrinking like they do on other browsers. - * Setting min-height and min-width 0 is a workaround for the issue so - * Firefox behaves like other browsers. - * https://bugzilla.mozilla.org/show_bug.cgi?id=1043520 - */ - @mixin minHWAutoFix() { - min-height: 0; - min-width: 0; - } + /* + * Firefox sets flex items to min-height: auto and min-width: auto, + * preventing flex children from shrinking like they do on other browsers. + * Setting min-height and min-width 0 is a workaround for the issue so + * Firefox behaves like other browsers. + * https://bugzilla.mozilla.org/show_bug.cgi?id=1043520 + */ + @mixin minHWAutoFix() { + min-height: 0; + min-width: 0; + } - @extend %align-right; - align-items: flex-end; - bottom: 0; - box-sizing: border-box; - display: flex; - flex-direction: column-reverse; - height: 100%; - width: 100%; - padding: 0; - /** - * fixed positioning is necessary for remote menus and tooltips to pop - * out of the scrolling filmstrip. AtlasKit dialogs and tooltips use - * a library called popper which will position its elements fixed if - * any parent is also fixed. - */ - position: fixed; - top: 0; - right: 0; - z-index: $filmstripVideosZ; - - &.no-vertical-padding { - padding: 0; - } - - /** - * Hide videos by making them slight to the right. - */ - .filmstrip__videos { @extend %align-right; + align-items: flex-end; bottom: 0; + box-sizing: border-box; + display: flex; + flex-direction: column-reverse; + height: 100%; + width: 100%; padding: 0; - position:relative; + /** + * fixed positioning is necessary for remote menus and tooltips to pop + * out of the scrolling filmstrip. AtlasKit dialogs and tooltips use + * a library called popper which will position its elements fixed if + * any parent is also fixed. + */ + position: fixed; + top: 0; right: 0; - width: auto; + z-index: $filmstripVideosZ; + + &.no-vertical-padding { + padding: 0; + } /** - * An id selector is used to match id specificity with existing - * filmstrip styles. + * Hide videos by making them slight to the right. */ - &#remoteVideos { - border: $thumbnailsBorder solid transparent; - padding-left: 0; - border-left: 0; + .filmstrip__videos { + @extend %align-right; + bottom: 0; + padding: 0; + position:relative; + right: 0; + width: auto; + + /** + * An id selector is used to match id specificity with existing + * filmstrip styles. + */ + &#remoteVideos { + border: $thumbnailsBorder solid transparent; + padding-left: 0; + border-left: 0; + width: 100%; + height: 100%; + justify-content: center; + } + } + + /** + * Re-styles the local Video to better fit vertical filmstrip layout. + */ + #filmstripLocalVideo { + align-self: initial; + margin-bottom: 5px; + display: flex; + flex-direction: column-reverse; + height: auto; + justify-content: flex-start; width: 100%; - height: 100%; - justify-content: center; - } - } - /** - * Re-styles the local Video to better fit vertical filmstrip layout. - */ - #filmstripLocalVideo { - align-self: initial; - margin-bottom: 5px; - display: flex; - flex-direction: column-reverse; - height: auto; - justify-content: flex-start; - width: 100%; + #filmstripLocalVideoThumbnail { + width: calc(100% - 15px); - #filmstripLocalVideoThumbnail { - width: calc(100% - 15px); - - .videocontainer { - height: 0px; - width: 100%; + .videocontainer { + height: 0px; + width: 100%; + } } } - } - #filmstripLocalScreenShare { - align-self: initial; - margin-bottom: 5px; - display: flex; - flex-direction: column-reverse; - height: auto; - justify-content: flex-start; - width: 100%; + #filmstripLocalScreenShare { + align-self: initial; + margin-bottom: 5px; + display: flex; + flex-direction: column-reverse; + height: auto; + justify-content: flex-start; + width: 100%; - #filmstripLocalScreenShareThumbnail { - width: calc(100% - 15px); + #filmstripLocalScreenShareThumbnail { + width: calc(100% - 15px); - .videocontainer { - height: 0px; - width: 100%; + .videocontainer { + height: 0px; + width: 100%; + } } } - } - /** - * Remove unnecssary padding that is normally used to prevent horizontal - * filmstrip from overlapping the left edge of the screen. - */ - #filmstripLocalVideo, - #filmstripLocalScreenShare, - .remote-videos { - padding: 0; - } - - #remoteVideos { - @include minHWAutoFix(); - - flex-direction: column; - flex-grow: 1; - } - - .resizable-filmstrip #remoteVideos .videocontainer { - border-left: 0; - margin: 0; - } - - &.reduce-height { - height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight})); - } - - .filmstrip__videos.vertical-view-grid#remoteVideos { - align-items: 'center'; - border: 0px; - padding-right: 7px; - - &.has-scroll { - padding-right: 0px; + /** + * Remove unnecssary padding that is normally used to prevent horizontal + * filmstrip from overlapping the left edge of the screen. + */ + #filmstripLocalVideo, + #filmstripLocalScreenShare, + .remote-videos { + padding: 0; } - .remote-videos > div { - left: 0px; // fixes an issue on FF - the div is aligned to the right by default for some reason + #remoteVideos { + @include minHWAutoFix(); + + flex-direction: column; + flex-grow: 1; } - .videocontainer { + .resizable-filmstrip #remoteVideos .videocontainer { + border-left: 0; + margin: 0; + } + + &.reduce-height { + height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight})); + } + + .filmstrip__videos.vertical-view-grid#remoteVideos { + align-items: 'center'; border: 0px; - margin: 2px; - } - } + padding-right: 7px; - .remote-videos { - display: flex; - overscroll-behavior: contain; + &.has-scroll { + padding-right: 0px; + } - &.height-transition { - transition: height .3s ease-in; + .remote-videos > div { + left: 0px; // fixes an issue on FF - the div is aligned to the right by default for some reason + } + + .videocontainer { + border: 0px; + margin: 2px; + } } - & > div { - position: absolute; - transition: opacity 1s; - } + .remote-videos { + display: flex; + overscroll-behavior: contain; - &.is-not-overflowing > div { - bottom: 0px; + &.height-transition { + transition: height .3s ease-in; + } + + & > div { + position: absolute; + transition: opacity 1s; + } + + &.is-not-overflowing > div { + bottom: 0px; + } } } } diff --git a/css/filmstrip/_vertical_filmstrip_overrides.scss b/css/filmstrip/_vertical_filmstrip_overrides.scss index 9e29a29f29..6dbf4690c6 100644 --- a/css/filmstrip/_vertical_filmstrip_overrides.scss +++ b/css/filmstrip/_vertical_filmstrip_overrides.scss @@ -3,14 +3,17 @@ * clashing with the filmstrip. */ .vertical-filmstrip #etherpad, -.vertical-filmstrip #sharedvideo { +.stage-filmstrip #etherpad, +.vertical-filmstrip #sharedvideo, +.stage-filmstrip #sharedvideo { text-align: left; } /** * Overrides for small videos in vertical filmstrip mode. */ -.vertical-filmstrip .filmstrip__videos .videocontainer { +.vertical-filmstrip .filmstrip__videos .videocontainer, +.stage-filmstrip .filmstrip__videos .videocontainer { .self-view-mobile-portrait video { object-fit: contain; } @@ -27,7 +30,8 @@ * The class opening is for when the filmstrip is transitioning from hidden * to visible. */ -.vertical-filmstrip .large-video-labels { +.vertical-filmstrip .large-video-labels, +.stage-filmstrip .large-video-labels { &.with-filmstrip { right: 150px; } @@ -47,6 +51,7 @@ * Overrides for self view when in portrait mode on mobile. * This is done in order to keep the aspect ratio. */ -.vertical-filmstrip .self-view-mobile-portrait #localVideo_container { +.vertical-filmstrip .self-view-mobile-portrait #localVideo_container, +.stage-filmstrip .self-view-mobile-portrait #localVideo_container { object-fit: contain; } diff --git a/modules/UI/videolayout/VideoContainer.js b/modules/UI/videolayout/VideoContainer.js index 41e9f27d50..57cbfeaf81 100644 --- a/modules/UI/videolayout/VideoContainer.js +++ b/modules/UI/videolayout/VideoContainer.js @@ -6,7 +6,7 @@ import ReactDOM from 'react-dom'; import { browser } from '../../../react/features/base/lib-jitsi-meet'; import { isTestModeEnabled } from '../../../react/features/base/testing'; -import { FILMSTRIP_BREAKPOINT, shouldDisplayStageFilmstrip } from '../../../react/features/filmstrip'; +import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip'; import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video'; import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout'; /* eslint-enable no-unused-vars */ @@ -414,7 +414,7 @@ export class VideoContainer extends LargeContainer { const verticalFilmstripWidth = state['features/filmstrip'].width?.current; - if (currentLayout === LAYOUTS.TILE_VIEW || shouldDisplayStageFilmstrip(state)) { + if (currentLayout === LAYOUTS.TILE_VIEW || currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW) { // We don't need to resize the large video since it won't be displayed and we'll resize when returning back // to stage view. return; diff --git a/react/features/base/participants/functions.js b/react/features/base/participants/functions.js index 79e841c883..fdf2e45032 100644 --- a/react/features/base/participants/functions.js +++ b/react/features/base/participants/functions.js @@ -4,7 +4,7 @@ import { getGravatarURL } from '@jitsi/js-utils/avatar'; import type { Store } from 'redux'; import { i18next } from '../../base/i18n'; -import { isStageFilmstripEnabled } from '../../filmstrip/functions'; +import { isStageFilmstripAvailable } from '../../filmstrip/functions'; import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar'; import { getSourceNameSignalingFeatureFlag } from '../config'; import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet'; @@ -372,7 +372,7 @@ export function getRemoteParticipantsSorted(stateful: Object | Function) { export function getPinnedParticipant(stateful: Object | Function) { const state = toState(stateful); const { pinnedParticipant } = state['features/base/participants']; - const stageFilmstrip = isStageFilmstripEnabled(state); + const stageFilmstrip = isStageFilmstripAvailable(state); if (stageFilmstrip) { const { activeParticipants } = state['features/filmstrip']; diff --git a/react/features/conference/components/web/Conference.js b/react/features/conference/components/web/Conference.js index 6c47ca179b..46c8cf3cd9 100644 --- a/react/features/conference/components/web/Conference.js +++ b/react/features/conference/components/web/Conference.js @@ -1,6 +1,5 @@ // @flow -import clsx from 'clsx'; import _ from 'lodash'; import React from 'react'; @@ -12,7 +11,7 @@ import { translate } from '../../../base/i18n'; import { connect as reactReduxConnect } from '../../../base/redux'; import { setColorAlpha } from '../../../base/util'; import { Chat } from '../../../chat'; -import { MainFilmstrip, StageFilmstrip, shouldDisplayStageFilmstrip } from '../../../filmstrip'; +import { MainFilmstrip, StageFilmstrip } from '../../../filmstrip'; import { CalleeInfoContainer } from '../../../invite'; import { LargeVideo } from '../../../large-video'; import { LobbyScreen } from '../../../lobby'; @@ -59,7 +58,8 @@ const FULL_SCREEN_EVENTS = [ export const LAYOUT_CLASSNAMES = { [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip', [LAYOUTS.TILE_VIEW]: 'tile-view', - [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip' + [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip', + [LAYOUTS.STAGE_FILMSTRIP_VIEW]: 'stage-filmstrip' }; /** @@ -103,11 +103,6 @@ type Props = AbstractProps & { */ _showPrejoin: boolean, - /** - * Whether or not the stage filmstrip should be displayed. - */ - _showStageFilmstrip: boolean, - dispatch: Function, t: Function } @@ -220,8 +215,7 @@ class Conference extends AbstractConference { _notificationsVisible, _overflowDrawer, _showLobby, - _showPrejoin, - _showStageFilmstrip + _showPrejoin } = this.props; return ( @@ -233,7 +227,7 @@ class Conference extends AbstractConference { ref = { this._setBackground }>
@@ -242,7 +236,7 @@ class Conference extends AbstractConference { id = 'videospace' onTouchStart = { this._onVidespaceTouchStart }> - {_showStageFilmstrip && } +
@@ -402,8 +396,7 @@ function _mapStateToProps(state) { _overflowDrawer: overflowDrawer, _roomName: getConferenceNameForTitle(state), _showLobby: getIsLobbyVisible(state), - _showPrejoin: isPrejoinPageVisible(state), - _showStageFilmstrip: shouldDisplayStageFilmstrip(state) + _showPrejoin: isPrejoinPageVisible(state) }; } diff --git a/react/features/display-name/components/web/DisplayName.js b/react/features/display-name/components/web/DisplayName.js index ec2a039139..9aa285dfbf 100644 --- a/react/features/display-name/components/web/DisplayName.js +++ b/react/features/display-name/components/web/DisplayName.js @@ -36,11 +36,6 @@ type Props = { */ allowEditing: boolean, - /** - * The current layout of the filmstrip. - */ - currentLayout: string, - /** * Invoked to update the participant's display name. */ @@ -70,7 +65,12 @@ type Props = { /** * Invoked to obtain translated strings. */ - t: Function + t: Function, + + /** + * The type of thumbnail. + */ + thumbnailType: string }; /** @@ -183,11 +183,11 @@ class DisplayName extends Component { const { _nameToDisplay, allowEditing, - currentLayout, displayNameSuffix, classes, elementID, - t + t, + thumbnailType } = this.props; if (allowEditing && this.state.isEditing) { @@ -211,7 +211,7 @@ class DisplayName extends Component { return ( + position = { getIndicatorsTooltipPosition(thumbnailType) }> 0, - numberOfParticipants, - numberOfVisibleTiles - }); + } = calculateResponsiveTileViewDimensions({ + clientWidth: availableWidth, + clientHeight, + disableTileEnlargement: false, + maxColumns, + noHorizontalContainerMargin: verticalWidth > 0, + numberOfParticipants, + numberOfVisibleTiles + }); const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height); const hasScroll = clientHeight < thumbnailsTotalHeight; const filmstripWidth @@ -469,3 +466,14 @@ export function togglePinStageParticipant(participantId) { participantId }; } + +/** + * Clears the stage participants list. + * + * @returns {Object} + */ +export function clearStageParticipants() { + return { + type: CLEAR_STAGE_PARTICIPANTS + }; +} diff --git a/react/features/filmstrip/components/web/Filmstrip.js b/react/features/filmstrip/components/web/Filmstrip.js index 1af0aa4576..6a5f005494 100644 --- a/react/features/filmstrip/components/web/Filmstrip.js +++ b/react/features/filmstrip/components/web/Filmstrip.js @@ -20,7 +20,7 @@ import { connect } from '../../../base/redux'; import { shouldHideSelfView } from '../../../base/settings/functions.any'; import { showToolbox } from '../../../toolbox/actions.web'; import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web'; -import { LAYOUTS } from '../../../video-layout'; +import { getCurrentLayout, LAYOUTS } from '../../../video-layout'; import { setFilmstripVisible, setVisibleRemoteParticipants, @@ -110,7 +110,7 @@ type Props = { /** * The local screen share participant. This prop is behind the sourceNameSignaling feature flag. */ - _localScreenShare: Object, + _localScreenShare: Object, /** * The maximum width of the vertical filmstrip. @@ -318,32 +318,29 @@ class Filmstrip extends PureComponent { const { isMouseDown } = this.state; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; - switch (_currentLayout) { - case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: { + if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && _stageFilmstrip) { + if (_visible) { + filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`; + } + } else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW + || (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && !_stageFilmstrip)) { filmstripStyle.maxWidth = _verticalViewMaxWidth; if (!_visible) { filmstripStyle.right = `-${filmstripStyle.maxWidth}px`; } - break; - } - case LAYOUTS.TILE_VIEW: { - if (_stageFilmstrip && _visible) { - filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`; - } - break; - } } let toolbar = null; - if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled && _currentLayout !== LAYOUTS.TILE_VIEW) { + if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled + && _currentLayout !== LAYOUTS.TILE_VIEW && !_stageFilmstrip) { toolbar = this._renderToggleButton(); } const filmstrip = (<>
{!_disableSelfView && !_verticalViewGrid && ( @@ -351,7 +348,7 @@ class Filmstrip extends PureComponent { className = 'filmstrip__videos' id = 'filmstripLocalVideo'> { - !tileViewActive &&
+ !tileViewActive && !_stageFilmstrip &&
@@ -364,7 +361,7 @@ class Filmstrip extends PureComponent { id = 'filmstripLocalScreenShare'>
{ - !tileViewActive && @@ -604,6 +601,7 @@ class Filmstrip extends PureComponent { _filmstripHeight, _filmstripWidth, _hasScroll, + _isVerticalFilmstrip, _remoteParticipantsLength, _resizableFilmstrip, _rows, @@ -619,7 +617,7 @@ class Filmstrip extends PureComponent { return null; } - if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid) { + if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || _stageFilmstrip) { return ( { props.className += ' is-not-overflowing'; } - } else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) { + } else if (_isVerticalFilmstrip) { const itemSize = _thumbnailHeight + TILE_VERTICAL_MARGIN; const isNotOverflowing = !_hasScroll; @@ -820,15 +818,20 @@ function _mapStateToProps(state, ownProps) { shouldReduceHeight ? 'reduce-height' : '' } ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim(); + const _currentLayout = getCurrentLayout(state); + const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW + || (!ownProps._stageFilmstrip && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW); + return { _className: className, _chatOpen: state['features/chat'].isOpen, + _currentLayout, _disableSelfView: disableSelfView, _hasScroll, _iAmRecorder: Boolean(iAmRecorder), _isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state), _isToolboxVisible: isToolboxVisible(state), - _isVerticalFilmstrip: ownProps._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW, + _isVerticalFilmstrip, _localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare, _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH, _thumbnailsReordered: enableThumbnailReordering, diff --git a/react/features/filmstrip/components/web/MainFilmstrip.js b/react/features/filmstrip/components/web/MainFilmstrip.js index de5b89d0bb..66d68e2df6 100644 --- a/react/features/filmstrip/components/web/MainFilmstrip.js +++ b/react/features/filmstrip/components/web/MainFilmstrip.js @@ -17,11 +17,6 @@ import Filmstrip from './Filmstrip'; type Props = { - /** - * The current layout of the filmstrip. - */ - _currentLayout: string, - /** * The number of columns in tile view. */ @@ -143,7 +138,8 @@ function _mapStateToProps(state) { && clientWidth <= ASPECT_RATIO_BREAKPOINT; const shouldReduceHeight = reduceHeight && ( - isMobileBrowser() || _currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW); + isMobileBrowser() || (_currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW + && _currentLayout !== LAYOUTS.STAGE_FILMSTRIP_VIEW)); let _thumbnailSize, remoteFilmstripHeight, remoteFilmstripWidth; @@ -154,7 +150,8 @@ function _mapStateToProps(state) { remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0); remoteFilmstripWidth = filmstripWidth; break; - case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: { + case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: + case LAYOUTS.STAGE_FILMSTRIP_VIEW: { const { remote, remoteVideosContainer, @@ -189,7 +186,6 @@ function _mapStateToProps(state) { return { _columns: gridDimensions.columns, - _currentLayout, _filmstripHeight: remoteFilmstripHeight, _filmstripWidth: remoteFilmstripWidth, _hasScroll, diff --git a/react/features/filmstrip/components/web/StageFilmstrip.js b/react/features/filmstrip/components/web/StageFilmstrip.js index 57f75b1c2a..97cbdcd23a 100644 --- a/react/features/filmstrip/components/web/StageFilmstrip.js +++ b/react/features/filmstrip/components/web/StageFilmstrip.js @@ -92,11 +92,10 @@ type Props = { _visible: boolean }; -const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW && ( +const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && ( ); diff --git a/react/features/filmstrip/components/web/StatusIndicators.js b/react/features/filmstrip/components/web/StatusIndicators.js index 7fd7900973..aaddf2d593 100644 --- a/react/features/filmstrip/components/web/StatusIndicators.js +++ b/react/features/filmstrip/components/web/StatusIndicators.js @@ -6,7 +6,6 @@ import { MEDIA_TYPE } from '../../../base/media'; import { getParticipantByIdOrUndefined, PARTICIPANT_ROLE } from '../../../base/participants'; import { connect } from '../../../base/redux'; import { getTrackByMediaTypeAndParticipant, isLocalTrackMuted, isRemoteTrackMuted } from '../../../base/tracks'; -import { getCurrentLayout } from '../../../video-layout'; import { getIndicatorsTooltipPosition } from '../../functions.web'; import AudioMutedIndicator from './AudioMutedIndicator'; @@ -20,11 +19,6 @@ declare var interfaceConfig: Object; */ type Props = { - /** - * The current layout of the filmstrip. - */ - _currentLayout: string, - /** * Indicates if the audio muted indicator should be visible or not. */ @@ -43,7 +37,12 @@ type Props = { /** * The ID of the participant for which the status bar is rendered. */ - participantID: String + participantID: String, + + /** + * The type of thumbnail. + */ + thumbnailType: string }; /** @@ -60,12 +59,12 @@ class StatusIndicators extends Component { */ render() { const { - _currentLayout, _showAudioMutedIndicator, _showModeratorIndicator, - _showScreenShareIndicator + _showScreenShareIndicator, + thumbnailType } = this.props; - const tooltipPosition = getIndicatorsTooltipPosition(_currentLayout); + const tooltipPosition = getIndicatorsTooltipPosition(thumbnailType); return ( <> @@ -111,7 +110,6 @@ function _mapStateToProps(state, ownProps) { const { disableModeratorIndicator } = state['features/base/config']; return { - _currentLayout: getCurrentLayout(state), _showAudioMutedIndicator: isAudioMuted && audio, _showModeratorIndicator: !disableModeratorIndicator && participant && participant.role === PARTICIPANT_ROLE.MODERATOR && moderator, diff --git a/react/features/filmstrip/components/web/Thumbnail.js b/react/features/filmstrip/components/web/Thumbnail.js index 5aba92bf4c..369916529f 100644 --- a/react/features/filmstrip/components/web/Thumbnail.js +++ b/react/features/filmstrip/components/web/Thumbnail.js @@ -36,6 +36,7 @@ import { DISPLAY_MODE_TO_CLASS_NAME, DISPLAY_VIDEO, SHOW_TOOLBAR_CONTEXT_MENU_AFTER, + THUMBNAIL_TYPE, VIDEO_TEST_EVENTS } from '../../constants'; import { @@ -44,9 +45,9 @@ import { getDisplayModeInput, isVideoPlayable, showGridInVerticalView, - isStageFilmstripEnabled, - shouldDisplayStageFilmstrip + isStageFilmstripAvailable } from '../../functions'; +import { getThumbnailTypeFromLayout } from '../../functions.web'; import FakeScreenShareParticipant from './FakeScreenShareParticipant'; import ThumbnailAudioIndicator from './ThumbnailAudioIndicator'; @@ -91,11 +92,6 @@ export type Props = {| */ _audioTrack: ?Object, - /** - * The current layout of the filmstrip. - */ - _currentLayout: string, - /** * Indicates whether the local video flip feature is disabled or not. */ @@ -189,9 +185,9 @@ export type Props = {| _raisedHand: boolean, /** - * Whether or not the stage filmstrip is disabled. + * Whether or not the current layout is stage filmstrip layout. */ - _stageFilmstripDisabled: boolean, + _stageFilmstripLayout: boolean, /** * Whether or not the participants are displayed on stage. @@ -200,6 +196,11 @@ export type Props = {| */ _stageParticipantsVisible: boolean, + /** + * The type of thumbnail to display. + */ + _thumbnailType: string, + /** * The video object position for the participant. */ @@ -447,15 +448,15 @@ class Thumbnail extends Component { */ _maybeSendScreenSharingIssueEvents(input) { const { - _currentLayout, _isAudioOnly, - _isScreenSharing + _isScreenSharing, + _thumbnailType } = this.props; const { displayMode } = this.state; - const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; + const isTileType = _thumbnailType === THUMBNAIL_TYPE.TILE; if (!(DISPLAY_VIDEO === displayMode) - && tileViewActive + && isTileType && _isScreenSharing && !_isAudioOnly) { sendAnalytics(createScreenSharingIssueEvent({ @@ -530,9 +531,9 @@ class Thumbnail extends Component { * @returns {void} */ _hidePopover() { - const { _currentLayout } = this.props; + const { _thumbnailType } = this.props; - if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) { + if (_thumbnailType === THUMBNAIL_TYPE.VERTICAL) { this.setState({ isHovered: false }); @@ -550,13 +551,13 @@ class Thumbnail extends Component { _getStyles(): Object { const { canPlayEventReceived } = this.state; const { - _currentLayout, _disableTileEnlargement, _height, _isFakeScreenShareParticipant, _isHidden, _isScreenSharing, _participant, + _thumbnailType, _videoObjectPosition, _videoTrack, _width, @@ -564,7 +565,7 @@ class Thumbnail extends Component { style } = this.props; - const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; + const isTileType = _thumbnailType === THUMBNAIL_TYPE.TILE; const jitsiVideoTrack = _videoTrack?.jitsiTrack; const track = jitsiVideoTrack?.track; const isPortraitVideo = ((track && track.getSettings()?.aspectRatio) || 1) < 1; @@ -587,7 +588,7 @@ class Thumbnail extends Component { } let videoStyles = null; - const doNotStretchVideo = (isPortraitVideo && tileViewActive) + const doNotStretchVideo = (isPortraitVideo && isTileType) || _disableTileEnlargement || _isScreenSharing; @@ -636,13 +637,13 @@ class Thumbnail extends Component { * @returns {void} */ _onClick() { - const { _participant, dispatch, _stageFilmstripDisabled } = this.props; + const { _participant, dispatch, _stageFilmstripLayout } = this.props; const { id, pinned } = _participant; - if (_stageFilmstripDisabled) { - dispatch(pinParticipant(pinned ? null : id)); - } else { + if (_stageFilmstripLayout) { dispatch(togglePinStageParticipant(id)); + } else { + dispatch(pinParticipant(pinned ? null : id)); } } @@ -790,8 +791,8 @@ class Thumbnail extends Component { const { _isDominantSpeakerDisabled, _participant, - _currentLayout, _raisedHand, + _thumbnailType, classes } = this.props; @@ -804,7 +805,7 @@ class Thumbnail extends Component { if (!_isDominantSpeakerDisabled && _participant?.dominantSpeaker) { className += ` ${classes.activeSpeaker} dominant-speaker`; } - if (_currentLayout !== LAYOUTS.TILE_VIEW && _participant?.pinned) { + if (_thumbnailType !== THUMBNAIL_TYPE.TILE && _participant?.pinned) { className += ' videoContainerFocused'; } @@ -902,16 +903,16 @@ class Thumbnail extends Component { _renderParticipant(local = false) { const { _audioTrack, - _currentLayout, _disableLocalVideoFlip, + _gifSrc, _isMobile, _isMobilePortrait, _isScreenSharing, _isTestModeEnabled, _localFlipX, _participant, + _thumbnailType, _videoTrack, - _gifSrc, classes, stageFilmstrip } = this.props; @@ -975,28 +976,28 @@ class Thumbnail extends Component {
+ showPopover = { this._showPopover } + thumbnailType = { _thumbnailType } />
+ participantId = { id } + thumbnailType = { _thumbnailType } />
{!_gifSrc && this._renderAvatar(styles.avatar) } { !local && ( @@ -1103,7 +1104,7 @@ function _mapStateToProps(state, ownProps): Object { } const _audioTrack = isLocal ? getLocalAudioTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantID); - const _currentLayout = stageFilmstrip ? LAYOUTS.TILE_VIEW : getCurrentLayout(state); + const _currentLayout = getCurrentLayout(state); let size = {}; let _isMobilePortrait = false; const { @@ -1116,10 +1117,12 @@ function _mapStateToProps(state, ownProps): Object { const { localFlipX } = state['features/base/settings']; const _isMobile = isMobileBrowser(); const activeParticipants = getActiveParticipantsIds(state); + const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip); - switch (_currentLayout) { - case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: - case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: { + + switch (tileType) { + case THUMBNAIL_TYPE.VERTICAL: + case THUMBNAIL_TYPE.HORIZONTAL: { const { horizontalViewDimensions = { local: {}, @@ -1133,7 +1136,7 @@ function _mapStateToProps(state, ownProps): Object { } = state['features/filmstrip']; const _verticalViewGrid = showGridInVerticalView(state); const { local, remote } - = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW + = tileType === THUMBNAIL_TYPE.VERTICAL ? verticalViewDimensions : horizontalViewDimensions; const { width, height } = (isLocal ? local : remote) ?? {}; @@ -1155,7 +1158,7 @@ function _mapStateToProps(state, ownProps): Object { break; } - case LAYOUTS.TILE_VIEW: { + case THUMBNAIL_TYPE.TILE: { const { thumbnailSize } = state['features/filmstrip'].tileViewDimensions; const { stageFilmstripDimensions = { @@ -1186,7 +1189,6 @@ function _mapStateToProps(state, ownProps): Object { return { _audioTrack, - _currentLayout, _defaultLocalDisplayName: defaultLocalDisplayName, _disableLocalVideoFlip: Boolean(disableLocalVideoFlip), _disableTileEnlargement: Boolean(disableTileEnlargement), @@ -1204,8 +1206,9 @@ function _mapStateToProps(state, ownProps): Object { _localFlipX: Boolean(localFlipX), _participant: participant, _raisedHand: hasRaisedHand(participant), - _stageFilmstripDisabled: !isStageFilmstripEnabled(state), - _stageParticipantsVisible: shouldDisplayStageFilmstrip(state, 1), + _stageFilmstripLayout: isStageFilmstripAvailable(state), + _stageParticipantsVisible: _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW, + _thumbnailType: tileType, _videoObjectPosition: getVideoObjectPosition(state, participant?.id), _videoTrack, ...size, diff --git a/react/features/filmstrip/components/web/ThumbnailBottomIndicators.js b/react/features/filmstrip/components/web/ThumbnailBottomIndicators.js index cfc5e24e69..78fab590d4 100644 --- a/react/features/filmstrip/components/web/ThumbnailBottomIndicators.js +++ b/react/features/filmstrip/components/web/ThumbnailBottomIndicators.js @@ -6,7 +6,7 @@ import { useSelector } from 'react-redux'; import { isDisplayNameVisible, isNameReadOnly } from '../../../base/config/functions.any'; import DisplayName from '../../../display-name/components/web/DisplayName'; -import { LAYOUTS } from '../../../video-layout'; +import { THUMBNAIL_TYPE } from '../../constants'; import StatusIndicators from './StatusIndicators'; @@ -14,11 +14,6 @@ declare var interfaceConfig: Object; type Props = { - /** - * The current layout of the filmstrip. - */ - currentLayout: string, - /** * Class name for indicators container. */ @@ -37,7 +32,12 @@ type Props = { /** * Whether or not to show the status indicators. */ - showStatusIndicators: string + showStatusIndicators: string, + + /** + * The type of thumbnail. + */ + thumbnailType: string } const useStyles = makeStyles(() => { @@ -61,10 +61,10 @@ const useStyles = makeStyles(() => { const ThumbnailBottomIndicators = ({ className, - currentLayout, local, participantId, - showStatusIndicators = true + showStatusIndicators = true, + thumbnailType }: Props) => { const styles = useStyles(); const _allowEditing = !useSelector(isNameReadOnly); @@ -77,17 +77,18 @@ const ThumbnailBottomIndicators = ({ audio = { true } moderator = { true } participantID = { participantId } - screenshare = { currentLayout === LAYOUTS.TILE_VIEW } /> + screenshare = { thumbnailType === THUMBNAIL_TYPE.TILE } + thumbnailType = { thumbnailType } /> } { _showDisplayName && ( + participantID = { participantId } + thumbnailType = { thumbnailType } /> ) } diff --git a/react/features/filmstrip/components/web/ThumbnailTopIndicators.js b/react/features/filmstrip/components/web/ThumbnailTopIndicators.js index 390018d215..14ebcff327 100644 --- a/react/features/filmstrip/components/web/ThumbnailTopIndicators.js +++ b/react/features/filmstrip/components/web/ThumbnailTopIndicators.js @@ -8,8 +8,7 @@ import { useSelector } from 'react-redux'; import { getSourceNameSignalingFeatureFlag } from '../../../base/config'; import { isMobileBrowser } from '../../../base/environment/utils'; import ConnectionIndicator from '../../../connection-indicator/components/web/ConnectionIndicator'; -import { LAYOUTS } from '../../../video-layout'; -import { STATS_POPOVER_POSITION } from '../../constants'; +import { STATS_POPOVER_POSITION, THUMBNAIL_TYPE } from '../../constants'; import { getIndicatorsTooltipPosition } from '../../functions.web'; import PinnedIndicator from './PinnedIndicator'; @@ -21,11 +20,6 @@ declare var interfaceConfig: Object; type Props = { - /** - * The current layout of the filmstrip. - */ - currentLayout: string, - /** * Hide popover callback. */ @@ -64,7 +58,12 @@ type Props = { /** * Show popover callback. */ - showPopover: Function + showPopover: Function, + + /** + * The type of thumbnail. + */ + thumbnailType: string } const useStyles = makeStyles(() => { @@ -80,7 +79,6 @@ const useStyles = makeStyles(() => { }); const ThumbnailTopIndicators = ({ - currentLayout, hidePopover, indicatorsClassName, isFakeScreenShareParticipant, @@ -88,7 +86,8 @@ const ThumbnailTopIndicators = ({ local, participantId, popoverVisible, - showPopover + showPopover, + thumbnailType }: Props) => { const styles = useStyles(); @@ -111,32 +110,34 @@ const ThumbnailTopIndicators = ({ enableStatsDisplay = { true } iconSize = { _indicatorIconSize } participantId = { participantId } - statsPopoverPosition = { STATS_POPOVER_POSITION[currentLayout] } /> + statsPopoverPosition = { STATS_POPOVER_POSITION[thumbnailType] } /> }
); } + const tooltipPosition = getIndicatorsTooltipPosition(thumbnailType); + return ( <>
+ tooltipPosition = { tooltipPosition } /> {!_connectionIndicatorDisabled && + statsPopoverPosition = { STATS_POPOVER_POSITION[thumbnailType] } /> } - {currentLayout !== LAYOUTS.TILE_VIEW && ( + tooltipPosition = { tooltipPosition } /> + {thumbnailType !== THUMBNAIL_TYPE.TILE && (
); diff --git a/react/features/filmstrip/components/web/VideoMenuTriggerButton.js b/react/features/filmstrip/components/web/VideoMenuTriggerButton.js index 70e65aab6f..e09237bf8f 100644 --- a/react/features/filmstrip/components/web/VideoMenuTriggerButton.js +++ b/react/features/filmstrip/components/web/VideoMenuTriggerButton.js @@ -6,11 +6,6 @@ import { LocalVideoMenuTriggerButton, RemoteVideoMenuTriggerButton } from '../.. type Props = { - /** - * The current layout of the filmstrip. - */ - currentLayout: string, - /** * Hide popover callback. */ @@ -36,6 +31,11 @@ type Props = { */ showPopover: Function, + /** + * The type of thumbnail. + */ + thumbnailType: string, + /** * Whether or not the component is visible. */ @@ -44,33 +44,33 @@ type Props = { // eslint-disable-next-line no-confusing-arrow const VideoMenuTriggerButton = ({ - currentLayout, hidePopover, local, participantId, popoverVisible, showPopover, + thumbnailType, visible }: Props) => local ? ( + showPopover = { showPopover } + thumbnailType = { thumbnailType } /> ) : ( + showPopover = { showPopover } + thumbnailType = { thumbnailType } /> ); diff --git a/react/features/filmstrip/constants.js b/react/features/filmstrip/constants.js index f99834247b..8f68b68781 100644 --- a/react/features/filmstrip/constants.js +++ b/react/features/filmstrip/constants.js @@ -1,7 +1,6 @@ // @flow import { BoxModel } from '../base/styles'; -import { LAYOUTS } from '../video-layout/constants'; /** * The size (height and width) of the small (not tile view) thumbnails. @@ -228,22 +227,31 @@ export const SHOW_TOOLBAR_CONTEXT_MENU_AFTER = 600; */ export const TILE_MARGIN = 10; +/** + * The types of thumbnails for filmstrip. + */ +export const THUMBNAIL_TYPE = { + TILE: 'TILE', + VERTICAL: 'VERTICAL', + HORIZONTAL: 'HORIZONTAL' +}; + /** * The popover position for the connection stats table. */ export const STATS_POPOVER_POSITION = { - [LAYOUTS.TILE_VIEW]: 'right-start', - [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left-start', - [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top-end' + [THUMBNAIL_TYPE.TILE]: 'right-start', + [THUMBNAIL_TYPE.VERTICAL]: 'left-start', + [THUMBNAIL_TYPE.HORIZONTAL]: 'top-end' }; /** * The tooltip position for the indicators on the thumbnail. */ export const INDICATORS_TOOLTIP_POSITION = { - [LAYOUTS.TILE_VIEW]: 'right', - [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left', - [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top' + [THUMBNAIL_TYPE.TILE]: 'right', + [THUMBNAIL_TYPE.VERTICAL]: 'left', + [THUMBNAIL_TYPE.HORIZONTAL]: 'top' }; /** diff --git a/react/features/filmstrip/functions.native.js b/react/features/filmstrip/functions.native.js index 586234cdd8..2a15878770 100644 --- a/react/features/filmstrip/functions.native.js +++ b/react/features/filmstrip/functions.native.js @@ -103,11 +103,11 @@ export function isReorderingEnabled(state) { } /** - * Whether the stage filmstrip is disabled or not. + * Whether the stage filmstrip is available or not. * * @param {Object} state - Redux state. * @returns {boolean} */ -export function isStageFilmstripEnabled() { +export function isStageFilmstripAvailable() { return false; } diff --git a/react/features/filmstrip/functions.web.js b/react/features/filmstrip/functions.web.js index 66115abe6c..2e744cf18b 100644 --- a/react/features/filmstrip/functions.web.js +++ b/react/features/filmstrip/functions.web.js @@ -35,6 +35,7 @@ import { INDICATORS_TOOLTIP_POSITION, SCROLL_SIZE, SQUARE_TILE_ASPECT_RATIO, + THUMBNAIL_TYPE, TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN, TILE_MIN_HEIGHT_LARGE, @@ -243,18 +244,16 @@ export function getNumberOfPartipantsForTileView(state) { * disabled. * * @param {Object} state - The redux store state. - * @param {boolean} stageFilmstrip - Whether the dimensions should be calculated for the stage filmstrip. * @returns {Object} - The dimensions. */ -export function calculateNonResponsiveTileViewDimensions(state, stageFilmstrip = false) { +export function calculateNonResponsiveTileViewDimensions(state) { const { clientHeight, clientWidth } = state['features/base/responsive-ui']; const { disableTileEnlargement } = state['features/base/config']; - const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state, stageFilmstrip); - const filmstripWidth = getVerticalViewMaxWidth(state); + const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state); const size = calculateThumbnailSizeForTileView({ columns: c, minVisibleRows, - clientWidth: clientWidth - (stageFilmstrip ? filmstripWidth : 0), + clientWidth, clientHeight, disableTileEnlargement, disableResponsiveTiles: true @@ -515,6 +514,7 @@ export function computeDisplayModeFromInput(input: Object) { canPlayEventReceived, isRemoteParticipant, stageParticipantsVisible, + stageFilmstrip, tileViewActive } = input; const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived); @@ -524,7 +524,7 @@ export function computeDisplayModeFromInput(input: Object) { } if (!tileViewActive && ((isScreenSharing && isRemoteParticipant) - || (stageParticipantsVisible && isActiveParticipant))) { + || (stageParticipantsVisible && isActiveParticipant && !stageFilmstrip))) { return DISPLAY_AVATAR; } else if (isCurrentlyOnLargeVideo && !tileViewActive) { // Display name is always and only displayed when user is on the stage @@ -556,7 +556,8 @@ export function getDisplayModeInput(props: Object, state: Object) { _isVideoPlayable, _participant, _stageParticipantsVisible, - _videoTrack + _videoTrack, + stageFilmstrip } = props; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; const { canPlayEventReceived } = state; @@ -574,6 +575,7 @@ export function getDisplayModeInput(props: Object, state: Object) { isScreenSharing: _isScreenSharing, isFakeScreenShareParticipant: _isFakeScreenShareParticipant, stageParticipantsVisible: _stageParticipantsVisible, + stageFilmstrip, videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream' }; } @@ -581,11 +583,11 @@ export function getDisplayModeInput(props: Object, state: Object) { /** * Gets the tooltip position for the thumbnail indicators. * - * @param {string} currentLayout - The current layout of the app. + * @param {string} thumbnailType - The current thumbnail type. * @returns {string} */ -export function getIndicatorsTooltipPosition(currentLayout: string) { - return INDICATORS_TOOLTIP_POSITION[currentLayout] || 'top'; +export function getIndicatorsTooltipPosition(thumbnailType: string) { + return INDICATORS_TOOLTIP_POSITION[thumbnailType] || 'top'; } /** @@ -599,7 +601,7 @@ export function isFilmstripResizable(state: Object) { const _currentLayout = getCurrentLayout(state); return !filmstrip?.disableResizable && !isMobileBrowser() - && _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW; + && (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW); } /** @@ -665,7 +667,8 @@ export function isFilmstripScrollVisible(state) { case LAYOUTS.TILE_VIEW: ({ hasScroll = false } = state['features/filmstrip'].tileViewDimensions); break; - case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: { + case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: + case LAYOUTS.STAGE_FILMSTRIP_VIEW: { ({ hasScroll = false } = state['features/filmstrip'].verticalViewDimensions); break; } @@ -703,21 +706,30 @@ export function getPinnedActiveParticipants(state) { } /** - * Get whether or not the stage filmstrip should be displayed. + * Get whether or not the stage filmstrip is available (enabled & can be used). * * @param {Object} state - Redux state. * @param {number} minParticipantCount - The min number of participants for the stage filmstrip * to be displayed. * @returns {boolean} */ -export function shouldDisplayStageFilmstrip(state, minParticipantCount = 2) { +export function isStageFilmstripAvailable(state, minParticipantCount = 0) { const { activeParticipants } = state['features/filmstrip']; const { remoteScreenShares } = state['features/video-layout']; - const currentLayout = getCurrentLayout(state); const sharedVideo = isSharingStatus(state['features/shared-video']?.status); return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo - && activeParticipants.length >= minParticipantCount && currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW; + && activeParticipants.length >= minParticipantCount; +} + +/** + * Get whether or not the stage filmstrip should be displayed. + * + * @param {Object} state - Redux state. + * @returns {boolean} + */ +export function shouldDisplayStageFilmstrip(state) { + return isStageFilmstripAvailable(state, 2); } /** @@ -731,3 +743,27 @@ export function isStageFilmstripEnabled(state) { return !(filmstrip?.disableStageFilmstrip ?? true) && interfaceConfig.VERTICAL_FILMSTRIP; } + +/** + * Gets the thumbnail type by filmstrip type. + * + * @param {string} currentLayout - Current app layout. + * @param {boolean} isStageFilmstrip - Whether the filmstrip is stage filmstrip or not. + * @returns {string} + */ +export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = false) { + switch (currentLayout) { + case LAYOUTS.TILE_VIEW: + return THUMBNAIL_TYPE.TILE; + case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: + return THUMBNAIL_TYPE.VERTICAL; + case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: + return THUMBNAIL_TYPE.HORIZONTAL; + case LAYOUTS.STAGE_FILMSTRIP_VIEW: + if (isStageFilmstrip) { + return THUMBNAIL_TYPE.TILE; + } + + return THUMBNAIL_TYPE.VERTICAL; + } +} diff --git a/react/features/filmstrip/middleware.web.js b/react/features/filmstrip/middleware.web.js index f5279333d8..6d4f75cb97 100644 --- a/react/features/filmstrip/middleware.web.js +++ b/react/features/filmstrip/middleware.web.js @@ -22,6 +22,7 @@ import { import { ADD_STAGE_PARTICIPANT, + CLEAR_STAGE_PARTICIPANTS, REMOVE_STAGE_PARTICIPANT, SET_MAX_STAGE_PARTICIPANTS, SET_USER_FILMSTRIP_WIDTH, @@ -42,10 +43,12 @@ import { import { isFilmstripResizable, updateRemoteParticipants, - updateRemoteParticipantsOnLeave + updateRemoteParticipantsOnLeave, + getActiveParticipantsIds, + getPinnedActiveParticipants, + isStageFilmstripAvailable } from './functions'; import './subscriber'; -import { getActiveParticipantsIds, getPinnedActiveParticipants, isStageFilmstripEnabled } from './functions.web'; /** * Map of timers. @@ -202,15 +205,15 @@ MiddlewareRegistry.register(store => next => action => { case DOMINANT_SPEAKER_CHANGED: { const { id } = action.participant; const state = store.getState(); - const stageFilmstrip = isStageFilmstripEnabled(state); - const currentLayout = getCurrentLayout(state); + const stageFilmstrip = isStageFilmstripAvailable(state); const local = getLocalParticipant(state); + const currentLayout = getCurrentLayout(state); - if (id === local.id) { + if (id === local.id || currentLayout === LAYOUTS.TILE_VIEW) { break; } - if (stageFilmstrip && currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) { + if (stageFilmstrip) { const isPinned = getPinnedActiveParticipants(state).some(p => p.participantId === id); store.dispatch(addStageParticipant(id, Boolean(isPinned))); @@ -276,7 +279,17 @@ MiddlewareRegistry.register(store => next => action => { } else { dispatch(addStageParticipant(participantId, true)); } + break; + } + case CLEAR_STAGE_PARTICIPANTS: { + const activeParticipants = getActiveParticipantsIds(store.getState()); + activeParticipants.forEach(pId => { + const tid = timers.get(pId); + + clearTimeout(tid); + timers.delete(pId); + }); } } diff --git a/react/features/filmstrip/reducer.js b/react/features/filmstrip/reducer.js index 8446f7f8d3..9898193a81 100644 --- a/react/features/filmstrip/reducer.js +++ b/react/features/filmstrip/reducer.js @@ -18,7 +18,8 @@ import { SET_VERTICAL_VIEW_DIMENSIONS, SET_VISIBLE_REMOTE_PARTICIPANTS, SET_VOLUME, - SET_MAX_STAGE_PARTICIPANTS + SET_MAX_STAGE_PARTICIPANTS, + CLEAR_STAGE_PARTICIPANTS } from './actionTypes'; const DEFAULT_STATE = { @@ -273,6 +274,12 @@ ReducerRegistry.register( maxStageParticipants: action.maxParticipants }; } + case CLEAR_STAGE_PARTICIPANTS: { + return { + ...state, + activeParticipants: [] + }; + } } return state; diff --git a/react/features/filmstrip/subscriber.web.js b/react/features/filmstrip/subscriber.web.js index 815fd50f67..261251ea85 100644 --- a/react/features/filmstrip/subscriber.web.js +++ b/react/features/filmstrip/subscriber.web.js @@ -1,7 +1,7 @@ // @flow import { isMobileBrowser } from '../base/environment/utils'; -import { getParticipantCountWithFake } from '../base/participants'; +import { getParticipantCountWithFake, pinParticipant } from '../base/participants'; import { StateListenerRegistry } from '../base/redux'; import { clientResized } from '../base/responsive-ui'; import { shouldHideSelfView } from '../base/settings'; @@ -17,6 +17,7 @@ import { setTileViewDimensions, setVerticalViewDimensions } from './actions'; +import { clearStageParticipants } from './actions.web'; import { ASPECT_RATIO_BREAKPOINT, DISPLAY_DRAWER_THRESHOLD @@ -24,7 +25,6 @@ import { import { isFilmstripResizable, isFilmstripScrollVisible, - shouldDisplayStageFilmstrip, updateRemoteParticipants } from './functions'; @@ -74,6 +74,12 @@ StateListenerRegistry.register( break; case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: store.dispatch(setVerticalViewDimensions()); + if (store.getState()['features/filmstrip'].activeParticipants.length > 1) { + store.dispatch(clearStageParticipants()); + } + break; + case LAYOUTS.STAGE_FILMSTRIP_VIEW: + store.dispatch(pinParticipant(null)); break; } }, { @@ -177,7 +183,7 @@ StateListenerRegistry.register( }; }, /* listener */(_, store) => { - if (shouldDisplayStageFilmstrip(store.getState())) { + if (getCurrentLayout(store.getState()) === LAYOUTS.STAGE_FILMSTRIP_VIEW) { store.dispatch(setStageFilmstripViewDimensions()); } }, { diff --git a/react/features/large-video/actions.any.js b/react/features/large-video/actions.any.js index 4bcd2bf7d3..b950175277 100644 --- a/react/features/large-video/actions.any.js +++ b/react/features/large-video/actions.any.js @@ -9,7 +9,8 @@ import { getPinnedParticipant, getRemoteParticipants } from '../base/participants'; -import { isStageFilmstripEnabled } from '../filmstrip/functions'; +import { isStageFilmstripAvailable } from '../filmstrip/functions'; +import { shouldDisplayStageFilmstrip } from '../filmstrip/functions.web'; import { SELECT_LARGE_VIDEO_PARTICIPANT, @@ -30,6 +31,10 @@ export function selectParticipantInLargeVideo(participant: ?string) { return (dispatch: Dispatch, getState: Function) => { const state = getState(); + if (shouldDisplayStageFilmstrip(state)) { + return; + } + // Keep Etherpad open. if (state['features/etherpad'].editing) { return; @@ -103,7 +108,7 @@ function _electLastVisibleRemoteVideo(tracks) { * @returns {(string|undefined)} */ function _electParticipantInLargeVideo(state) { - const stageFilmstrip = isStageFilmstripEnabled(state); + const stageFilmstrip = isStageFilmstripAvailable(state); let participant; if (!stageFilmstrip) { diff --git a/react/features/video-layout/constants.js b/react/features/video-layout/constants.js index 72488205fa..102fc5fa27 100644 --- a/react/features/video-layout/constants.js +++ b/react/features/video-layout/constants.js @@ -6,5 +6,6 @@ export const LAYOUTS = { HORIZONTAL_FILMSTRIP_VIEW: 'horizontal-filmstrip-view', TILE_VIEW: 'tile-view', - VERTICAL_FILMSTRIP_VIEW: 'vertical-filmstrip-view' + VERTICAL_FILMSTRIP_VIEW: 'vertical-filmstrip-view', + STAGE_FILMSTRIP_VIEW: 'stage-filmstrip-view' }; diff --git a/react/features/video-layout/functions.any.js b/react/features/video-layout/functions.any.js index 2b598bcebd..18df104b3d 100644 --- a/react/features/video-layout/functions.any.js +++ b/react/features/video-layout/functions.any.js @@ -7,6 +7,7 @@ import { getParticipantCount, pinParticipant } from '../base/participants'; +import { shouldDisplayStageFilmstrip } from '../filmstrip/functions.web'; import { isVideoPlaying } from '../shared-video/functions'; import { VIDEO_QUALITY_LEVELS } from '../video-quality/constants'; @@ -40,6 +41,10 @@ export function getCurrentLayout(state: Object) { if (shouldDisplayTileView(state)) { return LAYOUTS.TILE_VIEW; } else if (interfaceConfig.VERTICAL_FILMSTRIP) { + if (shouldDisplayStageFilmstrip(state)) { + return LAYOUTS.STAGE_FILMSTRIP_VIEW; + } + return LAYOUTS.VERTICAL_FILMSTRIP_VIEW; } diff --git a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js index e9da6ffb4e..0af2783094 100644 --- a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js +++ b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js @@ -18,8 +18,8 @@ import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actio import { getHideSelfView } from '../../../base/settings'; import { getLocalVideoTrack } from '../../../base/tracks'; import ConnectionIndicatorContent from '../../../connection-indicator/components/web/ConnectionIndicatorContent'; +import { THUMBNAIL_TYPE } from '../../../filmstrip'; import { isStageFilmstripEnabled } from '../../../filmstrip/functions.web'; -import { LAYOUTS } from '../../../video-layout'; import { renderConnectionStatus } from '../../actions.web'; import ConnectionStatusButton from './ConnectionStatusButton'; @@ -274,7 +274,7 @@ class LocalVideoMenuTriggerButton extends Component { * @returns {Props} */ function _mapStateToProps(state, ownProps) { - const { currentLayout } = ownProps; + const { thumbnailType } = ownProps; const localParticipant = getLocalParticipant(state); const { disableLocalVideoFlip, disableSelfViewSettings } = state['features/base/config']; const videoTrack = getLocalVideoTrack(state['features/base/tracks']); @@ -284,14 +284,14 @@ function _mapStateToProps(state, ownProps) { let _menuPosition; - switch (currentLayout) { - case LAYOUTS.TILE_VIEW: + switch (thumbnailType) { + case THUMBNAIL_TYPE.TILE: _menuPosition = 'left-start'; break; - case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: + case THUMBNAIL_TYPE.VERTICAL: _menuPosition = 'left-start'; break; - case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: + case THUMBNAIL_TYPE.HORIZONTAL: _menuPosition = 'top-start'; break; default: diff --git a/react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js b/react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js index 43c0a28361..2c00128aac 100644 --- a/react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js +++ b/react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js @@ -14,7 +14,7 @@ import { getParticipantById } from '../../../base/participants'; import { Popover } from '../../../base/popover'; import { connect } from '../../../base/redux'; import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions'; -import { LAYOUTS } from '../../../video-layout'; +import { THUMBNAIL_TYPE } from '../../../filmstrip'; import { renderConnectionStatus } from '../../actions.web'; import ParticipantContextMenu from './ParticipantContextMenu'; @@ -265,7 +265,7 @@ class RemoteVideoMenuTriggerButton extends Component { * @returns {Props} */ function _mapStateToProps(state, ownProps) { - const { participantID, currentLayout } = ownProps; + const { participantID, thumbnailType } = ownProps; let _remoteControlState = null; const participant = getParticipantById(state, participantID); const _participantDisplayName = participant?.name; @@ -291,14 +291,14 @@ function _mapStateToProps(state, ownProps) { let _menuPosition; - switch (currentLayout) { - case LAYOUTS.TILE_VIEW: + switch (thumbnailType) { + case THUMBNAIL_TYPE.TILE: _menuPosition = 'left-start'; break; - case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: + case THUMBNAIL_TYPE.VERTICAL: _menuPosition = 'left-end'; break; - case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: + case THUMBNAIL_TYPE.HORIZONTAL: _menuPosition = 'top'; break; default: