{ this.props._filmstripOnly
?
: this._renderToggleButton() }
+ id = 'remoteVideos'
+ style = { remoteVideosStyle }>
{
* thumbnails resize instead of causing overflow.
*/}
+ onMouseOver = { this._onMouseOver }
+ style = { filmstripRemoteVideosContainerStyle }>
@@ -301,20 +344,24 @@ class Filmstrip extends Component
{
function _mapStateToProps(state) {
const { hovered, visible } = state['features/filmstrip'];
const isFilmstripOnly = Boolean(interfaceConfig.filmStripOnly);
- const reduceHeight = !isFilmstripOnly
- && state['features/toolbox'].visible
- && interfaceConfig.TOOLBAR_BUTTONS.length;
+ const reduceHeight
+ = !isFilmstripOnly && state['features/toolbox'].visible && interfaceConfig.TOOLBAR_BUTTONS.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
- const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
- reduceHeight ? 'reduce-height' : ''}`.trim();
+ const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`.trim();
const videosClassName = `filmstrip__videos${
isFilmstripOnly ? ' filmstrip__videos-filmstripOnly' : ''}${
visible ? '' : ' hidden'}`;
+ const { gridDimensions = {}, filmstripWidth } = state['features/filmstrip'].tileViewDimensions;
+
return {
_className: className,
+ _columns: gridDimensions.columns,
+ _currentLayout: getCurrentLayout(state),
_filmstripOnly: isFilmstripOnly,
+ _filmstripWidth: filmstripWidth,
_hovered: hovered,
+ _rows: gridDimensions.rows,
_videosClassName: videosClassName,
_visible: visible
};
diff --git a/react/features/filmstrip/components/web/ModeratorIndicator.js b/react/features/filmstrip/components/web/ModeratorIndicator.js
index 1356dc407d26..0f6b88baf5e0 100644
--- a/react/features/filmstrip/components/web/ModeratorIndicator.js
+++ b/react/features/filmstrip/components/web/ModeratorIndicator.js
@@ -34,6 +34,7 @@ class ModeratorIndicator extends Component {
diff --git a/react/features/filmstrip/components/web/VideoMutedIndicator.js b/react/features/filmstrip/components/web/VideoMutedIndicator.js
index bf0e2d136d68..54775a2edd38 100644
--- a/react/features/filmstrip/components/web/VideoMutedIndicator.js
+++ b/react/features/filmstrip/components/web/VideoMutedIndicator.js
@@ -33,6 +33,7 @@ class VideoMutedIndicator extends Component
{
className = 'videoMuted toolbar-icon'
icon = { IconCameraDisabled }
iconId = 'camera-disabled'
+ iconSize = { 13 }
tooltipKey = 'videothumbnail.videomute'
tooltipPosition = { this.props.tooltipPosition } />
);
diff --git a/react/features/filmstrip/constants.js b/react/features/filmstrip/constants.js
index 6aa593081a41..147ff410ad27 100644
--- a/react/features/filmstrip/constants.js
+++ b/react/features/filmstrip/constants.js
@@ -4,3 +4,8 @@
* The height of the filmstrip in narrow aspect ratio, or width in wide.
*/
export const FILMSTRIP_SIZE = 90;
+
+/**
+ * The aspect ratio of a tile in tile view.
+ */
+export const TILE_ASPECT_RATIO = 16 / 9;
diff --git a/react/features/filmstrip/functions.web.js b/react/features/filmstrip/functions.web.js
index 622315bc01d5..bcd8fe96ef00 100644
--- a/react/features/filmstrip/functions.web.js
+++ b/react/features/filmstrip/functions.web.js
@@ -6,6 +6,8 @@ import {
} from '../base/participants';
import { toState } from '../base/redux';
+import { TILE_ASPECT_RATIO } from './constants';
+
declare var interfaceConfig: Object;
/**
@@ -59,3 +61,58 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|| state['features/base/config'].disable1On1Mode);
}
+
+/**
+ * Calculates the size for thumbnails when in horizontal view layout.
+ *
+ * @param {number} clientHeight - The height of the app window.
+ * @returns {{local: {height, width}, remote: {height, width}}}
+ */
+export function calculateThumbnailSizeForHorizontalView(clientHeight: number = 0) {
+ const topBottomMargin = 15;
+ const availableHeight = Math.min(clientHeight, (interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120) + topBottomMargin);
+ const height = availableHeight - topBottomMargin;
+
+ return {
+ local: {
+ height,
+ width: Math.floor(interfaceConfig.LOCAL_THUMBNAIL_RATIO * height)
+ },
+ remote: {
+ height,
+ width: Math.floor(interfaceConfig.REMOTE_THUMBNAIL_RATIO * height)
+ }
+ };
+}
+
+/**
+ * Calculates the size for thumbnails when in tile view layout.
+ *
+ * @param {Object} dimensions - The desired dimensions of the tile view grid.
+ * @returns {{height, width}}
+ */
+export function calculateThumbnailSizeForTileView({
+ columns,
+ visibleRows,
+ clientWidth,
+ clientHeight
+}: Object) {
+ // The distance from the top and bottom of the screen, as set by CSS, to
+ // avoid overlapping UI elements.
+ const topBottomPadding = 200;
+
+ // Minimum space to keep between the sides of the tiles and the sides
+ // of the window.
+ const sideMargins = 30 * 2;
+ const viewWidth = clientWidth - sideMargins;
+ const viewHeight = clientHeight - topBottomPadding;
+ const initialWidth = viewWidth / columns;
+ const aspectRatioHeight = initialWidth / TILE_ASPECT_RATIO;
+ const height = Math.floor(Math.min(aspectRatioHeight, viewHeight / visibleRows));
+ const width = Math.floor(TILE_ASPECT_RATIO * height);
+
+ return {
+ height,
+ width
+ };
+}
diff --git a/react/features/filmstrip/index.js b/react/features/filmstrip/index.js
index 0bacc21c5014..7689c0d79319 100644
--- a/react/features/filmstrip/index.js
+++ b/react/features/filmstrip/index.js
@@ -5,3 +5,5 @@ export * from './constants';
export * from './functions';
import './reducer';
+import './subscriber';
+import './middleware';
diff --git a/react/features/filmstrip/middleware.native.js b/react/features/filmstrip/middleware.native.js
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/react/features/filmstrip/middleware.web.js b/react/features/filmstrip/middleware.web.js
new file mode 100644
index 000000000000..d7bbd2e7930b
--- /dev/null
+++ b/react/features/filmstrip/middleware.web.js
@@ -0,0 +1,74 @@
+// @flow
+
+import { getNearestReceiverVideoQualityLevel, setMaxReceiverVideoQuality } from '../base/conference';
+import { MiddlewareRegistry } from '../base/redux';
+import { CLIENT_RESIZED } from '../base/responsive-ui';
+import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
+import {
+ getCurrentLayout,
+ LAYOUTS,
+ shouldDisplayTileView
+} from '../video-layout';
+
+import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
+import { SET_HORIZONTAL_VIEW_DIMENSIONS, SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
+
+/**
+ * The middleware of the feature Filmstrip.
+ */
+MiddlewareRegistry.register(store => next => action => {
+ const result = next(action);
+
+ switch (action.type) {
+ case CLIENT_RESIZED: {
+ const state = store.getState();
+ const layout = getCurrentLayout(state);
+
+ switch (layout) {
+ case LAYOUTS.TILE_VIEW: {
+ const { gridDimensions } = state['features/filmstrip'].tileViewDimensions;
+ const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
+
+ store.dispatch(setTileViewDimensions(gridDimensions, {
+ clientHeight,
+ clientWidth
+ }));
+ break;
+ }
+ case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
+ store.dispatch(setHorizontalViewDimensions(state['features/base/responsive-ui'].clientHeight));
+ break;
+ }
+ break;
+ }
+ case SET_TILE_VIEW_DIMENSIONS: {
+ const state = store.getState();
+
+ if (shouldDisplayTileView(state)) {
+ const { width, height } = state['features/filmstrip'].tileViewDimensions.thumbnailSize;
+ const qualityLevel = getNearestReceiverVideoQualityLevel(height);
+
+ store.dispatch(setMaxReceiverVideoQuality(qualityLevel));
+
+ // Once the thumbnails are reactified this should be moved there too.
+ Filmstrip.resizeThumbnailsForTileView(width, height, true);
+ }
+ break;
+ }
+ case SET_HORIZONTAL_VIEW_DIMENSIONS: {
+ const state = store.getState();
+
+ if (getCurrentLayout(state) === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW) {
+ const { horizontalViewDimensions = {} } = state['features/filmstrip'];
+
+ // Once the thumbnails are reactified this should be moved there too.
+ Filmstrip.resizeThumbnailsForHorizontalView(horizontalViewDimensions, true);
+ }
+
+ break;
+ }
+ }
+
+ return result;
+});
+
diff --git a/react/features/filmstrip/reducer.js b/react/features/filmstrip/reducer.js
index 7e4cc11ce905..b4c796c94ffa 100644
--- a/react/features/filmstrip/reducer.js
+++ b/react/features/filmstrip/reducer.js
@@ -5,7 +5,9 @@ import { ReducerRegistry } from '../base/redux';
import {
SET_FILMSTRIP_ENABLED,
SET_FILMSTRIP_HOVERED,
- SET_FILMSTRIP_VISIBLE
+ SET_FILMSTRIP_VISIBLE,
+ SET_HORIZONTAL_VIEW_DIMENSIONS,
+ SET_TILE_VIEW_DIMENSIONS
} from './actionTypes';
const DEFAULT_STATE = {
@@ -17,6 +19,22 @@ const DEFAULT_STATE = {
*/
enabled: true,
+ /**
+ * The horizontal view dimensions.
+ *
+ * @public
+ * @type {Object}
+ */
+ horizontalViewDimensions: {},
+
+ /**
+ * The tile view dimensions.
+ *
+ * @public
+ * @type {Object}
+ */
+ tileViewDimensions: {},
+
/**
* The indicator which determines whether the {@link Filmstrip} is visible.
*
@@ -55,6 +73,17 @@ ReducerRegistry.register(
...state,
visible: action.visible
};
+
+ case SET_HORIZONTAL_VIEW_DIMENSIONS:
+ return {
+ ...state,
+ horizontalViewDimensions: action.dimensions
+ };
+ case SET_TILE_VIEW_DIMENSIONS:
+ return {
+ ...state,
+ tileViewDimensions: action.dimensions
+ };
}
return state;
diff --git a/react/features/filmstrip/subscriber.native.js b/react/features/filmstrip/subscriber.native.js
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/react/features/filmstrip/subscriber.web.js b/react/features/filmstrip/subscriber.web.js
new file mode 100644
index 000000000000..e1f198a2e052
--- /dev/null
+++ b/react/features/filmstrip/subscriber.web.js
@@ -0,0 +1,58 @@
+// @flow
+
+import { StateListenerRegistry, equals } from '../base/redux';
+import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
+import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
+
+import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
+
+/**
+ * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
+ */
+StateListenerRegistry.register(
+ /* selector */ state => state['features/base/participants'].length,
+ /* listener */ (numberOfParticipants, store) => {
+ const state = store.getState();
+
+ if (shouldDisplayTileView(state)) {
+ const gridDimensions = getTileViewGridDimensions(state['features/base/participants'].length);
+ const oldGridDimensions = state['features/filmstrip'].tileViewDimensions.gridDimensions;
+ const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
+
+ if (!equals(gridDimensions, oldGridDimensions)) {
+ store.dispatch(setTileViewDimensions(gridDimensions, {
+ clientHeight,
+ clientWidth
+ }));
+ }
+ }
+ });
+
+/**
+ * Listens for changes in the selected layout to calculate the dimensions of the tile view grid and horizontal view.
+ */
+StateListenerRegistry.register(
+ /* selector */ state => getCurrentLayout(state),
+ /* listener */ (layout, store) => {
+ const state = store.getState();
+
+ switch (layout) {
+ case LAYOUTS.TILE_VIEW: {
+ const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
+
+ store.dispatch(setTileViewDimensions(
+ getTileViewGridDimensions(state['features/base/participants'].length), {
+ clientHeight,
+ clientWidth
+ }));
+ break;
+ }
+ case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
+ store.dispatch(setHorizontalViewDimensions(state['features/base/responsive-ui'].clientHeight));
+ break;
+ case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
+ // Once the thumbnails are reactified this should be moved there too.
+ Filmstrip.resizeThumbnailsForVerticalView();
+ break;
+ }
+ });
diff --git a/react/features/video-layout/functions.js b/react/features/video-layout/functions.js
index 95e4322e74b6..e9cf7040fe2f 100644
--- a/react/features/video-layout/functions.js
+++ b/react/features/video-layout/functions.js
@@ -39,25 +39,20 @@ export function getMaxColumnCount() {
* equal count of tiles for height and width, until maxColumn is reached in
* which rows will be added but no more columns.
*
- * @param {Object} state - The redux state.
+ * @param {number} numberOfParticipants - The number of participants including the fake participants.
* @param {number} maxColumns - The maximum number of columns that can be
* displayed.
* @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.
*/
-export function getTileViewGridDimensions(state: Object, maxColumns: number) {
- // Purposefully include all participants, which includes fake participants
- // that should show a thumbnail.
- const potentialThumbnails = state['features/base/participants'].length;
-
- const columnsToMaintainASquare = Math.ceil(Math.sqrt(potentialThumbnails));
+export function getTileViewGridDimensions(numberOfParticipants: number, maxColumns: number = getMaxColumnCount()) {
+ const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants));
const columns = Math.min(columnsToMaintainASquare, maxColumns);
- const rows = Math.ceil(potentialThumbnails / columns);
+ const rows = Math.ceil(numberOfParticipants / columns);
const visibleRows = Math.min(maxColumns, rows);
return {
columns,
- rows,
visibleRows
};
}