feat(tile-view): Optimize grid dimnsions.

Now the algorithm that calculates the
rows/columns/thumbnail-width/thumbnail-height configuration will
go trough all possible configurations and will choose the one
that covers with thumbnails the biggest area of the window.
This commit is contained in:
Hristo Terezov 2022-03-10 18:27:37 -06:00
parent 3884862996
commit e7c4a55add
10 changed files with 360 additions and 218 deletions

View File

@ -73,6 +73,10 @@
display: block; display: block;
} }
.filmstrip__videos.has-scroll {
padding-left: 7px;
}
.remote-videos { .remote-videos {
box-sizing: border-box; box-sizing: border-box;
@ -90,7 +94,6 @@
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
justify-content: center; justify-content: center;
position: absolute;
.videocontainer { .videocontainer {
border: 0; border: 0;

View File

@ -216,6 +216,7 @@ export default [
'testing', 'testing',
'toolbarButtons', 'toolbarButtons',
'toolbarConfig', 'toolbarConfig',
'tileView',
'transcribingEnabled', 'transcribingEnabled',
'useHostPageLocalStorage', 'useHostPageLocalStorage',
'useTurnUdp', 'useTurnUdp',

View File

@ -1,9 +1,13 @@
// @flow // @flow
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { getLocalParticipant, getParticipantById, pinParticipant } from '../base/participants'; import {
getLocalParticipant,
getParticipantById,
pinParticipant
} from '../base/participants';
import { shouldHideSelfView } from '../base/settings/functions.any'; import { shouldHideSelfView } from '../base/settings/functions.any';
import { getTileViewGridDimensions } from '../video-layout'; import { getMaxColumnCount } from '../video-layout';
import { import {
SET_FILMSTRIP_WIDTH, SET_FILMSTRIP_WIDTH,
@ -21,15 +25,20 @@ import {
TILE_HORIZONTAL_MARGIN, TILE_HORIZONTAL_MARGIN,
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_GRID_HORIZONTAL_MARGIN,
TILE_VIEW_GRID_VERTICAL_MARGIN,
VERTICAL_FILMSTRIP_VERTICAL_MARGIN VERTICAL_FILMSTRIP_VERTICAL_MARGIN
} from './constants'; } from './constants';
import { import {
calculateNotResponsiveTileViewDimensions,
calculateResponsiveTileViewDimensions,
calculateThumbnailSizeForHorizontalView, calculateThumbnailSizeForHorizontalView,
calculateThumbnailSizeForTileView,
calculateThumbnailSizeForVerticalView, calculateThumbnailSizeForVerticalView,
isFilmstripResizable, isFilmstripResizable,
showGridInVerticalView showGridInVerticalView
} from './functions'; } from './functions';
import { getNumberOfPartipantsForTileView } from './functions.web';
export * from './actions.any'; export * from './actions.any';
@ -41,37 +50,56 @@ export * from './actions.any';
* resolved to Redux state using the {@code toState} function. * resolved to Redux state using the {@code toState} function.
* @returns {Function} * @returns {Function}
*/ */
export function setTileViewDimensions(dimensions: Object) { export function setTileViewDimensions() {
return (dispatch: Dispatch<any>, getState: Function) => { return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState(); const state = getState();
const { clientHeight, clientWidth } = state['features/base/responsive-ui']; const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { disableResponsiveTiles, disableTileEnlargement } = state['features/base/config']; const {
disableResponsiveTiles,
disableTileEnlargement,
tileView = {}
} = state['features/base/config'];
const { numberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES } = tileView;
const numberOfParticipants = getNumberOfPartipantsForTileView(state);
const maxColumns = getMaxColumnCount(state);
const { const {
height, height,
width width,
} = calculateThumbnailSizeForTileView({ columns,
...dimensions, rows
clientWidth, } = disableResponsiveTiles
clientHeight, ? calculateNotResponsiveTileViewDimensions(state)
disableResponsiveTiles, : calculateResponsiveTileViewDimensions({
disableTileEnlargement clientWidth,
}); clientHeight,
const { columns, rows } = dimensions; disableTileEnlargement,
isVerticalFilmstrip: false,
maxColumns,
numberOfParticipants,
numberOfVisibleTiles
});
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height); const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
const hasScroll = clientHeight < thumbnailsTotalHeight; const hasScroll = clientHeight < thumbnailsTotalHeight;
const filmstripWidth = (columns * (TILE_HORIZONTAL_MARGIN + width)) + (hasScroll ? SCROLL_SIZE : 0); const filmstripWidth
const filmstripHeight = Math.min(clientHeight, thumbnailsTotalHeight); = Math.min(clientWidth - TILE_VIEW_GRID_HORIZONTAL_MARGIN, columns * (TILE_HORIZONTAL_MARGIN + width))
+ (hasScroll ? SCROLL_SIZE : 0);
const filmstripHeight = Math.min(clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN, thumbnailsTotalHeight);
dispatch({ dispatch({
type: SET_TILE_VIEW_DIMENSIONS, type: SET_TILE_VIEW_DIMENSIONS,
dimensions: { dimensions: {
gridDimensions: dimensions, gridDimensions: {
columns,
rows
},
thumbnailSize: { thumbnailSize: {
height, height,
width width
}, },
filmstripHeight, filmstripHeight,
filmstripWidth filmstripWidth,
hasScroll
} }
}); });
}; };
@ -97,26 +125,34 @@ export function setVerticalViewDimensions() {
// grid view in the vertical filmstrip // grid view in the vertical filmstrip
if (_verticalViewGrid) { if (_verticalViewGrid) {
const dimensions = getTileViewGridDimensions(state, filmstripWidth.current); const { tileView = {} } = state['features/base/config'];
const { numberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES } = tileView;
const numberOfParticipants = getNumberOfPartipantsForTileView(state);
const maxColumns = getMaxColumnCount(state);
const { const {
height, height,
width width,
} = calculateThumbnailSizeForTileView({ columns,
...dimensions, rows
} = calculateResponsiveTileViewDimensions({
clientWidth: filmstripWidth.current, clientWidth: filmstripWidth.current,
clientHeight, clientHeight,
disableResponsiveTiles: false,
disableTileEnlargement: false, disableTileEnlargement: false,
isVerticalFilmstrip: true isVerticalFilmstrip: true,
maxColumns,
numberOfParticipants,
numberOfVisibleTiles
}); });
const { columns, rows } = dimensions;
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height); const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
const hasScroll = clientHeight < thumbnailsTotalHeight; const hasScroll = clientHeight < thumbnailsTotalHeight;
const widthOfFilmstrip = (columns * (TILE_HORIZONTAL_MARGIN + width)) + (hasScroll ? SCROLL_SIZE : 0); const widthOfFilmstrip = (columns * (TILE_HORIZONTAL_MARGIN + width)) + (hasScroll ? SCROLL_SIZE : 0);
const filmstripHeight = Math.min(clientHeight, thumbnailsTotalHeight); const filmstripHeight = Math.min(clientHeight, thumbnailsTotalHeight);
gridView = { gridView = {
gridDimensions: dimensions, gridDimensions: {
columns,
rows
},
thumbnailSize: { thumbnailSize: {
height, height,
width width

View File

@ -767,6 +767,7 @@ function _mapStateToProps(state) {
gridDimensions: dimensions = {}, gridDimensions: dimensions = {},
filmstripHeight, filmstripHeight,
filmstripWidth, filmstripWidth,
hasScroll = false,
thumbnailSize: tileViewThumbnailSize thumbnailSize: tileViewThumbnailSize
} = state['features/filmstrip'].tileViewDimensions; } = state['features/filmstrip'].tileViewDimensions;
const _currentLayout = getCurrentLayout(state); const _currentLayout = getCurrentLayout(state);
@ -796,7 +797,7 @@ function _mapStateToProps(state) {
const shouldReduceHeight = reduceHeight && ( const shouldReduceHeight = reduceHeight && (
isMobileBrowser() || _currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW); isMobileBrowser() || _currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW);
const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}`; let videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}`;
const className = `${remoteVideosVisible || _verticalViewGrid ? '' : 'hide-videos'} ${ const className = `${remoteVideosVisible || _verticalViewGrid ? '' : 'hide-videos'} ${
shouldReduceHeight ? 'reduce-height' : '' shouldReduceHeight ? 'reduce-height' : ''
} ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim(); } ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim();
@ -804,6 +805,9 @@ function _mapStateToProps(state) {
switch (_currentLayout) { switch (_currentLayout) {
case LAYOUTS.TILE_VIEW: case LAYOUTS.TILE_VIEW:
if (hasScroll) {
videosClassName += ' has-scroll';
}
_thumbnailSize = tileViewThumbnailSize; _thumbnailSize = tileViewThumbnailSize;
remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0); remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);
remoteFilmstripWidth = filmstripWidth; remoteFilmstripWidth = filmstripWidth;

View File

@ -28,16 +28,6 @@ export const SQUARE_TILE_ASPECT_RATIO = 1;
*/ */
export const DISPLAY_DRAWER_THRESHOLD = 512; export const DISPLAY_DRAWER_THRESHOLD = 512;
/**
* Breakpoint past which a single column view is enforced in tile view.
*/
export const SINGLE_COLUMN_BREAKPOINT = 300;
/**
* Breakpoint past which a two column view is enforced in tile view.
*/
export const TWO_COLUMN_BREAKPOINT = 1000;
/** /**
* Breakpoint past which the aspect ratio is switched in tile view. * Breakpoint past which the aspect ratio is switched in tile view.
* Also, past this breakpoint, if there are two participants in the conference, we enforce * Also, past this breakpoint, if there are two participants in the conference, we enforce
@ -57,9 +47,14 @@ export const TILE_MIN_HEIGHT_SMALL = 150;
export const TILE_MIN_HEIGHT_LARGE = 200; export const TILE_MIN_HEIGHT_LARGE = 200;
/** /**
* Aspect ratio for portrait tiles. (height / width). * Aspect ratio for portrait tiles.
*/ */
export const TILE_PORTRAIT_ASPECT_RATIO = 1.3; export const TILE_PORTRAIT_ASPECT_RATIO = 1 / 1.3;
/**
* The default number of visible tiles for tile view.
*/
export const TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES = 25;
/** /**
* The default number of columns for tile view. * The default number of columns for tile view.
@ -150,14 +145,14 @@ export const TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN = 2;
* *
* @type {number} * @type {number}
*/ */
export const TILE_VIEW_GRID_VERTICAL_MARGIN = 12; export const TILE_VIEW_GRID_VERTICAL_MARGIN = 14;
/** /**
* The horizontal margin of the tile grid container. * The horizontal margin of the tile grid container.
* *
* @type {number} * @type {number}
*/ */
export const TILE_VIEW_GRID_HORIZONTAL_MARGIN = 12; export const TILE_VIEW_GRID_HORIZONTAL_MARGIN = 14;
/** /**
* The height of the whole toolbar. * The height of the whole toolbar.

View File

@ -10,6 +10,7 @@ import {
getPinnedParticipant getPinnedParticipant
} from '../base/participants'; } from '../base/participants';
import { toState } from '../base/redux'; import { toState } from '../base/redux';
import { shouldHideSelfView } from '../base/settings/functions.any';
import { import {
getLocalVideoTrack, getLocalVideoTrack,
getTrackByMediaTypeAndParticipant, getTrackByMediaTypeAndParticipant,
@ -17,7 +18,11 @@ import {
isRemoteTrackMuted isRemoteTrackMuted
} from '../base/tracks/functions'; } from '../base/tracks/functions';
import { isTrackStreamingStatusActive, isParticipantConnectionStatusActive } from '../connection-indicator/functions'; import { isTrackStreamingStatusActive, isParticipantConnectionStatusActive } from '../connection-indicator/functions';
import { getCurrentLayout, LAYOUTS } from '../video-layout'; import {
getCurrentLayout,
getNotResponsiveTileViewGridDimensions,
LAYOUTS
} from '../video-layout';
import { import {
ASPECT_RATIO_BREAKPOINT, ASPECT_RATIO_BREAKPOINT,
@ -27,7 +32,6 @@ import {
DISPLAY_VIDEO, DISPLAY_VIDEO,
FILMSTRIP_GRID_BREAKPOINT, FILMSTRIP_GRID_BREAKPOINT,
INDICATORS_TOOLTIP_POSITION, INDICATORS_TOOLTIP_POSITION,
SCROLL_SIZE,
SQUARE_TILE_ASPECT_RATIO, SQUARE_TILE_ASPECT_RATIO,
TILE_ASPECT_RATIO, TILE_ASPECT_RATIO,
TILE_HORIZONTAL_MARGIN, TILE_HORIZONTAL_MARGIN,
@ -35,6 +39,7 @@ import {
TILE_MIN_HEIGHT_SMALL, TILE_MIN_HEIGHT_SMALL,
TILE_PORTRAIT_ASPECT_RATIO, TILE_PORTRAIT_ASPECT_RATIO,
TILE_VERTICAL_MARGIN, TILE_VERTICAL_MARGIN,
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,
VERTICAL_VIEW_HORIZONTAL_MARGIN VERTICAL_VIEW_HORIZONTAL_MARGIN
@ -187,6 +192,211 @@ export function calculateThumbnailSizeForVerticalView(clientWidth: number = 0,
}; };
} }
/**
* Returns the minimum height of a thumbnail.
*
* @param {number} clientWidth - The width of the window.
* @returns {number} The minimum height of a thumbnail.
*/
export function getThumbnailMinHeight(clientWidth) {
return clientWidth < ASPECT_RATIO_BREAKPOINT ? TILE_MIN_HEIGHT_SMALL : TILE_MIN_HEIGHT_LARGE;
}
/**
* Returns the default aspect ratio for a tile.
*
* @param {boolean} disableResponsiveTiles - Indicates whether the responsive tiles functionality is disabled.
* @param {boolean} disableTileEnlargement - Indicates whether the tiles enlargement functionality is disabled.
* @param {number} clientWidth - The width of the window.
* @returns {number} The default aspect ratio for a tile.
*/
export function getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth) {
if (!disableResponsiveTiles && disableTileEnlargement && clientWidth < ASPECT_RATIO_BREAKPOINT) {
return SQUARE_TILE_ASPECT_RATIO;
}
return TILE_ASPECT_RATIO;
}
/**
* Returns the number of participants that will be displayed in tile view.
*
* @param {Object} state - The redux store state.
* @returns {number} The number of participants that will be displayed in tile view.
*/
export function getNumberOfPartipantsForTileView(state) {
const { iAmRecorder } = state['features/base/config'];
const disableSelfView = shouldHideSelfView(state);
const numberOfParticipants = getParticipantCountWithFake(state)
- (iAmRecorder ? 1 : 0)
- (disableSelfView ? 1 : 0);
return numberOfParticipants;
}
/**
* Calculates the dimensions (thumbnail width/height and columns/row) for tile view when the responsive tiles are
* disabled.
*
* @param {Object} state - The redux store state.
* @returns {Object} - The dimensions.
*/
export function calculateNotResponsiveTileViewDimensions(state) {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { disableTileEnlargement } = state['features/base/config'];
const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state);
const size = calculateThumbnailSizeForTileView({
columns: c,
minVisibleRows,
clientWidth,
clientHeight,
disableTileEnlargement,
disableResponsiveTiles: true
});
if (typeof size === 'undefined') { // The columns don't fit into the screen. We will have horizontal scroll.
const aspectRatio = disableTileEnlargement
? getTileDefaultAspectRatio(true, disableTileEnlargement, clientWidth)
: TILE_PORTRAIT_ASPECT_RATIO;
const height = getThumbnailMinHeight(clientWidth);
return {
height,
width: aspectRatio * height,
columns: c,
rows: r
};
}
return {
height: size.height,
width: size.width,
columns: c,
rows: r
};
}
/**
* Calculates the dimensions (thumbnail width/height and columns/row) for tile view when the responsive tiles are
* enabled.
*
* @param {Object} state - The redux store state.
* @returns {Object} - The dimensions.
*/
export function calculateResponsiveTileViewDimensions({
clientWidth,
clientHeight,
disableTileEnlargement = false,
isVerticalFilmstrip = false,
maxColumns,
numberOfParticipants,
numberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES
}) {
let height, width;
let columns, rows;
let dimensions = {
maxArea: 0
};
let minHeightEnforcedDimensions = {
maxArea: 0
};
let zeroVisibleRowsDimensions = {
maxArea: 0
};
for (let c = 1; c <= Math.min(maxColumns, numberOfParticipants); c++) {
const r = Math.ceil(numberOfParticipants / c);
// we want to display as much as possible tumbnails up to numberOfVisibleTiles
const visibleRows
= numberOfParticipants <= numberOfVisibleTiles ? r : Math.floor(numberOfVisibleTiles / c);
const size = calculateThumbnailSizeForTileView({
columns: c,
minVisibleRows: visibleRows,
clientWidth,
clientHeight,
disableTileEnlargement,
disableResponsiveTiles: false,
isVerticalFilmstrip
});
// eslint-disable-next-line no-negated-condition
if (typeof size !== 'undefined') {
const { height: currentHeight, width: currentWidth, minHeightEnforced, maxVisibleRows } = size;
console.error(`Num col = ${c}, visibleRows=${visibleRows}, hxw=${
currentHeight}x${currentWidth}, maxVisibleRows=${maxVisibleRows}`);
let area = currentHeight * currentWidth * Math.min(c * maxVisibleRows, numberOfParticipants);
// we have a preference to show more columns if possible, that's why even if the area is equal we
// overwrite.
if (!minHeightEnforced && area > dimensions.maxArea) {
dimensions = {
maxArea: area,
height: currentHeight,
width: currentWidth,
columns: c,
rows: r
};
} else if (minHeightEnforced) {
// eslint-disable-next-line max-depth
if (area > minHeightEnforcedDimensions.maxArea) {
minHeightEnforcedDimensions = {
maxArea: area,
height: currentHeight,
width: currentWidth,
columns: c,
rows: r
};
} else if (maxVisibleRows === 0) {
area = currentHeight * currentWidth * Math.min(c, numberOfParticipants);
// eslint-disable-next-line max-depth
if (area > zeroVisibleRowsDimensions.maxArea) {
zeroVisibleRowsDimensions = {
maxArea: area,
height: currentHeight,
width: currentWidth,
columns: c,
rows: r
};
}
}
}
} else {
console.error(`Num col = ${c}, visibleRows=${visibleRows} not possible`);
}
}
if (dimensions.maxArea > 0) {
({ height, width, columns, rows } = dimensions);
} else if (minHeightEnforcedDimensions.maxArea > 0) {
({ height, width, columns, rows } = minHeightEnforcedDimensions);
} else if (zeroVisibleRowsDimensions.maxArea > 0) {
({ height, width, columns, rows } = zeroVisibleRowsDimensions);
} else { // This would mean that we can't fit even one thumbnail with minimal size.
const aspectRatio = disableTileEnlargement
? getTileDefaultAspectRatio(false, disableTileEnlargement, clientWidth)
: TILE_PORTRAIT_ASPECT_RATIO;
height = getThumbnailMinHeight(clientWidth);
width = aspectRatio * height;
columns = 1;
rows = numberOfParticipants;
}
return {
height,
width,
columns,
rows
};
}
/** /**
* Calculates the size for thumbnails when in tile view layout. * Calculates the size for thumbnails when in tile view layout.
* *
@ -196,90 +406,78 @@ export function calculateThumbnailSizeForVerticalView(clientWidth: number = 0,
export function calculateThumbnailSizeForTileView({ export function calculateThumbnailSizeForTileView({
columns, columns,
minVisibleRows, minVisibleRows,
rows,
clientWidth, clientWidth,
clientHeight, clientHeight,
disableResponsiveTiles, disableResponsiveTiles = false,
disableTileEnlargement, disableTileEnlargement = false,
isVerticalFilmstrip = false isVerticalFilmstrip = false
}: Object) { }: Object) {
let aspectRatio = TILE_ASPECT_RATIO; const aspectRatio = getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth);
const minHeight = getThumbnailMinHeight(clientWidth);
if (!disableResponsiveTiles && clientWidth < ASPECT_RATIO_BREAKPOINT) {
aspectRatio = SQUARE_TILE_ASPECT_RATIO;
}
const minHeight = clientWidth < ASPECT_RATIO_BREAKPOINT ? TILE_MIN_HEIGHT_SMALL : TILE_MIN_HEIGHT_LARGE;
const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN) const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
- (isVerticalFilmstrip ? 0 : TILE_VIEW_GRID_HORIZONTAL_MARGIN); - (isVerticalFilmstrip ? 0 : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
const viewHeight = clientHeight - (minVisibleRows * TILE_VERTICAL_MARGIN) - TILE_VIEW_GRID_VERTICAL_MARGIN; const viewHeight = clientHeight - (minVisibleRows * TILE_VERTICAL_MARGIN) - TILE_VIEW_GRID_VERTICAL_MARGIN;
const initialWidth = viewWidth / columns; const initialWidth = viewWidth / columns;
const initialHeight = viewHeight / minVisibleRows; let initialHeight = viewHeight / minVisibleRows;
const aspectRatioHeight = initialWidth / aspectRatio; let minHeightEnforced = false;
const noScrollHeight = (clientHeight / rows) - TILE_VERTICAL_MARGIN;
const scrollInitialWidth = (viewWidth - SCROLL_SIZE) / columns;
let height = Math.floor(Math.min(aspectRatioHeight, initialHeight));
let width = Math.floor(aspectRatio * height);
if (height > noScrollHeight && width > scrollInitialWidth) { // we will have scroll and we need more space for it. if (initialHeight < minHeight) {
const scrollAspectRatioHeight = scrollInitialWidth / aspectRatio; minHeightEnforced = true;
initialHeight = minHeight;
// Recalculating width/height to fit the available space when a scroll is displayed.
// NOTE: Math.min(scrollAspectRatioHeight, initialHeight) would be enough to recalculate but since the new
// height value can theoretically be dramatically smaller and the scroll may not be neccessary anymore we need
// to compare it with noScrollHeight( the optimal height to fit all thumbnails without scroll) and get the
// bigger one. This way we ensure that we always strech the thumbnails as close as we can to the edges of the
// window.
height = Math.floor(Math.max(Math.min(scrollAspectRatioHeight, initialHeight), noScrollHeight));
width = Math.floor(aspectRatio * height);
return {
height,
width
};
} }
if (disableTileEnlargement) { if (disableTileEnlargement) {
return { const aspectRatioHeight = initialWidth / aspectRatio;
height,
width
};
}
if (initialHeight > noScrollHeight) { if (aspectRatioHeight < minHeight) { // we can't fit the required number of columns.
height = Math.max(height, viewHeight / rows, minHeight); return;
width = Math.max(width, initialWidth);
} else {
height = Math.max(initialHeight, minHeight);
width = initialWidth;
}
if (height > width) {
const heightFromWidth = TILE_PORTRAIT_ASPECT_RATIO * width;
if (height > heightFromWidth && heightFromWidth < minHeight) {
return {
height,
width: height / TILE_PORTRAIT_ASPECT_RATIO
};
} }
return { const height = Math.floor(Math.min(aspectRatioHeight, initialHeight));
height: Math.min(height, heightFromWidth),
width
};
} else if (height < width) {
return { return {
height, height,
width: Math.min(width, aspectRatio * height) width: Math.floor(aspectRatio * height),
minHeightEnforced,
maxVisibleRows: Math.floor(viewHeight / height)
}; };
} }
return { const initialRatio = initialWidth / initialHeight;
height, let height = Math.floor(initialHeight);
width
};
// The biggest area of the grid will be when the grid's height is equal to clientHeight or when the grid's width is
// equal to clientWidth.
if (initialRatio > aspectRatio) {
return {
height,
width: Math.floor(initialHeight * aspectRatio),
minHeightEnforced,
maxVisibleRows: Math.floor(viewHeight / height)
};
} else if (initialRatio >= TILE_PORTRAIT_ASPECT_RATIO) {
return {
height,
width: Math.floor(initialWidth),
minHeightEnforced,
maxVisibleRows: Math.floor(viewHeight / height)
};
} else if (!minHeightEnforced) {
height = Math.floor(initialWidth / TILE_PORTRAIT_ASPECT_RATIO);
if (height >= minHeight) {
return {
height,
width: Math.floor(initialWidth),
minHeightEnforced,
maxVisibleRows: Math.floor(viewHeight / height)
};
}
}
// else
// We can't fit that number of columns with the desired min height and aspect ratio.
} }
/** /**

View File

@ -44,9 +44,7 @@ MiddlewareRegistry.register(store => next => action => {
switch (layout) { switch (layout) {
case LAYOUTS.TILE_VIEW: { case LAYOUTS.TILE_VIEW: {
const { gridDimensions } = state['features/filmstrip'].tileViewDimensions; store.dispatch(setTileViewDimensions());
store.dispatch(setTileViewDimensions(gridDimensions));
break; break;
} }
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:

View File

@ -2,7 +2,7 @@
import { getParticipantCountWithFake } from '../base/participants'; import { getParticipantCountWithFake } from '../base/participants';
import { StateListenerRegistry } from '../base/redux'; import { StateListenerRegistry } from '../base/redux';
import { getTileViewGridDimensions, shouldDisplayTileView } from '../video-layout'; import { shouldDisplayTileView } from '../video-layout';
import { setTileViewDimensions } from './actions'; import { setTileViewDimensions } from './actions';
import './subscriber.any'; import './subscriber.any';
@ -34,9 +34,7 @@ StateListenerRegistry.register(
StateListenerRegistry.register( StateListenerRegistry.register(
/* selector */ state => shouldDisplayTileView(state), /* selector */ state => shouldDisplayTileView(state),
/* listener */ (isTileView, store) => { /* listener */ (isTileView, store) => {
const state = store.getState();
if (isTileView) { if (isTileView) {
store.dispatch(setTileViewDimensions(getTileViewGridDimensions(state))); store.dispatch(setTileViewDimensions());
} }
}); });

View File

@ -2,13 +2,13 @@
import { isMobileBrowser } from '../base/environment/utils'; import { isMobileBrowser } from '../base/environment/utils';
import { getParticipantCountWithFake } from '../base/participants'; import { getParticipantCountWithFake } from '../base/participants';
import { StateListenerRegistry, equals } from '../base/redux'; import { StateListenerRegistry } from '../base/redux';
import { clientResized } from '../base/responsive-ui'; import { clientResized } from '../base/responsive-ui';
import { shouldHideSelfView } from '../base/settings'; import { shouldHideSelfView } from '../base/settings';
import { setFilmstripVisible } from '../filmstrip/actions'; import { setFilmstripVisible } from '../filmstrip/actions';
import { getParticipantsPaneOpen } from '../participants-pane/functions'; import { getParticipantsPaneOpen } from '../participants-pane/functions';
import { setOverflowDrawer } from '../toolbox/actions.web'; import { setOverflowDrawer } from '../toolbox/actions.web';
import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout'; import { getCurrentLayout, shouldDisplayTileView, LAYOUTS } from '../video-layout';
import { import {
setHorizontalViewDimensions, setHorizontalViewDimensions,
@ -17,9 +17,7 @@ import {
} from './actions'; } from './actions';
import { import {
ASPECT_RATIO_BREAKPOINT, ASPECT_RATIO_BREAKPOINT,
DISPLAY_DRAWER_THRESHOLD, DISPLAY_DRAWER_THRESHOLD
SINGLE_COLUMN_BREAKPOINT,
TWO_COLUMN_BREAKPOINT
} from './constants'; } from './constants';
import { isFilmstripResizable } from './functions.web'; import { isFilmstripResizable } from './functions.web';
import './subscriber.any'; import './subscriber.any';
@ -40,12 +38,7 @@ StateListenerRegistry.register(
const resizableFilmstrip = isFilmstripResizable(state); const resizableFilmstrip = isFilmstripResizable(state);
if (shouldDisplayTileView(state)) { if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state); store.dispatch(setTileViewDimensions());
const oldGridDimensions = state['features/filmstrip'].tileViewDimensions.gridDimensions;
if (!equals(gridDimensions, oldGridDimensions)) {
store.dispatch(setTileViewDimensions(gridDimensions));
}
} }
if (resizableFilmstrip) { if (resizableFilmstrip) {
store.dispatch(setVerticalViewDimensions()); store.dispatch(setVerticalViewDimensions());
@ -60,11 +53,9 @@ StateListenerRegistry.register(
StateListenerRegistry.register( StateListenerRegistry.register(
/* selector */ state => getCurrentLayout(state), /* selector */ state => getCurrentLayout(state),
/* listener */ (layout, store) => { /* listener */ (layout, store) => {
const state = store.getState();
switch (layout) { switch (layout) {
case LAYOUTS.TILE_VIEW: case LAYOUTS.TILE_VIEW:
store.dispatch(setTileViewDimensions(getTileViewGridDimensions(state))); store.dispatch(setTileViewDimensions());
break; break;
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
store.dispatch(setHorizontalViewDimensions()); store.dispatch(setHorizontalViewDimensions());
@ -132,50 +123,6 @@ StateListenerRegistry.register(
} }
}); });
/**
* Symbol mapping used for the tile view responsiveness computation.
*/
const responsiveColumnMapping = {
multipleColumns: Symbol('multipleColumns'),
singleColumn: Symbol('singleColumn'),
twoColumns: Symbol('twoColumns'),
twoParticipantsSingleColumn: Symbol('twoParticipantsSingleColumn')
};
/**
* Listens for changes in the screen size to recompute
* the dimensions of the tile view grid and the tiles for responsiveness.
*/
StateListenerRegistry.register(
/* selector */ state => {
const { clientWidth } = state['features/base/responsive-ui'];
if (clientWidth < TWO_COLUMN_BREAKPOINT && clientWidth >= ASPECT_RATIO_BREAKPOINT) {
// Forcing the recomputation of tiles when screen switches in or out of
// the (TWO_COLUMN_BREAKPOINT, ASPECT_RATIO_BREAKPOINT] interval.
return responsiveColumnMapping.twoColumns;
} else if (clientWidth < ASPECT_RATIO_BREAKPOINT && clientWidth >= SINGLE_COLUMN_BREAKPOINT) {
// Forcing the recomputation of tiles when screen switches in or out of
// the (ASPECT_RATIO_BREAKPOINT, SINGLE_COLUMN_BREAKPOINT] interval.
return responsiveColumnMapping.twoParticipantsSingleColumn;
} else if (clientWidth < SINGLE_COLUMN_BREAKPOINT) {
// Forcing the recomputation of tiles when screen switches below SINGLE_COLUMN_BREAKPOINT.
return responsiveColumnMapping.singleColumn;
}
// Forcing the recomputation of tiles when screen switches above TWO_COLUMN_BREAKPOINT.
return responsiveColumnMapping.multipleColumns;
},
/* listener */ (_, store) => {
const state = store.getState();
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
store.dispatch(setTileViewDimensions(gridDimensions));
}
});
/** /**
* Listens for changes in the filmstrip width to determine the size of the tiles. * Listens for changes in the filmstrip width to determine the size of the tiles.
*/ */

View File

@ -5,17 +5,13 @@ import { getFeatureFlag, TILE_VIEW_ENABLED } from '../base/flags';
import { import {
getPinnedParticipant, getPinnedParticipant,
getParticipantCount, getParticipantCount,
pinParticipant, pinParticipant
getParticipantCountWithFake
} from '../base/participants'; } from '../base/participants';
import { shouldHideSelfView } from '../base/settings/functions.any';
import { import {
ASPECT_RATIO_BREAKPOINT,
DEFAULT_MAX_COLUMNS, DEFAULT_MAX_COLUMNS,
ABSOLUTE_MAX_COLUMNS, ABSOLUTE_MAX_COLUMNS
SINGLE_COLUMN_BREAKPOINT,
TWO_COLUMN_BREAKPOINT
} from '../filmstrip/constants'; } from '../filmstrip/constants';
import { getNumberOfPartipantsForTileView } from '../filmstrip/functions.web';
import { isVideoPlaying } from '../shared-video/functions'; import { isVideoPlaying } from '../shared-video/functions';
import { LAYOUTS } from './constants'; import { LAYOUTS } from './constants';
@ -62,32 +58,10 @@ export function getCurrentLayout(state: Object) {
* @param {number} width - Custom width to use for calculation. * @param {number} width - Custom width to use for calculation.
* @returns {number} * @returns {number}
*/ */
export function getMaxColumnCount(state: Object, width: ?number) { export function getMaxColumnCount() {
const configuredMax = (typeof interfaceConfig === 'undefined' const configuredMax = (typeof interfaceConfig === 'undefined'
? DEFAULT_MAX_COLUMNS ? DEFAULT_MAX_COLUMNS
: interfaceConfig.TILE_VIEW_MAX_COLUMNS) || DEFAULT_MAX_COLUMNS; : interfaceConfig.TILE_VIEW_MAX_COLUMNS) || DEFAULT_MAX_COLUMNS;
const { disableResponsiveTiles } = state['features/base/config'];
if (!disableResponsiveTiles) {
const { clientWidth } = state['features/base/responsive-ui'];
const widthToUse = width || clientWidth;
const participantCount = getParticipantCount(state);
// If there are just two participants in a conference, enforce single-column view for mobile size.
if (participantCount === 2 && widthToUse < ASPECT_RATIO_BREAKPOINT) {
return Math.min(1, Math.max(configuredMax, 1));
}
// Enforce single column view at very small screen widths.
if (widthToUse < SINGLE_COLUMN_BREAKPOINT) {
return Math.min(1, Math.max(configuredMax, 1));
}
// Enforce two column view below breakpoint.
if (widthToUse < TWO_COLUMN_BREAKPOINT) {
return Math.min(2, Math.max(configuredMax, 1));
}
}
return Math.min(Math.max(configuredMax, 1), ABSOLUTE_MAX_COLUMNS); return Math.min(Math.max(configuredMax, 1), ABSOLUTE_MAX_COLUMNS);
} }
@ -102,22 +76,10 @@ export function getMaxColumnCount(state: Object, width: ?number) {
* @returns {Object} An object is return with the desired number of columns, * @returns {Object} An object is return with the desired number of columns,
* rows, and visible rows (the rest should overflow) for the tile view layout. * rows, and visible rows (the rest should overflow) for the tile view layout.
*/ */
export function getTileViewGridDimensions(state: Object, width: ?number) { export function getNotResponsiveTileViewGridDimensions(state: Object) {
const maxColumns = getMaxColumnCount(state, width); const maxColumns = getMaxColumnCount(state);
const numberOfParticipants = getNumberOfPartipantsForTileView(state);
// When in tile view mode, we must discount ourselves (the local participant) because our const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants));
// tile is not visible.
const { iAmRecorder } = state['features/base/config'];
const disableSelfView = shouldHideSelfView(state);
const numberOfParticipants = getParticipantCountWithFake(state)
- (iAmRecorder ? 1 : 0)
- (disableSelfView ? 1 : 0);
const isWeb = navigator.product !== 'ReactNative';
// When there are 3 participants in the call we want them to be placed on a single row unless the maxColumn setting
// is lower.
const columnsToMaintainASquare
= isWeb && numberOfParticipants === 3 ? 3 : Math.ceil(Math.sqrt(numberOfParticipants));
const columns = Math.min(columnsToMaintainASquare, maxColumns); const columns = Math.min(columnsToMaintainASquare, maxColumns);
const rows = Math.ceil(numberOfParticipants / columns); const rows = Math.ceil(numberOfParticipants / columns);
const minVisibleRows = Math.min(maxColumns, rows); const minVisibleRows = Math.min(maxColumns, rows);