feat: Add screenshare filmstrip (#11714)

Add new screen share layout with resizable top panel
Only enable new layout in large meetings (min 50 participants - configurable)
This commit is contained in:
Robert Pintilii 2022-06-29 16:59:49 +03:00 committed by GitHub
parent bac1347961
commit 21cf7f23c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 740 additions and 139 deletions

View File

@ -1205,7 +1205,8 @@ var config = {
// 'transcribing', // 'transcribing',
// 'video-quality', // 'video-quality',
// 'insecure-room', // 'insecure-room',
// 'highlight-moment' // 'highlight-moment',
// 'top-panel-toggle'
// ] // ]
// }, // },
@ -1399,7 +1400,14 @@ var config = {
// // Disables the stage filmstrip // // Disables the stage filmstrip
// // (displaying multiple participants on stage besides the vertical filmstrip) // // (displaying multiple participants on stage besides the vertical filmstrip)
// disableStageFilmstrip: false // disableStageFilmstrip: false,
// // Disables the top panel (only shown when a user is sharing their screen).
// disableTopPanel: false,
// // The minimum number of participants that must be in the call for
// // the top panel layout to be used.
// minParticipantCountForTopPanel: 50
// }, // },
// Tile view related config options. // Tile view related config options.

View File

@ -1033,6 +1033,7 @@
"termsView": { "termsView": {
"header": "Terms" "header": "Terms"
}, },
"toggleTopPanelLabel": "Toggle top panel",
"toolbar": { "toolbar": {
"Settings": "Settings", "Settings": "Settings",
"accessibilityLabel": { "accessibilityLabel": {

12
package-lock.json generated
View File

@ -7888,9 +7888,9 @@
"integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA==" "integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.3", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@ -26086,9 +26086,9 @@
"integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA==" "integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
}, },
"debug": { "debug": {
"version": "4.3.3", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": { "requires": {
"ms": "2.1.2" "ms": "2.1.2"
} }

View File

@ -8,6 +8,7 @@ export const CONFERENCE_INFO = {
'e2ee', 'e2ee',
'transcribing', 'transcribing',
'video-quality', 'video-quality',
'insecure-room' 'insecure-room',
'top-panel-toggle'
] ]
}; };

View File

@ -11,7 +11,7 @@ import { translate } from '../../../base/i18n';
import { connect as reactReduxConnect } from '../../../base/redux'; import { connect as reactReduxConnect } from '../../../base/redux';
import { setColorAlpha } from '../../../base/util'; import { setColorAlpha } from '../../../base/util';
import { Chat } from '../../../chat'; import { Chat } from '../../../chat';
import { MainFilmstrip, StageFilmstrip } from '../../../filmstrip'; import { MainFilmstrip, StageFilmstrip, ScreenshareFilmstrip } from '../../../filmstrip';
import { CalleeInfoContainer } from '../../../invite'; import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video'; import { LargeVideo } from '../../../large-video';
import { LobbyScreen } from '../../../lobby'; import { LobbyScreen } from '../../../lobby';
@ -239,6 +239,7 @@ class Conference extends AbstractConference<Props, *> {
{ {
_showPrejoin || _showLobby || (<> _showPrejoin || _showLobby || (<>
<StageFilmstrip /> <StageFilmstrip />
<ScreenshareFilmstrip />
<MainFilmstrip /> <MainFilmstrip />
</>) </>)
} }

View File

@ -20,6 +20,7 @@ import InsecureRoomNameLabel from './InsecureRoomNameLabel';
import ParticipantsCount from './ParticipantsCount'; import ParticipantsCount from './ParticipantsCount';
import RaisedHandsCountLabel from './RaisedHandsCountLabel'; import RaisedHandsCountLabel from './RaisedHandsCountLabel';
import SubjectText from './SubjectText'; import SubjectText from './SubjectText';
import ToggleTopPanelLabel from './ToggleTopPanelLabel';
/** /**
* The type of the React {@code Component} props of {@link Subject}. * The type of the React {@code Component} props of {@link Subject}.
@ -82,6 +83,10 @@ const COMPONENTS = [
{ {
Component: InsecureRoomNameLabel, Component: InsecureRoomNameLabel,
id: 'insecure-room' id: 'insecure-room'
},
{
Component: ToggleTopPanelLabel,
id: 'top-panel-toggle'
} }
]; ];

View File

@ -0,0 +1,31 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
// @ts-ignore
import { IconMenuDown } from '../../../base/icons';
// @ts-ignore
import { Label } from '../../../base/label';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
// @ts-ignore
import { setTopPanelVisible } from '../../../filmstrip/actions.web';
const ToggleTopPanelLabel = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const topPanelHidden = !useSelector((state: any) => state['features/filmstrip'].topPanelVisible);
const onClick = useCallback(() => {
dispatch(setTopPanelVisible(true));
}, []);
return topPanelHidden && (<Tooltip
content={t('toggleTopPanelLabel') }
position = { 'bottom' }>
<Label
icon={IconMenuDown}
onClick = { onClick }/>
</Tooltip>);
};
export default ToggleTopPanelLabel;

View File

@ -92,6 +92,15 @@ export const SET_VOLUME = 'SET_VOLUME';
*/ */
export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS'; export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS';
/**
* The type of action which sets the height for the top panel filmstrip.
* {
* type: SET_FILMSTRIP_HEIGHT,
* height: number
* }
*/
export const SET_FILMSTRIP_HEIGHT = 'SET_FILMSTRIP_HEIGHT';
/** /**
* The type of action which sets the width for the vertical filmstrip. * The type of action which sets the width for the vertical filmstrip.
* { * {
@ -101,6 +110,15 @@ export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS'
*/ */
export const SET_FILMSTRIP_WIDTH = 'SET_FILMSTRIP_WIDTH'; export const SET_FILMSTRIP_WIDTH = 'SET_FILMSTRIP_WIDTH';
/**
* The type of action which sets the height for the top panel filmstrip (user resized).
* {
* type: SET_USER_FILMSTRIP_HEIGHT,
* height: number
* }
*/
export const SET_USER_FILMSTRIP_HEIGHT = 'SET_USER_FILMSTRIP_HEIGHT';
/** /**
* The type of action which sets the width for the vertical filmstrip (user resized). * The type of action which sets the width for the vertical filmstrip (user resized).
* { * {
@ -187,3 +205,19 @@ export const TOGGLE_PIN_STAGE_PARTICIPANT = 'TOGGLE_PIN_STAGE_PARTICIPANT';
* } * }
*/ */
export const CLEAR_STAGE_PARTICIPANTS = 'CLEAR_STAGE_PARTICIPANTS'; export const CLEAR_STAGE_PARTICIPANTS = 'CLEAR_STAGE_PARTICIPANTS';
/**
* The type of Redux action which sets the dimensions of the screenshare tile.
* {
* type: SET_SCREENSHARING_TILE_DIMENSIONS
* }
*/
export const SET_SCREENSHARING_TILE_DIMENSIONS = 'SET_SCREENSHARING_TILE_DIMENSIONS';
/**
* The type of Redux action which sets the visibility of the top panel.
* {
* type: SET_TOP_PANEL_VISIBILITY
* }
*/
export const SET_TOP_PANEL_VISIBILITY = 'SET_TOP_PANEL_VISIBILITY';

View File

@ -25,7 +25,11 @@ import {
SET_VOLUME, SET_VOLUME,
SET_MAX_STAGE_PARTICIPANTS, SET_MAX_STAGE_PARTICIPANTS,
TOGGLE_PIN_STAGE_PARTICIPANT, TOGGLE_PIN_STAGE_PARTICIPANT,
CLEAR_STAGE_PARTICIPANTS CLEAR_STAGE_PARTICIPANTS,
SET_SCREENSHARING_TILE_DIMENSIONS,
SET_USER_FILMSTRIP_HEIGHT,
SET_FILMSTRIP_HEIGHT,
SET_TOP_PANEL_VISIBILITY
} from './actionTypes'; } from './actionTypes';
import { import {
HORIZONTAL_FILMSTRIP_MARGIN, HORIZONTAL_FILMSTRIP_MARGIN,
@ -33,11 +37,13 @@ import {
SCROLL_SIZE, SCROLL_SIZE,
STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER, STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER,
TILE_HORIZONTAL_MARGIN, TILE_HORIZONTAL_MARGIN,
TILE_MIN_HEIGHT_SMALL,
TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN, TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN,
TILE_VERTICAL_MARGIN, TILE_VERTICAL_MARGIN,
TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES, TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
TILE_VIEW_GRID_HORIZONTAL_MARGIN, TILE_VIEW_GRID_HORIZONTAL_MARGIN,
TILE_VIEW_GRID_VERTICAL_MARGIN, TILE_VIEW_GRID_VERTICAL_MARGIN,
TOP_FILMSTRIP_HEIGHT,
VERTICAL_FILMSTRIP_VERTICAL_MARGIN VERTICAL_FILMSTRIP_VERTICAL_MARGIN
} from './constants'; } from './constants';
import { import {
@ -48,6 +54,7 @@ import {
getNumberOfPartipantsForTileView, getNumberOfPartipantsForTileView,
getVerticalViewMaxWidth, getVerticalViewMaxWidth,
isFilmstripResizable, isFilmstripResizable,
isStageFilmstripTopPanel,
showGridInVerticalView showGridInVerticalView
} from './functions'; } from './functions';
import { isStageFilmstripAvailable } from './functions.web'; import { isStageFilmstripAvailable } from './functions.web';
@ -270,7 +277,7 @@ export function setStageFilmstripViewDimensions() {
const { const {
tileView = {} tileView = {}
} = state['features/base/config']; } = state['features/base/config'];
const { visible } = state['features/filmstrip']; const { visible, topPanelHeight } = state['features/filmstrip'];
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0; const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
const { numberOfVisibleTiles = MAX_ACTIVE_PARTICIPANTS } = tileView; const { numberOfVisibleTiles = MAX_ACTIVE_PARTICIPANTS } = tileView;
const numberOfParticipants = state['features/filmstrip'].activeParticipants.length; const numberOfParticipants = state['features/filmstrip'].activeParticipants.length;
@ -280,6 +287,7 @@ export function setStageFilmstripViewDimensions() {
disableResponsiveTiles: false, disableResponsiveTiles: false,
disableTileEnlargement: false disableTileEnlargement: false
}); });
const topPanel = isStageFilmstripTopPanel(state);
const { const {
height, height,
@ -288,12 +296,13 @@ export function setStageFilmstripViewDimensions() {
rows rows
} = calculateResponsiveTileViewDimensions({ } = calculateResponsiveTileViewDimensions({
clientWidth: availableWidth, clientWidth: availableWidth,
clientHeight, clientHeight: topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : clientHeight,
disableTileEnlargement: false, disableTileEnlargement: false,
maxColumns, maxColumns,
noHorizontalContainerMargin: verticalWidth > 0, noHorizontalContainerMargin: verticalWidth > 0,
numberOfParticipants, numberOfParticipants,
numberOfVisibleTiles numberOfVisibleTiles,
minTileHeight: topPanel ? TILE_MIN_HEIGHT_SMALL : null
}); });
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height); const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
const hasScroll = clientHeight < thumbnailsTotalHeight; const hasScroll = clientHeight < thumbnailsTotalHeight;
@ -368,6 +377,22 @@ export function setVolume(participantId: string, volume: number) {
}; };
} }
/**
* Sets the top filmstrip's height.
*
* @param {number} height - The new height of the filmstrip.
* @returns {{
* type: SET_FILMSTRIP_HEIGHT,
* height: number
* }}
*/
export function setFilmstripHeight(height: number) {
return {
type: SET_FILMSTRIP_HEIGHT,
height
};
}
/** /**
* Sets the filmstrip's width. * Sets the filmstrip's width.
* *
@ -384,6 +409,22 @@ export function setFilmstripWidth(width: number) {
}; };
} }
/**
* Sets the filmstrip's height and the user preferred height.
*
* @param {number} height - The new height of the filmstrip.
* @returns {{
* type: SET_USER_FILMSTRIP_WIDTH,
* height: number
* }}
*/
export function setUserFilmstripHeight(height: number) {
return {
type: SET_USER_FILMSTRIP_HEIGHT,
height
};
}
/** /**
* Sets the filmstrip's width and the user preferred width. * Sets the filmstrip's width and the user preferred width.
* *
@ -490,3 +531,45 @@ export function clearStageParticipants() {
type: CLEAR_STAGE_PARTICIPANTS type: CLEAR_STAGE_PARTICIPANTS
}; };
} }
/**
* Set the screensharing tile dimensions.
*
* @returns {Object}
*/
export function setScreensharingTileDimensions() {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { visible, topPanelHeight, topPanelVisible } = state['features/filmstrip'];
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
const availableWidth = clientWidth - verticalWidth;
const topPanel = isStageFilmstripTopPanel(state) && topPanelVisible;
const availableHeight = clientHeight - (topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : 0);
dispatch({
type: SET_SCREENSHARING_TILE_DIMENSIONS,
dimensions: {
filmstripHeight: availableHeight,
filmstripWidth: availableWidth,
thumbnailSize: {
width: availableWidth - TILE_HORIZONTAL_MARGIN,
height: availableHeight - TILE_VERTICAL_MARGIN
}
}
});
};
}
/**
* Sets the visibility of the top panel.
*
* @param {boolean} visible - Whether it should be visible or not.
* @returns {Object}
*/
export function setTopPanelVisible(visible) {
return {
type: SET_TOP_PANEL_VISIBILITY,
visible
};
}

View File

@ -23,20 +23,26 @@ import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.we
import { getCurrentLayout, LAYOUTS } from '../../../video-layout'; import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { import {
setFilmstripVisible, setFilmstripVisible,
setVisibleRemoteParticipants, setUserFilmstripHeight,
setUserFilmstripWidth, setUserFilmstripWidth,
setUserIsResizing setUserIsResizing,
setTopPanelVisible,
setVisibleRemoteParticipants
} from '../../actions'; } from '../../actions';
import { import {
ASPECT_RATIO_BREAKPOINT, ASPECT_RATIO_BREAKPOINT,
DEFAULT_FILMSTRIP_WIDTH, DEFAULT_FILMSTRIP_WIDTH,
FILMSTRIP_TYPE,
MIN_STAGE_VIEW_HEIGHT,
MIN_STAGE_VIEW_WIDTH, MIN_STAGE_VIEW_WIDTH,
TILE_HORIZONTAL_MARGIN, TILE_HORIZONTAL_MARGIN,
TILE_VERTICAL_MARGIN TILE_VERTICAL_MARGIN,
TOP_FILMSTRIP_HEIGHT
} from '../../constants'; } from '../../constants';
import { import {
getVerticalViewMaxWidth, getVerticalViewMaxWidth,
shouldRemoteVideosBeVisible shouldRemoteVideosBeVisible,
isStageFilmstripTopPanel
} from '../../functions'; } from '../../functions';
import AudioTracksContainer from './AudioTracksContainer'; import AudioTracksContainer from './AudioTracksContainer';
@ -112,11 +118,21 @@ type Props = {
*/ */
_localScreenShare: Object, _localScreenShare: Object,
/**
* Whether or not the filmstrip videos should currently be displayed.
*/
_mainFilmstripVisible: boolean,
/** /**
* The maximum width of the vertical filmstrip. * The maximum width of the vertical filmstrip.
*/ */
_maxFilmstripWidth: number, _maxFilmstripWidth: number,
/**
* The maximum height of the top panel.
*/
_maxTopPanelHeight: number,
/** /**
* The participants in the call. * The participants in the call.
*/ */
@ -137,11 +153,6 @@ type Props = {
*/ */
_rows: number, _rows: number,
/**
* Whether or not this is the stage filmstrip.
*/
_stageFilmstrip: boolean,
/** /**
* The height of the thumbnail. * The height of the thumbnail.
*/ */
@ -157,6 +168,26 @@ type Props = {
*/ */
_thumbnailsReordered: Boolean, _thumbnailsReordered: Boolean,
/**
* Whether or not the filmstrip is top panel.
*/
_topPanelFilmstrip: boolean,
/**
* The max height of the top panel.
*/
_topPanelMaxHeight: number,
/**
* The height of the top panel (user resized).
*/
_topPanelHeight: ?number,
/**
* Whether or not the top panel is visible.
*/
_topPanelVisible: boolean,
/** /**
* The width of the vertical filmstrip (user resized). * The width of the vertical filmstrip (user resized).
*/ */
@ -182,11 +213,6 @@ type Props = {
*/ */
_videosClassName: string, _videosClassName: string,
/**
* Whether or not the filmstrip videos should currently be displayed.
*/
_visible: boolean,
/** /**
* An object containing the CSS classes. * An object containing the CSS classes.
*/ */
@ -197,6 +223,11 @@ type Props = {
*/ */
dispatch: Dispatch<any>, dispatch: Dispatch<any>,
/**
* The type of filmstrip to be displayed.
*/
filmstripType: string,
/** /**
* Invoked to obtain translated strings. * Invoked to obtain translated strings.
*/ */
@ -218,7 +249,12 @@ type State = {
/** /**
* Initial filmstrip width on drag handle mouse down. * Initial filmstrip width on drag handle mouse down.
*/ */
dragFilmstripWidth: ?number dragFilmstripWidth: ?number,
/**
* Initial top panel height on drag handle mouse down.
*/
dragFilmstripHeight: ?number
} }
/** /**
@ -307,25 +343,45 @@ class Filmstrip extends PureComponent <Props, State> {
_currentLayout, _currentLayout,
_disableSelfView, _disableSelfView,
_localScreenShare, _localScreenShare,
_mainFilmstripVisible,
_resizableFilmstrip, _resizableFilmstrip,
_stageFilmstrip, _topPanelFilmstrip,
_visible, _topPanelMaxHeight,
_topPanelVisible,
_verticalViewBackground, _verticalViewBackground,
_verticalViewGrid, _verticalViewGrid,
_verticalViewMaxWidth, _verticalViewMaxWidth,
classes classes,
filmstripType
} = this.props; } = this.props;
const { isMouseDown } = this.state; const { isMouseDown } = this.state;
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && _stageFilmstrip) { if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.STAGE) {
if (_visible) { if (_topPanelFilmstrip) {
filmstripStyle.maxHeight = `${_topPanelMaxHeight}px`;
filmstripStyle.zIndex = 1;
if (!_topPanelVisible) {
filmstripStyle.top = `-${_topPanelMaxHeight}px`;
}
}
if (_mainFilmstripVisible) {
filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`; filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
} }
} else if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
if (_mainFilmstripVisible) {
filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
}
if (_topPanelVisible) {
filmstripStyle.maxHeight = `calc(100% - ${_topPanelMaxHeight}px)`;
}
filmstripStyle.bottom = 0;
filmstripStyle.top = 'auto';
} else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW } else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|| (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && !_stageFilmstrip)) { || (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.MAIN)) {
filmstripStyle.maxWidth = _verticalViewMaxWidth; filmstripStyle.maxWidth = _verticalViewMaxWidth;
if (!_visible) { if (!_mainFilmstripVisible) {
filmstripStyle.right = `-${filmstripStyle.maxWidth}px`; filmstripStyle.right = `-${filmstripStyle.maxWidth}px`;
} }
} }
@ -333,14 +389,17 @@ class Filmstrip extends PureComponent <Props, State> {
let toolbar = null; let toolbar = null;
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
&& _currentLayout !== LAYOUTS.TILE_VIEW && !_stageFilmstrip) { && _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
toolbar = this._renderToggleButton(); toolbar = this._renderToggleButton();
} }
const filmstrip = (<> const filmstrip = (<>
<div <div
className = { clsx(this.props._videosClassName, className = { clsx(this.props._videosClassName,
!tileViewActive && !_stageFilmstrip && !_resizableFilmstrip && 'filmstrip-hover', !tileViewActive && (filmstripType === FILMSTRIP_TYPE.MAIN
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))
&& !_resizableFilmstrip && 'filmstrip-hover',
_verticalViewGrid && 'vertical-view-grid') } _verticalViewGrid && 'vertical-view-grid') }
id = 'remoteVideos'> id = 'remoteVideos'>
{!_disableSelfView && !_verticalViewGrid && ( {!_disableSelfView && !_verticalViewGrid && (
@ -348,8 +407,10 @@ class Filmstrip extends PureComponent <Props, State> {
className = 'filmstrip__videos' className = 'filmstrip__videos'
id = 'filmstripLocalVideo'> id = 'filmstripLocalVideo'>
{ {
!tileViewActive && !_stageFilmstrip && <div id = 'filmstripLocalVideoThumbnail'> !tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN
&& <div id = 'filmstripLocalVideoThumbnail'>
<Thumbnail <Thumbnail
filmstripType = { FILMSTRIP_TYPE.MAIN }
key = 'local' /> key = 'local' />
</div> </div>
} }
@ -361,10 +422,9 @@ class Filmstrip extends PureComponent <Props, State> {
id = 'filmstripLocalScreenShare'> id = 'filmstripLocalScreenShare'>
<div id = 'filmstripLocalScreenShareThumbnail'> <div id = 'filmstripLocalScreenShareThumbnail'>
{ {
!tileViewActive && !_stageFilmstrip && <Thumbnail !tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN && <Thumbnail
key = 'localScreenShare' key = 'localScreenShare'
participantID = { _localScreenShare.id } /> participantID = { _localScreenShare.id } />
} }
</div> </div>
</div> </div>
@ -385,11 +445,14 @@ class Filmstrip extends PureComponent <Props, State> {
style = { filmstripStyle }> style = { filmstripStyle }>
{ toolbar } { toolbar }
{_resizableFilmstrip {_resizableFilmstrip
? <div className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer) }> ? <div
className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer,
_topPanelFilmstrip && 'top-panel-filmstrip') }>
<div <div
className = { clsx('dragHandleContainer', className = { clsx('dragHandleContainer',
classes.dragHandleContainer, classes.dragHandleContainer,
isMouseDown && 'visible') isMouseDown && 'visible',
_topPanelFilmstrip && 'top-panel')
} }
onMouseDown = { this._onDragHandleMouseDown }> onMouseDown = { this._onDragHandleMouseDown }>
<div className = { clsx(classes.dragHandle, 'dragHandle') } /> <div className = { clsx(classes.dragHandle, 'dragHandle') } />
@ -412,10 +475,13 @@ class Filmstrip extends PureComponent <Props, State> {
* @returns {void} * @returns {void}
*/ */
_onDragHandleMouseDown(e) { _onDragHandleMouseDown(e) {
const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props;
this.setState({ this.setState({
isMouseDown: true, isMouseDown: true,
mousePosition: e.clientX, mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX,
dragFilmstripWidth: this.props._verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH dragFilmstripWidth: _verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH,
dragFilmstripHeight: _topPanelHeight || TOP_FILMSTRIP_HEIGHT
}); });
this.props.dispatch(setUserIsResizing(true)); this.props.dispatch(setUserIsResizing(true));
} }
@ -446,16 +512,36 @@ class Filmstrip extends PureComponent <Props, State> {
*/ */
_onFilmstripResize(e) { _onFilmstripResize(e) {
if (this.state.isMouseDown) { if (this.state.isMouseDown) {
const { dispatch, _verticalFilmstripWidth, _maxFilmstripWidth } = this.props; const {
const { dragFilmstripWidth, mousePosition } = this.state; dispatch,
const diff = mousePosition - e.clientX; _verticalFilmstripWidth,
const width = Math.max( _maxFilmstripWidth,
Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth), _topPanelHeight,
DEFAULT_FILMSTRIP_WIDTH _maxTopPanelHeight,
); _topPanelFilmstrip
} = this.props;
const { dragFilmstripWidth, dragFilmstripHeight, mousePosition } = this.state;
if (width !== _verticalFilmstripWidth) { if (_topPanelFilmstrip) {
dispatch(setUserFilmstripWidth(width)); const diff = e.clientY - mousePosition;
const height = Math.max(
Math.min(dragFilmstripHeight + diff, _maxTopPanelHeight),
TOP_FILMSTRIP_HEIGHT
);
if (height !== _topPanelHeight) {
dispatch(setUserFilmstripHeight(height));
}
} else {
const diff = mousePosition - e.clientX;
const width = Math.max(
Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth),
DEFAULT_FILMSTRIP_WIDTH
);
if (width !== _verticalFilmstripWidth) {
dispatch(setUserFilmstripWidth(width));
}
} }
} }
} }
@ -495,7 +581,7 @@ class Filmstrip extends PureComponent <Props, State> {
* @returns {void} * @returns {void}
*/ */
_onTabIn() { _onTabIn() {
if (!this.props._isToolboxVisible && this.props._visible) { if (!this.props._isToolboxVisible && this.props._mainFilmstripVisible) {
this.props.dispatch(showToolbox()); this.props.dispatch(showToolbox());
} }
} }
@ -605,10 +691,10 @@ class Filmstrip extends PureComponent <Props, State> {
_remoteParticipantsLength, _remoteParticipantsLength,
_resizableFilmstrip, _resizableFilmstrip,
_rows, _rows,
_stageFilmstrip,
_thumbnailHeight, _thumbnailHeight,
_thumbnailWidth, _thumbnailWidth,
_verticalViewGrid _verticalViewGrid,
filmstripType
} = this.props; } = this.props;
if (!_thumbnailWidth || isNaN(_thumbnailWidth) || !_thumbnailHeight if (!_thumbnailWidth || isNaN(_thumbnailWidth) || !_thumbnailHeight
@ -617,7 +703,7 @@ class Filmstrip extends PureComponent <Props, State> {
return null; return null;
} }
if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || _stageFilmstrip) { if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || filmstripType !== FILMSTRIP_TYPE.MAIN) {
return ( return (
<FixedSizeGrid <FixedSizeGrid
className = 'filmstrip__videos remote-videos' className = 'filmstrip__videos remote-videos'
@ -626,7 +712,7 @@ class Filmstrip extends PureComponent <Props, State> {
height = { _filmstripHeight } height = { _filmstripHeight }
initialScrollLeft = { 0 } initialScrollLeft = { 0 }
initialScrollTop = { 0 } initialScrollTop = { 0 }
itemData = {{ stageFilmstrip: _stageFilmstrip }} itemData = {{ filmstripType }}
itemKey = { this._gridItemKey } itemKey = { this._gridItemKey }
onItemsRendered = { this._onGridItemsRendered } onItemsRendered = { this._onGridItemsRendered }
overscanRowCount = { 1 } overscanRowCount = { 1 }
@ -694,7 +780,11 @@ class Filmstrip extends PureComponent <Props, State> {
* @returns {void} * @returns {void}
*/ */
_doToggleFilmstrip() { _doToggleFilmstrip() {
this.props.dispatch(setFilmstripVisible(!this.props._visible)); const { dispatch, _mainFilmstripVisible, _topPanelFilmstrip, _topPanelVisible } = this.props;
_topPanelFilmstrip
? dispatch(setTopPanelVisible(!_topPanelVisible))
: dispatch(setFilmstripVisible(!_mainFilmstripVisible));
} }
_onShortcutToggleFilmstrip: () => void; _onShortcutToggleFilmstrip: () => void;
@ -710,7 +800,7 @@ class Filmstrip extends PureComponent <Props, State> {
sendAnalytics(createShortcutEvent( sendAnalytics(createShortcutEvent(
'toggle.filmstrip', 'toggle.filmstrip',
{ {
enable: this.props._visible enable: this.props._mainFilmstripVisible
})); }));
this._doToggleFilmstrip(); this._doToggleFilmstrip();
@ -729,7 +819,7 @@ class Filmstrip extends PureComponent <Props, State> {
sendAnalytics(createToolbarEvent( sendAnalytics(createToolbarEvent(
'toggle.filmstrip.button', 'toggle.filmstrip.button',
{ {
enable: this.props._visible enable: this.props._mainFilmstripVisible
})); }));
this._doToggleFilmstrip(); this._doToggleFilmstrip();
@ -758,8 +848,15 @@ class Filmstrip extends PureComponent <Props, State> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderToggleButton() { _renderToggleButton() {
const icon = this.props._visible ? IconMenuDown : IconMenuUp; const {
const { t, classes, _isVerticalFilmstrip } = this.props; t,
classes,
_isVerticalFilmstrip,
_mainFilmstripVisible,
_topPanelFilmstrip,
_topPanelVisible
} = this.props;
const icon = (_topPanelFilmstrip ? _topPanelVisible : _mainFilmstripVisible) ? IconMenuDown : IconMenuUp;
const actions = isMobileBrowser() const actions = isMobileBrowser()
? { onTouchStart: this._onToggleButtonTouch } ? { onTouchStart: this._onToggleButtonTouch }
: { onClick: this._onToolbarToggleFilmstrip }; : { onClick: this._onToolbarToggleFilmstrip };
@ -768,9 +865,11 @@ class Filmstrip extends PureComponent <Props, State> {
<div <div
className = { clsx(classes.toggleFilmstripContainer, className = { clsx(classes.toggleFilmstripContainer,
_isVerticalFilmstrip && classes.toggleVerticalFilmstripContainer, _isVerticalFilmstrip && classes.toggleVerticalFilmstripContainer,
_topPanelFilmstrip && classes.toggleTopPanelContainer,
_topPanelFilmstrip && !_topPanelVisible && classes.toggleTopPanelContainerHidden,
'toggleFilmstripContainer') }> 'toggleFilmstripContainer') }>
<button <button
aria-expanded = { this.props._visible } aria-expanded = { this.props._mainFilmstripVisible }
aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') } aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') }
className = { classes.toggleFilmstripButton } className = { classes.toggleFilmstripButton }
id = 'toggleFilmstripButton' id = 'toggleFilmstripButton'
@ -795,32 +894,38 @@ class Filmstrip extends PureComponent <Props, State> {
* @returns {Props} * @returns {Props}
*/ */
function _mapStateToProps(state, ownProps) { function _mapStateToProps(state, ownProps) {
const { _hasScroll = false } = ownProps; const { _hasScroll = false, filmstripType, _topPanelFilmstrip, _remoteParticipants } = ownProps;
const toolbarButtons = getToolbarButtons(state); const toolbarButtons = getToolbarButtons(state);
const { testing = {}, iAmRecorder } = state['features/base/config']; const { testing = {}, iAmRecorder } = state['features/base/config'];
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true; const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
const { visible, width: verticalFilmstripWidth } = state['features/filmstrip']; const { topPanelHeight, topPanelVisible, visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
const { localScreenShare } = state['features/base/participants']; const { localScreenShare } = state['features/base/participants'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length; const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state); const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
const { isOpen: shiftRight } = state['features/chat']; const { isOpen: shiftRight } = state['features/chat'];
const disableSelfView = shouldHideSelfView(state); const disableSelfView = shouldHideSelfView(state);
const { clientWidth } = state['features/base/responsive-ui']; const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
const collapseTileView = reduceHeight const collapseTileView = reduceHeight
&& isMobileBrowser() && isMobileBrowser()
&& clientWidth <= ASPECT_RATIO_BREAKPOINT; && clientWidth <= ASPECT_RATIO_BREAKPOINT;
const shouldReduceHeight = reduceHeight && isMobileBrowser(); const shouldReduceHeight = reduceHeight && isMobileBrowser();
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}${_hasScroll ? ' has-scroll' : ''}`; let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
if (_topPanelFilmstrip) {
isVisible = _topPanelVisible;
}
const videosClassName = `filmstrip__videos${isVisible ? '' : ' hidden'}${_hasScroll ? ' has-scroll' : ''}`;
const className = `${remoteVideosVisible || ownProps._verticalViewGrid ? '' : 'hide-videos'} ${ const className = `${remoteVideosVisible || ownProps._verticalViewGrid ? '' : 'hide-videos'} ${
shouldReduceHeight ? 'reduce-height' : '' shouldReduceHeight ? 'reduce-height' : ''
} ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim(); } ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${isVisible ? '' : 'hidden'}`.trim();
const _currentLayout = getCurrentLayout(state); const _currentLayout = getCurrentLayout(state);
const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|| (!ownProps._stageFilmstrip && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW); || (filmstripType === FILMSTRIP_TYPE.MAIN && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
return { return {
_className: className, _className: className,
@ -833,8 +938,14 @@ function _mapStateToProps(state, ownProps) {
_isToolboxVisible: isToolboxVisible(state), _isToolboxVisible: isToolboxVisible(state),
_isVerticalFilmstrip, _isVerticalFilmstrip,
_localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare, _localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare,
_mainFilmstripVisible: visible,
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH, _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
_remoteParticipantsLength: _remoteParticipants.length,
_thumbnailsReordered: enableThumbnailReordering, _thumbnailsReordered: enableThumbnailReordering,
_topPanelHeight: topPanelHeight.current,
_topPanelMaxHeight: topPanelHeight.current || TOP_FILMSTRIP_HEIGHT,
_topPanelVisible,
_verticalFilmstripWidth: verticalFilmstripWidth.current, _verticalFilmstripWidth: verticalFilmstripWidth.current,
_verticalViewMaxWidth: getVerticalViewMaxWidth(state), _verticalViewMaxWidth: getVerticalViewMaxWidth(state),
_videosClassName: videosClassName _videosClassName: videosClassName

View File

@ -9,6 +9,7 @@ import {
ASPECT_RATIO_BREAKPOINT, ASPECT_RATIO_BREAKPOINT,
FILMSTRIP_BREAKPOINT, FILMSTRIP_BREAKPOINT,
FILMSTRIP_BREAKPOINT_OFFSET, FILMSTRIP_BREAKPOINT_OFFSET,
FILMSTRIP_TYPE,
TOOLBAR_HEIGHT, TOOLBAR_HEIGHT,
TOOLBAR_HEIGHT_MOBILE } from '../../constants'; TOOLBAR_HEIGHT_MOBILE } from '../../constants';
import { isFilmstripResizable, showGridInVerticalView } from '../../functions.web'; import { isFilmstripResizable, showGridInVerticalView } from '../../functions.web';
@ -85,15 +86,16 @@ type Props = {
/** /**
* Additional CSS class names to add to the container of all the thumbnails. * Additional CSS class names to add to the container of all the thumbnails.
*/ */
_videosClassName: string, _videosClassName: string
/**
* Whether or not the filmstrip videos should currently be displayed.
*/
_visible: boolean
}; };
const MainFilmstrip = (props: Props) => <span><Filmstrip { ...props } /></span>; const MainFilmstrip = (props: Props) => (
<span>
<Filmstrip
{ ...props }
filmstripType = { FILMSTRIP_TYPE.MAIN } />
</span>
);
/** /**
* Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props. * Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
@ -104,7 +106,7 @@ const MainFilmstrip = (props: Props) => <span><Filmstrip { ...props } /></span>;
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
const toolbarButtons = getToolbarButtons(state); const toolbarButtons = getToolbarButtons(state);
const { visible, remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip']; const { remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length; const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const { const {
gridDimensions: dimensions = {}, gridDimensions: dimensions = {},
@ -189,13 +191,11 @@ function _mapStateToProps(state) {
_filmstripHeight: remoteFilmstripHeight, _filmstripHeight: remoteFilmstripHeight,
_filmstripWidth: remoteFilmstripWidth, _filmstripWidth: remoteFilmstripWidth,
_hasScroll, _hasScroll,
_remoteParticipantsLength: remoteParticipants.length,
_remoteParticipants: remoteParticipants, _remoteParticipants: remoteParticipants,
_resizableFilmstrip, _resizableFilmstrip,
_rows: gridDimensions.rows, _rows: gridDimensions.rows,
_thumbnailWidth: _thumbnailSize?.width, _thumbnailWidth: _thumbnailSize?.width,
_thumbnailHeight: _thumbnailSize?.height, _thumbnailHeight: _thumbnailSize?.height,
_visible: visible,
_verticalViewGrid, _verticalViewGrid,
_verticalViewBackground: verticalFilmstripWidth.current + FILMSTRIP_BREAKPOINT_OFFSET >= FILMSTRIP_BREAKPOINT _verticalViewBackground: verticalFilmstripWidth.current + FILMSTRIP_BREAKPOINT_OFFSET >= FILMSTRIP_BREAKPOINT
}; };

View File

@ -0,0 +1,125 @@
// @flow
import React from 'react';
import { connect } from '../../../base/redux';
import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference';
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import {
FILMSTRIP_TYPE
} from '../../constants';
import Filmstrip from './Filmstrip';
type Props = {
/**
* The current layout of the filmstrip.
*/
_currentLayout: string,
/**
* The number of columns in tile view.
*/
_columns: number,
/**
* The width of the filmstrip.
*/
_filmstripWidth: number,
/**
* The height of the filmstrip.
*/
_filmstripHeight: number,
/**
* Whether or not the current layout is vertical filmstrip.
*/
_isVerticalFilmstrip: boolean,
/**
* The participants in the call.
*/
_remoteParticipants: Array<Object>,
/**
* The length of the remote participants array.
*/
_remoteParticipantsLength: number,
/**
* Whether or not the filmstrip should be user-resizable.
*/
_resizableFilmstrip: boolean,
/**
* The number of rows in tile view.
*/
_rows: number,
/**
* The height of the thumbnail.
*/
_thumbnailHeight: number,
/**
* The width of the thumbnail.
*/
_thumbnailWidth: number,
/**
* Whether or not the vertical filmstrip should have a background color.
*/
_verticalViewBackground: boolean,
/**
* Whether or not the vertical filmstrip should be displayed as grid.
*/
_verticalViewGrid: boolean,
/**
* Additional CSS class names to add to the container of all the thumbnails.
*/
_videosClassName: string
};
const ScreenshareFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW
&& props._remoteParticipantsLength === 1 && (
<span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
<Filmstrip
{ ...props }
filmstripType = { FILMSTRIP_TYPE.SCREENSHARE } />
</span>
);
/**
* Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
const {
filmstripHeight,
filmstripWidth,
thumbnailSize
} = state['features/filmstrip'].screenshareFilmstripDimensions;
const screenshares = state['features/video-layout'].remoteScreenShares;
return {
_columns: 1,
_currentLayout: getCurrentLayout(state),
_filmstripHeight: filmstripHeight,
_filmstripWidth: filmstripWidth,
_remoteParticipants: screenshares.length ? [ screenshares[0] ] : [],
_resizableFilmstrip: false,
_rows: 1,
_thumbnailWidth: thumbnailSize?.width,
_thumbnailHeight: thumbnailSize?.height,
_verticalViewGrid: false,
_verticalViewBackground: false
};
}
export default connect(_mapStateToProps)(ScreenshareFilmstrip);

View File

@ -8,9 +8,11 @@ import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference
import { getCurrentLayout, LAYOUTS } from '../../../video-layout'; import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { import {
ASPECT_RATIO_BREAKPOINT, ASPECT_RATIO_BREAKPOINT,
FILMSTRIP_TYPE,
TOOLBAR_HEIGHT_MOBILE TOOLBAR_HEIGHT_MOBILE
} from '../../constants'; } from '../../constants';
import { getActiveParticipantsIds } from '../../functions'; import { getActiveParticipantsIds } from '../../functions';
import { isFilmstripResizable, isStageFilmstripTopPanel } from '../../functions.web';
import Filmstrip from './Filmstrip'; import Filmstrip from './Filmstrip';
@ -84,19 +86,14 @@ type Props = {
/** /**
* Additional CSS class names to add to the container of all the thumbnails. * Additional CSS class names to add to the container of all the thumbnails.
*/ */
_videosClassName: string, _videosClassName: string
/**
* Whether or not the filmstrip videos should currently be displayed.
*/
_visible: boolean
}; };
const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && ( const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && (
<span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }> <span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
<Filmstrip <Filmstrip
{ ...props } { ...props }
_stageFilmstrip = { true } /> filmstripType = { FILMSTRIP_TYPE.STAGE } />
</span> </span>
); );
@ -109,7 +106,6 @@ const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
const toolbarButtons = getToolbarButtons(state); const toolbarButtons = getToolbarButtons(state);
const { visible } = state['features/filmstrip'];
const activeParticipants = getActiveParticipantsIds(state); const activeParticipants = getActiveParticipantsIds(state);
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length; const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const { const {
@ -139,19 +135,19 @@ function _mapStateToProps(state) {
&& clientWidth <= ASPECT_RATIO_BREAKPOINT; && clientWidth <= ASPECT_RATIO_BREAKPOINT;
const remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0); const remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);
const _topPanelFilmstrip = isStageFilmstripTopPanel(state);
return { return {
_columns: gridDimensions.columns, _columns: gridDimensions.columns,
_currentLayout: getCurrentLayout(state), _currentLayout: getCurrentLayout(state),
_filmstripHeight: remoteFilmstripHeight, _filmstripHeight: remoteFilmstripHeight,
_filmstripWidth: filmstripWidth, _filmstripWidth: filmstripWidth,
_remoteParticipantsLength: activeParticipants.length,
_remoteParticipants: activeParticipants, _remoteParticipants: activeParticipants,
_resizableFilmstrip: false, _resizableFilmstrip: isFilmstripResizable(state) && _topPanelFilmstrip,
_rows: gridDimensions.rows, _rows: gridDimensions.rows,
_thumbnailWidth: thumbnailSize?.width, _thumbnailWidth: thumbnailSize?.width,
_thumbnailHeight: thumbnailSize?.height, _thumbnailHeight: thumbnailSize?.height,
_visible: visible, _topPanelFilmstrip,
_verticalViewGrid: false, _verticalViewGrid: false,
_verticalViewBackground: false _verticalViewBackground: false
}; };

View File

@ -37,6 +37,7 @@ import { togglePinStageParticipant } from '../../actions';
import { import {
DISPLAY_MODE_TO_CLASS_NAME, DISPLAY_MODE_TO_CLASS_NAME,
DISPLAY_VIDEO, DISPLAY_VIDEO,
FILMSTRIP_TYPE,
SHOW_TOOLBAR_CONTEXT_MENU_AFTER, SHOW_TOOLBAR_CONTEXT_MENU_AFTER,
THUMBNAIL_TYPE, THUMBNAIL_TYPE,
VIDEO_TEST_EVENTS VIDEO_TEST_EVENTS
@ -234,6 +235,11 @@ export type Props = {|
*/ */
dispatch: Function, dispatch: Function,
/**
* The type of filmstrip the tile is displayed in.
*/
filmstripType: string,
/** /**
* The horizontal offset in px for the thumbnail. Used to center the thumbnails from the last row in tile view. * The horizontal offset in px for the thumbnail. Used to center the thumbnails from the last row in tile view.
*/ */
@ -244,11 +250,6 @@ export type Props = {|
*/ */
participantID: ?string, participantID: ?string,
/**
* Whether the tile is displayed in the stage filmstrip or not.
*/
stageFilmstrip: boolean,
/** /**
* Styles that will be set to the Thumbnail's main span element. * Styles that will be set to the Thumbnail's main span element.
*/ */
@ -993,7 +994,7 @@ class Thumbnail extends Component<Props, State> {
_thumbnailType, _thumbnailType,
_videoTrack, _videoTrack,
classes, classes,
stageFilmstrip filmstripType
} = this.props; } = this.props;
const { id } = _participant || {}; const { id } = _participant || {};
const { isHovered, popoverVisible } = this.state; const { isHovered, popoverVisible } = this.state;
@ -1031,8 +1032,8 @@ class Thumbnail extends Component<Props, State> {
<span <span
className = { containerClassName } className = { containerClassName }
id = { local id = { local
? `localVideoContainer${stageFilmstrip ? '_stage' : ''}` ? `localVideoContainer${filmstripType === FILMSTRIP_TYPE.MAIN ? '' : `_${filmstripType}`}`
: `participant_${id}${stageFilmstrip ? '_stage' : ''}` : `participant_${id}${filmstripType === FILMSTRIP_TYPE.MAIN ? '' : `_${filmstripType}`}`
} }
{ ...(_isMobile { ...(_isMobile
? { ? {
@ -1168,7 +1169,7 @@ class Thumbnail extends Component<Props, State> {
* @returns {Props} * @returns {Props}
*/ */
function _mapStateToProps(state, ownProps): Object { function _mapStateToProps(state, ownProps): Object {
const { participantID, stageFilmstrip } = ownProps; const { participantID, filmstripType = FILMSTRIP_TYPE.MAIN } = ownProps;
const participant = getParticipantByIdOrUndefined(state, participantID); const participant = getParticipantByIdOrUndefined(state, participantID);
const id = participant?.id; const id = participant?.id;
@ -1199,7 +1200,7 @@ function _mapStateToProps(state, ownProps): Object {
const { localFlipX } = state['features/base/settings']; const { localFlipX } = state['features/base/settings'];
const _isMobile = isMobileBrowser(); const _isMobile = isMobileBrowser();
const activeParticipants = getActiveParticipantsIds(state); const activeParticipants = getActiveParticipantsIds(state);
const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip); const tileType = getThumbnailTypeFromLayout(_currentLayout, filmstripType);
switch (tileType) { switch (tileType) {
case THUMBNAIL_TYPE.VERTICAL: case THUMBNAIL_TYPE.VERTICAL:
@ -1244,7 +1245,8 @@ function _mapStateToProps(state, ownProps): Object {
const { const {
stageFilmstripDimensions = { stageFilmstripDimensions = {
thumbnailSize: {} thumbnailSize: {}
} },
screenshareFilmstripDimensions
} = state['features/filmstrip']; } = state['features/filmstrip'];
size = { size = {
@ -1252,9 +1254,16 @@ function _mapStateToProps(state, ownProps): Object {
_height: thumbnailSize?.height _height: thumbnailSize?.height
}; };
if (stageFilmstrip) { if (filmstripType === FILMSTRIP_TYPE.STAGE) {
const { width: _width, height: _height } = stageFilmstripDimensions.thumbnailSize; const { width: _width, height: _height } = stageFilmstripDimensions.thumbnailSize;
size = {
_width,
_height
};
} else if (filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
const { width: _width, height: _height } = screenshareFilmstripDimensions.thumbnailSize;
size = { size = {
_width, _width,
_height _height

View File

@ -7,7 +7,7 @@ import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { shouldHideSelfView } from '../../../base/settings/functions.any'; import { shouldHideSelfView } from '../../../base/settings/functions.any';
import { getCurrentLayout, LAYOUTS } from '../../../video-layout'; import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN } from '../../constants'; import { TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN, FILMSTRIP_TYPE } from '../../constants';
import { showGridInVerticalView, getActiveParticipantsIds } from '../../functions'; import { showGridInVerticalView, getActiveParticipantsIds } from '../../functions';
import Thumbnail from './Thumbnail'; import Thumbnail from './Thumbnail';
@ -22,6 +22,11 @@ type Props = {
*/ */
_disableSelfView: boolean, _disableSelfView: boolean,
/**
* The type of filmstrip this thumbnail is displayed in.
*/
_filmstripType: string,
/** /**
* The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view. * The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view.
*/ */
@ -37,11 +42,6 @@ type Props = {
*/ */
_isLocalScreenShare: boolean, _isLocalScreenShare: boolean,
/**
* Whether or not the filmstrip is used a stage filmstrip.
*/
_stageFilmstrip: boolean,
/** /**
* The width of the thumbnail. Used for expanding the width of the thumbnails on last row in case * The width of the thumbnail. Used for expanding the width of the thumbnails on last row in case
* there is empty space. * there is empty space.
@ -97,10 +97,10 @@ class ThumbnailWrapper extends Component<Props> {
render() { render() {
const { const {
_disableSelfView, _disableSelfView,
_filmstripType = FILMSTRIP_TYPE.MAIN,
_isLocalScreenShare = false, _isLocalScreenShare = false,
_horizontalOffset = 0, _horizontalOffset = 0,
_participantID, _participantID,
_stageFilmstrip,
_thumbnailWidth, _thumbnailWidth,
style style
} = this.props; } = this.props;
@ -112,9 +112,9 @@ class ThumbnailWrapper extends Component<Props> {
if (_participantID === 'local') { if (_participantID === 'local') {
return _disableSelfView ? null : ( return _disableSelfView ? null : (
<Thumbnail <Thumbnail
filmstripType = { _filmstripType }
horizontalOffset = { _horizontalOffset } horizontalOffset = { _horizontalOffset }
key = 'local' key = 'local'
stageFilmstrip = { _stageFilmstrip }
style = { style } style = { style }
width = { _thumbnailWidth } />); width = { _thumbnailWidth } />);
} }
@ -122,20 +122,20 @@ class ThumbnailWrapper extends Component<Props> {
if (_isLocalScreenShare) { if (_isLocalScreenShare) {
return _disableSelfView ? null : ( return _disableSelfView ? null : (
<Thumbnail <Thumbnail
filmstripType = { _filmstripType }
horizontalOffset = { _horizontalOffset } horizontalOffset = { _horizontalOffset }
key = 'localScreenShare' key = 'localScreenShare'
participantID = { _participantID } participantID = { _participantID }
stageFilmstrip = { _stageFilmstrip }
style = { style } style = { style }
width = { _thumbnailWidth } />); width = { _thumbnailWidth } />);
} }
return ( return (
<Thumbnail <Thumbnail
filmstripType = { _filmstripType }
horizontalOffset = { _horizontalOffset } horizontalOffset = { _horizontalOffset }
key = { `remote_${_participantID}` } key = { `remote_${_participantID}` }
participantID = { _participantID } participantID = { _participantID }
stageFilmstrip = { _stageFilmstrip }
style = { style } style = { style }
width = { _thumbnailWidth } />); width = { _thumbnailWidth } />);
} }
@ -158,7 +158,8 @@ function _mapStateToProps(state, ownProps) {
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true; const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state); const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
const _verticalViewGrid = showGridInVerticalView(state); const _verticalViewGrid = showGridInVerticalView(state);
const stageFilmstrip = ownProps.data?.stageFilmstrip; const filmstripType = ownProps.data?.filmstripType;
const stageFilmstrip = filmstripType === FILMSTRIP_TYPE.STAGE;
const sortedActiveParticipants = activeParticipants.sort(); const sortedActiveParticipants = activeParticipants.sort();
const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote; const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote;
const remoteParticipantsLength = remoteParticipants.length; const remoteParticipantsLength = remoteParticipants.length;
@ -235,9 +236,9 @@ function _mapStateToProps(state, ownProps) {
if (stageFilmstrip) { if (stageFilmstrip) {
return { return {
_disableSelfView: disableSelfView, _disableSelfView: disableSelfView,
_filmstripType: filmstripType,
_participantID: remoteParticipants[index] === localId ? 'local' : remoteParticipants[index], _participantID: remoteParticipants[index] === localId ? 'local' : remoteParticipants[index],
_horizontalOffset: horizontalOffset, _horizontalOffset: horizontalOffset,
_stageFilmstrip: stageFilmstrip,
_thumbnailWidth: thumbnailWidth _thumbnailWidth: thumbnailWidth
}; };
} }
@ -260,6 +261,7 @@ function _mapStateToProps(state, ownProps) {
if (!iAmRecorder && index === localIndex) { if (!iAmRecorder && index === localIndex) {
return { return {
_disableSelfView: disableSelfView, _disableSelfView: disableSelfView,
_filmstripType: filmstripType,
_participantID: 'local', _participantID: 'local',
_horizontalOffset: horizontalOffset, _horizontalOffset: horizontalOffset,
_thumbnailWidth: thumbnailWidth _thumbnailWidth: thumbnailWidth
@ -269,6 +271,7 @@ function _mapStateToProps(state, ownProps) {
if (sourceNameSignalingEnabled && !iAmRecorder && localScreenShare && index === localScreenShareIndex) { if (sourceNameSignalingEnabled && !iAmRecorder && localScreenShare && index === localScreenShareIndex) {
return { return {
_disableSelfView: disableSelfView, _disableSelfView: disableSelfView,
_filmstripType: filmstripType,
_isLocalScreenShare: true, _isLocalScreenShare: true,
_participantID: localScreenShare?.id, _participantID: localScreenShare?.id,
_horizontalOffset: horizontalOffset, _horizontalOffset: horizontalOffset,
@ -277,12 +280,22 @@ function _mapStateToProps(state, ownProps) {
} }
return { return {
_filmstripType: filmstripType,
_participantID: remoteParticipants[remoteIndex], _participantID: remoteParticipants[remoteIndex],
_horizontalOffset: horizontalOffset, _horizontalOffset: horizontalOffset,
_thumbnailWidth: thumbnailWidth _thumbnailWidth: thumbnailWidth
}; };
} }
if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
const { remoteScreenShares } = state['features/video-layout'];
return {
_filmstripType: filmstripType,
_participantID: remoteScreenShares[remoteScreenShares.length - 1]
};
}
const { index } = ownProps; const { index } = ownProps;
if (typeof index !== 'number' || remoteParticipantsLength <= index) { if (typeof index !== 'number' || remoteParticipantsLength <= index) {

View File

@ -5,6 +5,7 @@ export { default as Filmstrip } from './Filmstrip';
export { default as MainFilmstrip } from './MainFilmstrip'; export { default as MainFilmstrip } from './MainFilmstrip';
export { default as ModeratorIndicator } from './ModeratorIndicator'; export { default as ModeratorIndicator } from './ModeratorIndicator';
export { default as RaisedHandIndicator } from './RaisedHandIndicator'; export { default as RaisedHandIndicator } from './RaisedHandIndicator';
export { default as ScreenshareFilmstrip } from './ScreenshareFilmstrip';
export { default as StageFilmstrip } from './StageFilmstrip'; export { default as StageFilmstrip } from './StageFilmstrip';
export { default as StatusIndicators } from './StatusIndicators'; export { default as StatusIndicators } from './StatusIndicators';
export { default as Thumbnail } from './Thumbnail'; export { default as Thumbnail } from './Thumbnail';

View File

@ -23,6 +23,7 @@ export const styles = theme => {
left: 'calc(50% - 16px)', left: 'calc(50% - 16px)',
opacity: 0, opacity: 0,
transition: 'opacity .3s', transition: 'opacity .3s',
zIndex: 1,
'&:hover': { '&:hover': {
backgroundColor: theme.palette.ui02 backgroundColor: theme.palette.ui02
@ -53,8 +54,18 @@ export const styles = theme => {
top: 'calc(50% - 12px)' top: 'calc(50% - 12px)'
}, },
toggleTopPanelContainer: {
transform: 'rotate(180deg)',
bottom: 'calc(-24px - 6px)',
top: 'auto'
},
toggleTopPanelContainerHidden: {
visibility: 'hidden'
},
filmstrip: { filmstrip: {
transition: 'background .2s ease-in-out, right 1s, bottom 1s, height .3s ease-in', transition: 'background .2s ease-in-out, right 1s, bottom 1s, top 1s, height .3s ease-in',
right: 0, right: 0,
bottom: 0, bottom: 0,
@ -111,6 +122,10 @@ export const styles = theme => {
'& .avatar-container': { '& .avatar-container': {
maxWidth: 'initial', maxWidth: 'initial',
maxHeight: 'initial' maxHeight: 'initial'
},
'&.top-panel-filmstrip': {
flexDirection: 'column'
} }
}, },
@ -137,6 +152,18 @@ export const styles = theme => {
'& .dragHandle': { '& .dragHandle': {
backgroundColor: theme.palette.icon01 backgroundColor: theme.palette.icon01
} }
},
'&.top-panel': {
order: 2,
width: '100%',
height: '9px',
cursor: 'row-resize',
'& .dragHandle': {
height: '3px',
width: '100px'
}
} }
}, },

View File

@ -281,6 +281,12 @@ export const FILMSTRIP_GRID_BREAKPOINT = 300;
*/ */
export const FILMSTRIP_BREAKPOINT_OFFSET = 5; export const FILMSTRIP_BREAKPOINT_OFFSET = 5;
/**
* The minimum height for the stage view
* (used to determine the maximum height of the user-resizable top panel).
*/
export const MIN_STAGE_VIEW_HEIGHT = 700;
/** /**
* The minimum width for the stage view * The minimum width for the stage view
* (used to determine the maximum width of the user-resizable vertical filmstrip). * (used to determine the maximum width of the user-resizable vertical filmstrip).
@ -298,7 +304,21 @@ export const VERTICAL_VIEW_HORIZONTAL_MARGIN = VERTICAL_FILMSTRIP_MIN_HORIZONTAL
*/ */
export const ACTIVE_PARTICIPANT_TIMEOUT = 1000 * 60; export const ACTIVE_PARTICIPANT_TIMEOUT = 1000 * 60;
/**
* The types of filmstrip.
*/
export const FILMSTRIP_TYPE = {
MAIN: 'main',
STAGE: 'stage',
SCREENSHARE: 'screenshare'
};
/** /**
* The max number of participants to be displayed on the stage filmstrip. * The max number of participants to be displayed on the stage filmstrip.
*/ */
export const MAX_ACTIVE_PARTICIPANTS = 6; export const MAX_ACTIVE_PARTICIPANTS = 6;
/**
* Top filmstrip default height.
*/
export const TOP_FILMSTRIP_HEIGHT = 180;

View File

@ -6,6 +6,7 @@ import { MEDIA_TYPE } from '../base/media';
import { import {
getLocalParticipant, getLocalParticipant,
getParticipantById, getParticipantById,
getParticipantCount,
getParticipantCountWithFake, getParticipantCountWithFake,
getPinnedParticipant getPinnedParticipant
} from '../base/participants'; } from '../base/participants';
@ -32,6 +33,7 @@ import {
DISPLAY_AVATAR, DISPLAY_AVATAR,
DISPLAY_VIDEO, DISPLAY_VIDEO,
FILMSTRIP_GRID_BREAKPOINT, FILMSTRIP_GRID_BREAKPOINT,
FILMSTRIP_TYPE,
INDICATORS_TOOLTIP_POSITION, INDICATORS_TOOLTIP_POSITION,
SCROLL_SIZE, SCROLL_SIZE,
SQUARE_TILE_ASPECT_RATIO, SQUARE_TILE_ASPECT_RATIO,
@ -296,7 +298,8 @@ export function calculateResponsiveTileViewDimensions({
noHorizontalContainerMargin = false, noHorizontalContainerMargin = false,
maxColumns, maxColumns,
numberOfParticipants, numberOfParticipants,
desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
minTileHeight
}) { }) {
let height, width; let height, width;
let columns, rows; let columns, rows;
@ -324,7 +327,8 @@ export function calculateResponsiveTileViewDimensions({
clientHeight, clientHeight,
disableTileEnlargement, disableTileEnlargement,
disableResponsiveTiles: false, disableResponsiveTiles: false,
noHorizontalContainerMargin noHorizontalContainerMargin,
minTileHeight
}); });
if (size) { if (size) {
@ -413,10 +417,11 @@ export function calculateThumbnailSizeForTileView({
clientHeight, clientHeight,
disableResponsiveTiles = false, disableResponsiveTiles = false,
disableTileEnlargement = false, disableTileEnlargement = false,
noHorizontalContainerMargin = false noHorizontalContainerMargin = false,
minTileHeight
}: Object) { }: Object) {
const aspectRatio = getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth); const aspectRatio = getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth);
const minHeight = getThumbnailMinHeight(clientWidth); const minHeight = minTileHeight || getThumbnailMinHeight(clientWidth);
const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN) const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
- (noHorizontalContainerMargin ? SCROLL_SIZE : TILE_VIEW_GRID_HORIZONTAL_MARGIN); - (noHorizontalContainerMargin ? SCROLL_SIZE : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN; const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
@ -506,6 +511,7 @@ export function getVerticalFilmstripVisibleAreaWidth() {
*/ */
export function computeDisplayModeFromInput(input: Object) { export function computeDisplayModeFromInput(input: Object) {
const { const {
filmstripType,
isActiveParticipant, isActiveParticipant,
isAudioOnly, isAudioOnly,
isCurrentlyOnLargeVideo, isCurrentlyOnLargeVideo,
@ -515,7 +521,6 @@ export function computeDisplayModeFromInput(input: Object) {
isRemoteParticipant, isRemoteParticipant,
multipleVideoSupport, multipleVideoSupport,
stageParticipantsVisible, stageParticipantsVisible,
stageFilmstrip,
tileViewActive tileViewActive
} = input; } = input;
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived); const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
@ -534,8 +539,8 @@ export function computeDisplayModeFromInput(input: Object) {
} }
} }
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant) if (!tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN && ((isScreenSharing && isRemoteParticipant)
|| (stageParticipantsVisible && isActiveParticipant && !stageFilmstrip))) { || (stageParticipantsVisible && isActiveParticipant))) {
return DISPLAY_AVATAR; return DISPLAY_AVATAR;
} else if (isCurrentlyOnLargeVideo && !tileViewActive) { } else if (isCurrentlyOnLargeVideo && !tileViewActive) {
// Display name is always and only displayed when user is on the stage // Display name is always and only displayed when user is on the stage
@ -569,12 +574,13 @@ export function getDisplayModeInput(props: Object, state: Object) {
_participant, _participant,
_stageParticipantsVisible, _stageParticipantsVisible,
_videoTrack, _videoTrack,
stageFilmstrip filmstripType = FILMSTRIP_TYPE.MAIN
} = props; } = props;
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
const { canPlayEventReceived } = state; const { canPlayEventReceived } = state;
return { return {
filmstripType,
isActiveParticipant: _isActiveParticipant, isActiveParticipant: _isActiveParticipant,
isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo, isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo,
isAudioOnly: _isAudioOnly, isAudioOnly: _isAudioOnly,
@ -588,7 +594,6 @@ export function getDisplayModeInput(props: Object, state: Object) {
isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant, isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant,
multipleVideoSupport: _multipleVideoSupport, multipleVideoSupport: _multipleVideoSupport,
stageParticipantsVisible: _stageParticipantsVisible, stageParticipantsVisible: _stageParticipantsVisible,
stageFilmstrip,
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream' videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
}; };
} }
@ -717,8 +722,24 @@ export function isStageFilmstripAvailable(state, minParticipantCount = 0) {
const { remoteScreenShares } = state['features/video-layout']; const { remoteScreenShares } = state['features/video-layout'];
const sharedVideo = isSharingStatus(state['features/shared-video']?.status); const sharedVideo = isSharingStatus(state['features/shared-video']?.status);
return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo return isStageFilmstripEnabled(state) && !sharedVideo
&& activeParticipants.length >= minParticipantCount; && activeParticipants.length >= minParticipantCount
&& (isTopPanelEnabled(state) || remoteScreenShares.length === 0);
}
/**
* Whether the stage filmstrip should be displayed on the top.
*
* @param {Object} state - Redux state.
* @param {number} minParticipantCount - The min number of participants for the stage filmstrip
* to be displayed.
* @returns {boolean}
*/
export function isStageFilmstripTopPanel(state, minParticipantCount = 0) {
const { remoteScreenShares } = state['features/video-layout'];
return isTopPanelEnabled(state)
&& isStageFilmstripAvailable(state, minParticipantCount) && remoteScreenShares.length > 0;
} }
/** /**
@ -737,10 +758,10 @@ export function isStageFilmstripEnabled(state) {
* Gets the thumbnail type by filmstrip type. * Gets the thumbnail type by filmstrip type.
* *
* @param {string} currentLayout - Current app layout. * @param {string} currentLayout - Current app layout.
* @param {boolean} isStageFilmstrip - Whether the filmstrip is stage filmstrip or not. * @param {string} filmstripType - The current filmstrip type.
* @returns {string} * @returns {string}
*/ */
export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = false) { export function getThumbnailTypeFromLayout(currentLayout, filmstripType) {
switch (currentLayout) { switch (currentLayout) {
case LAYOUTS.TILE_VIEW: case LAYOUTS.TILE_VIEW:
return THUMBNAIL_TYPE.TILE; return THUMBNAIL_TYPE.TILE;
@ -749,10 +770,24 @@ export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = fal
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
return THUMBNAIL_TYPE.HORIZONTAL; return THUMBNAIL_TYPE.HORIZONTAL;
case LAYOUTS.STAGE_FILMSTRIP_VIEW: case LAYOUTS.STAGE_FILMSTRIP_VIEW:
if (isStageFilmstrip) { if (filmstripType !== FILMSTRIP_TYPE.MAIN) {
return THUMBNAIL_TYPE.TILE; return THUMBNAIL_TYPE.TILE;
} }
return THUMBNAIL_TYPE.VERTICAL; return THUMBNAIL_TYPE.VERTICAL;
} }
} }
/**
* Whether or not the top panel is enabled.
*
* @param {Object} state - Redux state.
* @returns {boolean}
*/
export function isTopPanelEnabled(state) {
const { filmstrip } = state['features/base/config'];
const participantsCount = getParticipantCount(state);
return !filmstrip?.disableTopPanel && participantsCount >= (filmstrip?.minParticipantCountForTopPanel ?? 50);
}

View File

@ -31,6 +31,7 @@ import {
import { import {
addStageParticipant, addStageParticipant,
removeStageParticipant, removeStageParticipant,
setFilmstripHeight,
setFilmstripWidth, setFilmstripWidth,
setStageParticipants setStageParticipants
} from './actions'; } from './actions';
@ -38,7 +39,9 @@ import {
ACTIVE_PARTICIPANT_TIMEOUT, ACTIVE_PARTICIPANT_TIMEOUT,
DEFAULT_FILMSTRIP_WIDTH, DEFAULT_FILMSTRIP_WIDTH,
MAX_ACTIVE_PARTICIPANTS, MAX_ACTIVE_PARTICIPANTS,
MIN_STAGE_VIEW_WIDTH MIN_STAGE_VIEW_HEIGHT,
MIN_STAGE_VIEW_WIDTH,
TOP_FILMSTRIP_HEIGHT
} from './constants'; } from './constants';
import { import {
isFilmstripResizable, isFilmstripResizable,
@ -77,19 +80,27 @@ MiddlewareRegistry.register(store => next => action => {
const state = store.getState(); const state = store.getState();
if (isFilmstripResizable(state)) { if (isFilmstripResizable(state)) {
const { width: filmstripWidth } = state['features/filmstrip']; const { width: filmstripWidth, topPanelHeight } = state['features/filmstrip'];
const { clientWidth } = action; const { clientWidth, clientHeight } = action;
let width; let height, width;
if (filmstripWidth.current > clientWidth - MIN_STAGE_VIEW_WIDTH) { if (filmstripWidth.current > clientWidth - MIN_STAGE_VIEW_WIDTH) {
width = Math.max(clientWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH); width = Math.max(clientWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH);
} else { } else {
width = Math.min(clientWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet); width = Math.min(clientWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet);
} }
if (width !== filmstripWidth.current) { if (width !== filmstripWidth.current) {
store.dispatch(setFilmstripWidth(width)); store.dispatch(setFilmstripWidth(width));
} }
if (topPanelHeight.current > clientHeight - MIN_STAGE_VIEW_HEIGHT) {
height = Math.max(clientHeight - MIN_STAGE_VIEW_HEIGHT, TOP_FILMSTRIP_HEIGHT);
} else {
height = Math.min(clientHeight - MIN_STAGE_VIEW_HEIGHT, topPanelHeight.userSet);
}
if (height !== topPanelHeight.current) {
store.dispatch(setFilmstripHeight(height));
}
} }
break; break;
} }

View File

@ -19,7 +19,11 @@ import {
SET_VISIBLE_REMOTE_PARTICIPANTS, SET_VISIBLE_REMOTE_PARTICIPANTS,
SET_VOLUME, SET_VOLUME,
SET_MAX_STAGE_PARTICIPANTS, SET_MAX_STAGE_PARTICIPANTS,
CLEAR_STAGE_PARTICIPANTS CLEAR_STAGE_PARTICIPANTS,
SET_SCREENSHARING_TILE_DIMENSIONS,
SET_USER_FILMSTRIP_HEIGHT,
SET_FILMSTRIP_HEIGHT,
SET_TOP_PANEL_VISIBILITY
} from './actionTypes'; } from './actionTypes';
const DEFAULT_STATE = { const DEFAULT_STATE = {
@ -76,6 +80,11 @@ const DEFAULT_STATE = {
*/ */
remoteParticipants: [], remoteParticipants: [],
/**
* The dimensions of the screenshare filmstrip.
*/
screenshareFilmstripDimensions: {},
/** /**
* The stage filmstrip view dimensions. * The stage filmstrip view dimensions.
* *
@ -92,6 +101,27 @@ const DEFAULT_STATE = {
*/ */
tileViewDimensions: {}, tileViewDimensions: {},
/**
* The height of the resizable top panel.
*/
topPanelHeight: {
/**
* Current height. Affected by: user top panel resize,
* window resize.
*/
current: null,
/**
* Height set by user resize. Used as the preferred height.
*/
userSet: null
},
/**
* The indicator determines if the top panel is visible.
*/
topPanelVisible: true,
/** /**
* The vertical view dimensions. * The vertical view dimensions.
* *
@ -227,6 +257,15 @@ ReducerRegistry.register(
...state ...state
}; };
} }
case SET_FILMSTRIP_HEIGHT:{
return {
...state,
topPanelHeight: {
...state.topPanelHeight,
current: action.height
}
};
}
case SET_FILMSTRIP_WIDTH: { case SET_FILMSTRIP_WIDTH: {
return { return {
...state, ...state,
@ -236,6 +275,17 @@ ReducerRegistry.register(
} }
}; };
} }
case SET_USER_FILMSTRIP_HEIGHT: {
const { height } = action;
return {
...state,
topPanelHeight: {
current: height,
userSet: height
}
};
}
case SET_USER_FILMSTRIP_WIDTH: { case SET_USER_FILMSTRIP_WIDTH: {
const { width } = action; const { width } = action;
@ -283,6 +333,18 @@ ReducerRegistry.register(
activeParticipants: [] activeParticipants: []
}; };
} }
case SET_SCREENSHARING_TILE_DIMENSIONS: {
return {
...state,
screenshareFilmstripDimensions: action.dimensions
};
}
case SET_TOP_PANEL_VISIBILITY: {
return {
...state,
topPanelVisible: action.visible
};
}
} }
return state; return state;

View File

@ -14,6 +14,7 @@ import { getCurrentLayout, shouldDisplayTileView, LAYOUTS } from '../video-layou
import { import {
clearStageParticipants, clearStageParticipants,
setHorizontalViewDimensions, setHorizontalViewDimensions,
setScreensharingTileDimensions,
setStageFilmstripViewDimensions, setStageFilmstripViewDimensions,
setTileViewDimensions, setTileViewDimensions,
setVerticalViewDimensions setVerticalViewDimensions
@ -23,7 +24,8 @@ import {
DISPLAY_DRAWER_THRESHOLD DISPLAY_DRAWER_THRESHOLD
} from './constants'; } from './constants';
import { import {
isFilmstripResizable isFilmstripResizable,
isTopPanelEnabled
} from './functions'; } from './functions';
import './subscriber.any'; import './subscriber.any';
@ -176,7 +178,8 @@ StateListenerRegistry.register(
visible: state['features/filmstrip'].visible, visible: state['features/filmstrip'].visible,
clientWidth: state['features/base/responsive-ui'].clientWidth, clientWidth: state['features/base/responsive-ui'].clientWidth,
clientHeight: state['features/base/responsive-ui'].clientHeight, clientHeight: state['features/base/responsive-ui'].clientHeight,
tileView: state['features/video-layout'].tileViewEnabled tileView: state['features/video-layout'].tileViewEnabled,
height: state['features/filmstrip'].topPanelHeight?.current
}; };
}, },
/* listener */(_, store) => { /* listener */(_, store) => {
@ -198,3 +201,27 @@ StateListenerRegistry.register(
store.dispatch(selectParticipantInLargeVideo()); store.dispatch(selectParticipantInLargeVideo());
} }
}); });
/**
* Listens for changes to determine the size of the screenshare filmstrip.
*/
StateListenerRegistry.register(
/* selector */ state => {
return {
length: state['features/video-layout'].remoteScreenShares.length,
clientWidth: state['features/base/responsive-ui'].clientWidth,
clientHeight: state['features/base/responsive-ui'].clientHeight,
height: state['features/filmstrip'].topPanelHeight?.current,
width: state['features/filmstrip'].width?.current,
visible: state['features/filmstrip'].visible,
topPanelVisible: state['features/filmstrip'].topPanelVisible
};
},
/* listener */({ length }, store) => {
if (length >= 1 && isTopPanelEnabled(store.getState())) {
store.dispatch(setScreensharingTileDimensions());
}
}, {
deepEquals: true
});