ref(receiver-constraints): Refactor and fixes.

This commit is contained in:
Hristo Terezov 2022-08-16 14:56:47 +03:00
parent cfb1fef162
commit adcd9a501b
15 changed files with 639 additions and 275 deletions

View File

@ -9,6 +9,7 @@ import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { isTestModeEnabled } from '../../../react/features/base/testing'; import { isTestModeEnabled } from '../../../react/features/base/testing';
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip'; import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip';
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video'; import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
import { setLargeVideoDimensions } from '../../../react/features/large-video/actions.any';
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout'; import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
import UIUtil from '../util/UIUtil'; import UIUtil from '../util/UIUtil';
@ -446,6 +447,8 @@ export class VideoContainer extends LargeContainer {
const { horizontalIndent, verticalIndent } const { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height, containerWidth, containerHeight, verticalFilmstripWidth); = this.getVideoPosition(width, height, containerWidth, containerHeight, verticalFilmstripWidth);
APP.store.dispatch(setLargeVideoDimensions(height, width));
this.$wrapper.animate({ this.$wrapper.animate({
width, width,
height, height,

View File

@ -7,6 +7,7 @@ import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { import {
FILMSTRIP_TYPE FILMSTRIP_TYPE
} from '../../constants'; } from '../../constants';
import { getScreenshareFilmstripParticipantId } from '../../functions';
import Filmstrip from './Filmstrip'; import Filmstrip from './Filmstrip';
@ -105,15 +106,9 @@ function _mapStateToProps(state) {
filmstripHeight, filmstripHeight,
filmstripWidth, filmstripWidth,
thumbnailSize thumbnailSize
}, }
screenshareFilmstripParticipantId
} = state['features/filmstrip']; } = state['features/filmstrip'];
const screenshares = state['features/video-layout'].remoteScreenShares; const id = getScreenshareFilmstripParticipantId(state);
let id = screenshares.find(sId => sId === screenshareFilmstripParticipantId);
if (!id && screenshares.length) {
id = screenshares[0];
}
return { return {
_columns: 1, _columns: 1,

View File

@ -71,6 +71,15 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|| disable1On1Mode); || disable1On1Mode);
} }
/**
* Not implemented on mobile.
*
* @returns {Array<string>}
*/
export function getActiveParticipantsIds() {
return [];
}
/** /**
* Returns the number of participants displayed in tile view. * Returns the number of participants displayed in tile view.
* *
@ -169,6 +178,16 @@ export function isStageFilmstripEnabled() {
return false; return false;
} }
/**
* Whether or not the top panel is enabled.
*
* @returns {boolean}
*/
export function isTopPanelEnabled() {
return false;
}
/** /**
* Calculates the width and height of the filmstrip based on the screen size and aspect ratio. * Calculates the width and height of the filmstrip based on the screen size and aspect ratio.
* *
@ -232,4 +251,13 @@ export function shouldDisplayLocalThumbnailSeparately() {
return Platform.OS !== 'android'; return Platform.OS !== 'android';
} }
/**
* Not implemented on mobile.
*
* @returns {undefined}
*/
export function getScreenshareFilmstripParticipantId() {
return undefined;
}

View File

@ -778,6 +778,24 @@ export function getThumbnailTypeFromLayout(currentLayout, filmstripType) {
} }
} }
/**
* Returns the id of the participant displayed on the screen share filmstrip.
*
* @param {Object} state - Redux state.
* @returns {string} - The participant id.
*/
export function getScreenshareFilmstripParticipantId(state) {
const { screenshareFilmstripParticipantId } = state['features/filmstrip'];
const screenshares = state['features/video-layout'].remoteScreenShares;
let id = screenshares.find(sId => sId === screenshareFilmstripParticipantId);
if (!id && screenshares.length) {
id = screenshares[0];
}
return id;
}
/** /**
* Whether or not the top panel is enabled. * Whether or not the top panel is enabled.
* *

View File

@ -9,6 +9,17 @@
export const SELECT_LARGE_VIDEO_PARTICIPANT export const SELECT_LARGE_VIDEO_PARTICIPANT
= 'SELECT_LARGE_VIDEO_PARTICIPANT'; = 'SELECT_LARGE_VIDEO_PARTICIPANT';
/**
* Action to set the dimensions of the large video.
*
* {
* type: SET_LARGE_VIDEO_DIMENSIONS,
* height: number,
* width: number
* }
*/
export const SET_LARGE_VIDEO_DIMENSIONS = 'SET_LARGE_VIDEO_DIMENSIONS';
/** /**
* Action to update the redux store with the current resolution of large video. * Action to update the redux store with the current resolution of large video.
* *

View File

@ -13,6 +13,7 @@ import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { import {
SELECT_LARGE_VIDEO_PARTICIPANT, SELECT_LARGE_VIDEO_PARTICIPANT,
SET_LARGE_VIDEO_DIMENSIONS,
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
} from './actionTypes'; } from './actionTypes';
@ -79,6 +80,25 @@ export function updateKnownLargeVideoResolution(resolution: number) {
}; };
} }
/**
* Sets the dimenstions of the large video in redux.
*
* @param {number} height - The height of the large video.
* @param {number} width - The width of the large video.
* @returns {{
* type: SET_LARGE_VIDEO_DIMENSIONS,
* height: number,
* width: number
* }}
*/
export function setLargeVideoDimensions(height, width) {
return {
type: SET_LARGE_VIDEO_DIMENSIONS,
height,
width
};
}
/** /**
* Returns the most recent existing remote video track. * Returns the most recent existing remote video track.
* *

View File

@ -3,6 +3,7 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
import { import {
SELECT_LARGE_VIDEO_PARTICIPANT, SELECT_LARGE_VIDEO_PARTICIPANT,
SET_LARGE_VIDEO_DIMENSIONS,
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
SET_SEE_WHAT_IS_BEING_SHARED SET_SEE_WHAT_IS_BEING_SHARED
@ -38,6 +39,13 @@ ReducerRegistry.register('features/large-video', (state: ILargeVideoState = {},
participantId: action.participantId participantId: action.participantId
}; };
case SET_LARGE_VIDEO_DIMENSIONS:
return {
...state,
height: action.height,
width: action.width
};
case UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION: case UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION:
return { return {
...state, ...state,

View File

@ -10,6 +10,8 @@ import {
import { isStageFilmstripAvailable } from '../filmstrip/functions'; import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { isVideoPlaying } from '../shared-video/functions'; import { isVideoPlaying } from '../shared-video/functions';
import { VIDEO_QUALITY_LEVELS } from '../video-quality/constants'; import { VIDEO_QUALITY_LEVELS } from '../video-quality/constants';
import { getReceiverVideoQualityLevel } from '../video-quality/functions';
import { getMinHeightForQualityLvlMap } from '../video-quality/selector';
import { LAYOUTS } from './constants'; import { LAYOUTS } from './constants';
@ -154,7 +156,7 @@ export function isLayoutTileView(state: Object) {
} }
/** /**
* Gets the video quality for the given height. * Returns the video quality for the given height.
* *
* @param {number|undefined} height - Height of the video container. * @param {number|undefined} height - Height of the video container.
* @returns {number} * @returns {number}
@ -177,36 +179,56 @@ function getVideoQualityForHeight(height: number) {
} }
/** /**
* Gets the video quality level for the resizable filmstrip thumbnail height. * Returns the video quality level for the resizable filmstrip thumbnail height.
* *
* @param {number} height - The height of the thumbnail.
* @param {Object} state - Redux state. * @param {Object} state - Redux state.
* @returns {number} * @returns {number}
*/ */
export function getVideoQualityForResizableFilmstripThumbnails(state) { export function getVideoQualityForResizableFilmstripThumbnails(height, state) {
const height = state['features/filmstrip'].verticalViewDimensions?.gridView?.thumbnailSize?.height; if (!height) {
return VIDEO_QUALITY_LEVELS.LOW;
}
return getVideoQualityForHeight(height); return getReceiverVideoQualityLevel(height, getMinHeightForQualityLvlMap(state));
} }
/** /**
* Gets the video quality for the large video. * Returns the video quality level for the screen sharing filmstrip thumbnail height.
*
* @returns {number}
*/
export function getVideoQualityForLargeVideo() {
const wrapper = document.querySelector('#largeVideoWrapper');
return getVideoQualityForHeight(wrapper.clientHeight);
}
/**
* Gets the video quality level for the thumbnails in the stage filmstrip.
* *
* @param {number} height - The height of the thumbnail.
* @param {Object} state - Redux state. * @param {Object} state - Redux state.
* @returns {number} * @returns {number}
*/ */
export function getVideoQualityForStageThumbnails(state) { export function getVideoQualityForScreenSharingFilmstrip(height, state) {
const height = state['features/filmstrip'].stageFilmstripDimensions?.thumbnailSize?.height; if (!height) {
return VIDEO_QUALITY_LEVELS.LOW;
}
return getVideoQualityForHeight(height); return getReceiverVideoQualityLevel(height, getMinHeightForQualityLvlMap(state));
}
/**
* Returns the video quality for the large video.
*
* @param {number} largeVideoHeight - The height of the large video.
* @returns {number} - The video quality for the large video.
*/
export function getVideoQualityForLargeVideo(largeVideoHeight) {
return getVideoQualityForHeight(largeVideoHeight);
}
/**
* Returns the video quality level for the thumbnails in the stage filmstrip.
*
* @param {number} height - The height of the thumbnails.
* @param {Object} state - Redux state.
* @returns {number}
*/
export function getVideoQualityForStageThumbnails(height, state) {
if (!height) {
return VIDEO_QUALITY_LEVELS.LOW;
}
return getReceiverVideoQualityLevel(height, getMinHeightForQualityLvlMap(state));
} }

View File

@ -1,5 +1,7 @@
import { MEDIA_TYPE, setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../base/media'; import { MEDIA_TYPE, setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../base/media';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { CLIENT_RESIZED } from '../base/responsive-ui';
import { setLargeVideoDimensions } from '../large-video/actions.any';
import { SET_CAR_MODE } from './actionTypes'; import { SET_CAR_MODE } from './actionTypes';
import './middleware.any'; import './middleware.any';
@ -18,6 +20,13 @@ MiddlewareRegistry.register(store => next => action => {
case SET_CAR_MODE: case SET_CAR_MODE:
dispatch(setVideoMuted(action.enabled, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.CAR_MODE)); dispatch(setVideoMuted(action.enabled, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.CAR_MODE));
break; break;
case CLIENT_RESIZED: {
const { clientHeight, clientWidth } = store.getState()['features/base/responsive-ui'];
// On mobile the large video should always fill the screen.
dispatch(setLargeVideoDimensions(clientHeight, clientWidth));
break;
}
} }
return result; return result;

View File

@ -1,3 +1,65 @@
/**
* The type of (redux) action which sets the maximum video height that should be
* received from remote participants for the large video, even if the user prefers a larger video
* height.
*
* {
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO,
* maxReceiverVideoQuality: number
* }
*/
export const SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO = 'SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO';
/**
* The type of (redux) action which sets the maximum video height that should be
* received from remote participants for the screen sharing filmstrip, even if the user prefers a larger video
* height.
*
* {
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP,
* maxReceiverVideoQuality: number
* }
*/
export const SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP = 'SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP';
/**
* The type of (redux) action which sets the maximum video height that should be
* received from remote participants for stage filmstrip, even if the user prefers a larger video
* height.
*
* {
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP,
* maxReceiverVideoQuality: number
* }
*/
export const SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP = 'SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP';
/**
* The type of (redux) action which sets the maximum video height that should be
* received from remote participants for tile view, even if the user prefers a larger video
* height.
*
* {
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW,
* maxReceiverVideoQuality: number
* }
*/
export const SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW = 'SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW';
/**
* The type of (redux) action which sets the maximum video height that should be
* received from remote participants for vertical filmstrip, even if the user prefers a larger video
* height.
*
* {
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP,
* maxReceiverVideoQuality: number
* }
*/
export const SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP = 'SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP';
/** /**
* The type of (redux) action which sets the preferred maximum video height that * The type of (redux) action which sets the preferred maximum video height that
* should be sent to and received from remote participants. * should be sent to and received from remote participants.
@ -8,16 +70,3 @@
* } * }
*/ */
export const SET_PREFERRED_VIDEO_QUALITY = 'SET_PREFERRED_VIDEO_QUALITY'; export const SET_PREFERRED_VIDEO_QUALITY = 'SET_PREFERRED_VIDEO_QUALITY';
/**
* The type of (redux) action which sets the maximum video height that should be
* received from remote participants, even if the user prefers a larger video
* height.
*
* {
* type: SET_MAX_RECEIVER_VIDEO_QUALITY,
* maxReceiverVideoQuality: number
* }
*/
export const SET_MAX_RECEIVER_VIDEO_QUALITY = 'SET_MAX_RECEIVER_VIDEO_QUALITY';

View File

@ -2,10 +2,102 @@
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { SET_MAX_RECEIVER_VIDEO_QUALITY, SET_PREFERRED_VIDEO_QUALITY } from './actionTypes'; import {
import { VIDEO_QUALITY_LEVELS } from './constants'; SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP,
SET_PREFERRED_VIDEO_QUALITY
} from './actionTypes';
import { MAX_VIDEO_QUALITY, VIDEO_QUALITY_LEVELS } from './constants';
import logger from './logger'; import logger from './logger';
/**
* Sets the max frame height that should be received for the large video.
*
* @param {number} maxReceiverVideoQuality - The max video frame height to
* receive.
* @returns {{
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO,
* maxReceiverVideoQuality: number
* }}
*/
export function setMaxReceiverVideoQualityForLargeVideo(maxReceiverVideoQuality: number) {
return {
type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO,
maxReceiverVideoQuality
};
}
/**
* Sets the max frame height that should be received for the screen sharing filmstrip particpant.
*
* @param {number} maxReceiverVideoQuality - The max video frame height to
* receive.
* @returns {{
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP,
* maxReceiverVideoQuality: number
* }}
*/
export function setMaxReceiverVideoQualityForScreenSharingFilmstrip(maxReceiverVideoQuality: number) {
return {
type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP,
maxReceiverVideoQuality
};
}
/**
* Sets the max frame height that should be received from remote videos for the stage filmstrip.
*
* @param {number} maxReceiverVideoQuality - The max video frame height to
* receive.
* @returns {{
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP,
* maxReceiverVideoQuality: number
* }}
*/
export function setMaxReceiverVideoQualityForStageFilmstrip(maxReceiverVideoQuality: number) {
return {
type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP,
maxReceiverVideoQuality
};
}
/**
* Sets the max frame height that should be received from remote videos in tile view.
*
* @param {number} maxReceiverVideoQuality - The max video frame height to
* receive.
* @returns {{
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW,
* maxReceiverVideoQuality: number
* }}
*/
export function setMaxReceiverVideoQualityForTileView(maxReceiverVideoQuality: number) {
return {
type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW,
maxReceiverVideoQuality
};
}
/**
* Sets the max frame height that should be received from remote videos for the vertical filmstrip.
*
* @param {number} maxReceiverVideoQuality - The max video frame height to
* receive.
* @returns {{
* type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP,
* maxReceiverVideoQuality: number
* }}
*/
export function setMaxReceiverVideoQualityForVerticalFilmstrip(maxReceiverVideoQuality: number) {
return {
type: SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP,
maxReceiverVideoQuality
};
}
/** /**
* Sets the max frame height the user prefers to send and receive from the * Sets the max frame height the user prefers to send and receive from the
* remote participants. * remote participants.
@ -24,24 +116,6 @@ export function setPreferredVideoQuality(preferredVideoQuality: number) {
}; };
} }
/**
* Sets the max frame height that should be received from remote videos.
*
* @param {number} maxReceiverVideoQuality - The max video frame height to
* receive.
* @returns {{
* type: SET_MAX_RECEIVER_VIDEO_QUALITY,
* maxReceiverVideoQuality: number
* }}
*/
export function setMaxReceiverVideoQuality(maxReceiverVideoQuality: number) {
return {
type: SET_MAX_RECEIVER_VIDEO_QUALITY,
maxReceiverVideoQuality
};
}
/** /**
* Sets the maximum video size the local participant should send and receive from * Sets the maximum video size the local participant should send and receive from
* remote participants. * remote participants.
@ -58,6 +132,6 @@ export function setVideoQuality(frameHeight: number) {
return; return;
} }
dispatch(setPreferredVideoQuality(Math.min(frameHeight, VIDEO_QUALITY_LEVELS.HIGH))); dispatch(setPreferredVideoQuality(Math.min(frameHeight, MAX_VIDEO_QUALITY)));
}; };
} }

View File

@ -19,6 +19,16 @@ export const VIDEO_QUALITY_LEVELS = {
NONE: 0 NONE: 0
}; };
/**
* Indicates unlimited video quality.
*/
export const VIDEO_QUALITY_UNLIMITED = -1;
/**
* The maximum video quality from the VIDEO_QUALITY_LEVELS map.
*/
export const MAX_VIDEO_QUALITY = Math.max(...Object.values(VIDEO_QUALITY_LEVELS));
/** /**
* Maps quality level names used in the config.videoQuality.minHeightForQualityLvl to the quality level constants used * Maps quality level names used in the config.videoQuality.minHeightForQualityLvl to the quality level constants used
* by the application. * by the application.
@ -28,5 +38,6 @@ export const VIDEO_QUALITY_LEVELS = {
export const CFG_LVL_TO_APP_QUALITY_LVL = { export const CFG_LVL_TO_APP_QUALITY_LVL = {
'low': VIDEO_QUALITY_LEVELS.LOW, 'low': VIDEO_QUALITY_LEVELS.LOW,
'standard': VIDEO_QUALITY_LEVELS.STANDARD, 'standard': VIDEO_QUALITY_LEVELS.STANDARD,
'high': VIDEO_QUALITY_LEVELS.HIGH 'high': VIDEO_QUALITY_LEVELS.HIGH,
'ultra': VIDEO_QUALITY_LEVELS.ULTRA
}; };

View File

@ -1,28 +1,5 @@
// @flow
import { CFG_LVL_TO_APP_QUALITY_LVL, VIDEO_QUALITY_LEVELS } from './constants'; import { CFG_LVL_TO_APP_QUALITY_LVL, VIDEO_QUALITY_LEVELS } from './constants';
const { LOW, STANDARD, HIGH, ULTRA } = VIDEO_QUALITY_LEVELS;
const videoQualityLevels = [ LOW, STANDARD, HIGH, ULTRA ];
/**
* Finds the nearest video quality level to the passed video quality.
*
* @param {number} videoQuality - The video quality.
* @returns {number|undefined} - The found quality level.
*/
export function findNearestQualityLevel(videoQuality: number) {
for (let i = 0; i < videoQualityLevels.length; i++) {
const level = videoQualityLevels[i];
if (level >= videoQuality) {
return level;
}
}
return undefined;
}
/** /**
* Selects {@code VIDEO_QUALITY_LEVELS} for the given {@link availableHeight} and threshold to quality mapping. * Selects {@code VIDEO_QUALITY_LEVELS} for the given {@link availableHeight} and threshold to quality mapping.
* *
@ -52,7 +29,7 @@ export function getReceiverVideoQualityLevel(availableHeight: number, heightToLe
* @returns {Map<number, number>|undefined} - A mapping of minimal thumbnail height required for given quality level or * @returns {Map<number, number>|undefined} - A mapping of minimal thumbnail height required for given quality level or
* {@code undefined} if the map contains invalid values. * {@code undefined} if the map contains invalid values.
*/ */
export function validateMinHeightForQualityLvl(minHeightForQualityLvl: Object): ?Map<number, number> { export function validateMinHeightForQualityLvl(minHeightForQualityLvl) {
if (typeof minHeightForQualityLvl !== 'object' if (typeof minHeightForQualityLvl !== 'object'
|| Object.keys(minHeightForQualityLvl).map(lvl => Number(lvl)) || Object.keys(minHeightForQualityLvl).map(lvl => Number(lvl))
.find(lvl => lvl === null || isNaN(lvl) || lvl < 0)) { .find(lvl => lvl === null || isNaN(lvl) || lvl < 0)) {
@ -65,6 +42,13 @@ export function validateMinHeightForQualityLvl(minHeightForQualityLvl: Object):
.sort((a, b) => a - b); .sort((a, b) => a - b);
const map = new Map(); const map = new Map();
Object.values(VIDEO_QUALITY_LEVELS).sort()
.forEach(value => {
if (value > VIDEO_QUALITY_LEVELS.NONE) {
map.set(value, value);
}
});
for (const level of levelsSorted) { for (const level of levelsSorted) {
const configQuality = minHeightForQualityLvl[level]; const configQuality = minHeightForQualityLvl[level];
const appQuality = CFG_LVL_TO_APP_QUALITY_LVL[configQuality]; const appQuality = CFG_LVL_TO_APP_QUALITY_LVL[configQuality];
@ -73,6 +57,7 @@ export function validateMinHeightForQualityLvl(minHeightForQualityLvl: Object):
return undefined; return undefined;
} }
map.delete(appQuality);
map.set(level, appQuality); map.set(level, appQuality);
} }

View File

@ -4,7 +4,14 @@ import PersistenceRegistry from '../base/redux/PersistenceRegistry';
import ReducerRegistry from '../base/redux/ReducerRegistry'; import ReducerRegistry from '../base/redux/ReducerRegistry';
import { set } from '../base/redux/functions'; import { set } from '../base/redux/functions';
import { SET_MAX_RECEIVER_VIDEO_QUALITY, SET_PREFERRED_VIDEO_QUALITY } from './actionTypes'; import {
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW,
SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP,
SET_PREFERRED_VIDEO_QUALITY
} from './actionTypes';
import { VIDEO_QUALITY_LEVELS } from './constants'; import { VIDEO_QUALITY_LEVELS } from './constants';
/* eslint-disable-next-line lines-around-comment */ /* eslint-disable-next-line lines-around-comment */
// @ts-ignore // @ts-ignore
@ -12,16 +19,29 @@ import { validateMinHeightForQualityLvl } from './functions';
import logger from './logger'; import logger from './logger';
const DEFAULT_STATE = { const DEFAULT_STATE = {
maxReceiverVideoQuality: VIDEO_QUALITY_LEVELS.ULTRA, maxReceiverVideoQualityForLargeVideo: VIDEO_QUALITY_LEVELS.ULTRA,
maxReceiverVideoQualityForScreenSharingFilmstrip: VIDEO_QUALITY_LEVELS.HIGH,
maxReceiverVideoQualityForStageFilmstrip: VIDEO_QUALITY_LEVELS.HIGH,
maxReceiverVideoQualityForTileView: VIDEO_QUALITY_LEVELS.STANDARD,
maxReceiverVideoQualityForVerticalFilmstrip: VIDEO_QUALITY_LEVELS.LOW,
minHeightForQualityLvl: new Map(), minHeightForQualityLvl: new Map(),
preferredVideoQuality: VIDEO_QUALITY_LEVELS.ULTRA preferredVideoQuality: VIDEO_QUALITY_LEVELS.ULTRA
}; };
DEFAULT_STATE.minHeightForQualityLvl.set(360, VIDEO_QUALITY_LEVELS.STANDARD);
DEFAULT_STATE.minHeightForQualityLvl.set(720, VIDEO_QUALITY_LEVELS.HIGH); Object.values(VIDEO_QUALITY_LEVELS).sort()
.forEach(value => {
if (value > VIDEO_QUALITY_LEVELS.NONE) {
DEFAULT_STATE.minHeightForQualityLvl.set(value, value);
}
});
export interface IVideoQualityState { export interface IVideoQualityState {
maxReceiverVideoQuality: number; maxReceiverVideoQualityForLargeVideo: number,
maxReceiverVideoQualityForScreenSharingFilmstrip: number,
maxReceiverVideoQualityForStageFilmstrip: number,
maxReceiverVideoQualityForTileView: number,
maxReceiverVideoQualityForVerticalFilmstrip: number,
minHeightForQualityLvl: Map<number, number>; minHeightForQualityLvl: Map<number, number>;
preferredVideoQuality: number; preferredVideoQuality: number;
} }
@ -56,10 +76,28 @@ ReducerRegistry.register('features/video-quality',
switch (action.type) { switch (action.type) {
case SET_CONFIG: case SET_CONFIG:
return _setConfig(state, action); return _setConfig(state, action);
case SET_MAX_RECEIVER_VIDEO_QUALITY: case SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_LARGE_VIDEO:
return set(state,
'maxReceiverVideoQualityForLargeVideo',
action.maxReceiverVideoQuality);
case SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_SCREEN_SHARING_FILMSTRIP:
return set(state,
'maxReceiverVideoQualityForScreenSharingFilmstrip',
action.maxReceiverVideoQuality);
case SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_STAGE_FILMSTRIP:
return set( return set(
state, state,
'maxReceiverVideoQuality', 'maxReceiverVideoQualityForStageFilmstrip',
action.maxReceiverVideoQuality);
case SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_TILE_VIEW:
return set(
state,
'maxReceiverVideoQualityForTileView',
action.maxReceiverVideoQuality);
case SET_MAX_RECEIVER_VIDEO_QUALITY_FOR_VERTICAL_FILMSTRIP:
return set(
state,
'maxReceiverVideoQualityForVerticalFilmstrip',
action.maxReceiverVideoQuality); action.maxReceiverVideoQuality);
case SET_PREFERRED_VIDEO_QUALITY: { case SET_PREFERRED_VIDEO_QUALITY: {
const { preferredVideoQuality } = action; const { preferredVideoQuality } = action;

View File

@ -1,30 +1,40 @@
// @flow /* global APP */
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { _handleParticipantError } from '../base/conference'; import { _handleParticipantError } from '../base/conference';
import { getSourceNameSignalingFeatureFlag } from '../base/config'; import { getSourceNameSignalingFeatureFlag } from '../base/config';
import { MEDIA_TYPE } from '../base/media'; import { MEDIA_TYPE } from '../base/media';
import { getLocalParticipant, getParticipantCount } from '../base/participants'; import { getLocalParticipant } from '../base/participants';
import { StateListenerRegistry } from '../base/redux'; import { StateListenerRegistry } from '../base/redux';
import { getRemoteScreenSharesSourceNames, getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks'; import { getRemoteScreenSharesSourceNames, getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks';
import { reportError } from '../base/util'; import { reportError } from '../base/util';
import { getActiveParticipantsIds } from '../filmstrip/functions.web'; import {
getActiveParticipantsIds,
getScreenshareFilmstripParticipantId,
isTopPanelEnabled
} from '../filmstrip/functions';
import { import {
getVideoQualityForLargeVideo, getVideoQualityForLargeVideo,
getVideoQualityForResizableFilmstripThumbnails, getVideoQualityForResizableFilmstripThumbnails,
getVideoQualityForStageThumbnails, getVideoQualityForStageThumbnails,
LAYOUTS,
shouldDisplayTileView shouldDisplayTileView
} from '../video-layout'; } from '../video-layout';
import { getCurrentLayout, getVideoQualityForScreenSharingFilmstrip } from '../video-layout/functions.any';
import { setMaxReceiverVideoQuality } from './actions'; import {
import { VIDEO_QUALITY_LEVELS } from './constants'; setMaxReceiverVideoQualityForLargeVideo,
setMaxReceiverVideoQualityForScreenSharingFilmstrip,
setMaxReceiverVideoQualityForStageFilmstrip,
setMaxReceiverVideoQualityForTileView,
setMaxReceiverVideoQualityForVerticalFilmstrip
} from './actions';
import { MAX_VIDEO_QUALITY, VIDEO_QUALITY_LEVELS, VIDEO_QUALITY_UNLIMITED } from './constants';
import { getReceiverVideoQualityLevel } from './functions'; import { getReceiverVideoQualityLevel } from './functions';
import logger from './logger'; import logger from './logger';
import { getMinHeightForQualityLvlMap } from './selector'; import { getMinHeightForQualityLvlMap } from './selector';
declare var APP: Object;
/** /**
* Handles changes in the visible participants in the filmstrip. The listener is debounced * Handles changes in the visible participants in the filmstrip. The listener is debounced
* so that the client doesn't end up sending too many bridge messages when the user is * so that the client doesn't end up sending too many bridge messages when the user is
@ -63,17 +73,6 @@ StateListenerRegistry.register(
} }
); );
/**
* Updates the receiver constraints when the layout changes. When we are in stage view we need to handle the
* on-stage participant differently.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/video-layout'].tileViewEnabled,
/* listener */ (tileViewEnabled, store) => {
_updateReceiverVideoConstraints(store);
}
);
/** /**
* StateListenerRegistry provides a reliable way of detecting changes to * StateListenerRegistry provides a reliable way of detecting changes to
* lastn state and dispatching additional actions. * lastn state and dispatching additional actions.
@ -84,26 +83,6 @@ StateListenerRegistry.register(
_updateReceiverVideoConstraints(store); _updateReceiverVideoConstraints(store);
}); });
/**
* Updates the receiver constraints when the tiles in the resizable filmstrip change dimensions.
*/
StateListenerRegistry.register(
state => getVideoQualityForResizableFilmstripThumbnails(state),
(_, store) => {
_updateReceiverVideoConstraints(store);
}
);
/**
* Updates the receiver constraints when the tiles in the resizable top panel change dimensions.
*/
StateListenerRegistry.register(
state => getVideoQualityForStageThumbnails(state),
(_, store) => {
_updateReceiverVideoConstraints(store);
}
);
/** /**
* Updates the receiver constraints when the stage participants change. * Updates the receiver constraints when the stage participants change.
*/ */
@ -118,30 +97,37 @@ StateListenerRegistry.register(
/** /**
* StateListenerRegistry provides a reliable way of detecting changes to * StateListenerRegistry provides a reliable way of detecting changes to
* maxReceiverVideoQuality and preferredVideoQuality state and dispatching additional actions. * maxReceiverVideoQuality* and preferredVideoQuality state and dispatching additional actions.
*/ */
StateListenerRegistry.register( StateListenerRegistry.register(
/* selector */ state => { /* selector */ state => {
const { const {
maxReceiverVideoQuality, maxReceiverVideoQualityForLargeVideo,
maxReceiverVideoQualityForScreenSharingFilmstrip,
maxReceiverVideoQualityForStageFilmstrip,
maxReceiverVideoQualityForTileView,
maxReceiverVideoQualityForVerticalFilmstrip,
preferredVideoQuality preferredVideoQuality
} = state['features/video-quality']; } = state['features/video-quality'];
return { return {
maxReceiverVideoQuality, maxReceiverVideoQualityForLargeVideo,
maxReceiverVideoQualityForScreenSharingFilmstrip,
maxReceiverVideoQualityForStageFilmstrip,
maxReceiverVideoQualityForTileView,
maxReceiverVideoQualityForVerticalFilmstrip,
preferredVideoQuality preferredVideoQuality
}; };
}, },
/* listener */ (currentState, store, previousState = {}) => { /* listener */ (currentState, store, previousState = {}) => {
const { maxReceiverVideoQuality, preferredVideoQuality } = currentState; const { preferredVideoQuality } = currentState;
const changedPreferredVideoQuality = preferredVideoQuality !== previousState.preferredVideoQuality; const changedPreferredVideoQuality = preferredVideoQuality !== previousState.preferredVideoQuality;
const changedReceiverVideoQuality = maxReceiverVideoQuality !== previousState.maxReceiverVideoQuality;
if (changedPreferredVideoQuality) { if (changedPreferredVideoQuality) {
_setSenderVideoConstraint(preferredVideoQuality, store); _setSenderVideoConstraint(preferredVideoQuality, store);
typeof APP !== 'undefined' && APP.API.notifyVideoQualityChanged(preferredVideoQuality); typeof APP !== 'undefined' && APP.API.notifyVideoQualityChanged(preferredVideoQuality);
} }
changedReceiverVideoQuality && _updateReceiverVideoConstraints(store); _updateReceiverVideoConstraints(store);
}, { }, {
deepEquals: true deepEquals: true
}); });
@ -153,48 +139,158 @@ StateListenerRegistry.register(
/* selector */ state => { /* selector */ state => {
const { reducedUI } = state['features/base/responsive-ui']; const { reducedUI } = state['features/base/responsive-ui'];
const _shouldDisplayTileView = shouldDisplayTileView(state); const _shouldDisplayTileView = shouldDisplayTileView(state);
const thumbnailSize = state['features/filmstrip']?.tileViewDimensions?.thumbnailSize; const tileViewThumbnailSize = state['features/filmstrip']?.tileViewDimensions?.thumbnailSize;
const participantCount = getParticipantCount(state); const { visibleRemoteParticipants } = state['features/filmstrip'];
const { height: largeVideoHeight } = state['features/large-video'];
const activeParticipantsIds = getActiveParticipantsIds(state);
const {
screenshareFilmstripDimensions: {
thumbnailSize
}
} = state['features/filmstrip'];
const screenshareFilmstripParticipantId = getScreenshareFilmstripParticipantId(state);
return { return {
activeParticipantsCount: activeParticipantsIds?.length,
displayTileView: _shouldDisplayTileView, displayTileView: _shouldDisplayTileView,
participantCount, largeVideoHeight,
participantCount: visibleRemoteParticipants?.size || 0,
reducedUI, reducedUI,
thumbnailHeight: thumbnailSize?.height screenSharingFilmstripHeight:
screenshareFilmstripParticipantId && getCurrentLayout(state) === LAYOUTS.STAGE_FILMSTRIP_VIEW
? thumbnailSize?.height : undefined,
stageFilmstripThumbnailHeight: state['features/filmstrip'].stageFilmstripDimensions?.thumbnailSize?.height,
tileViewThumbnailHeight: tileViewThumbnailSize?.height,
verticalFilmstripThumbnailHeight:
state['features/filmstrip'].verticalViewDimensions?.gridView?.thumbnailSize?.height
}; };
}, },
/* listener */ ({ displayTileView, participantCount, reducedUI, thumbnailHeight }, { dispatch, getState }) => { /* listener */ ({
activeParticipantsCount,
displayTileView,
largeVideoHeight,
participantCount,
reducedUI,
screenSharingFilmstripHeight,
stageFilmstripThumbnailHeight,
tileViewThumbnailHeight,
verticalFilmstripThumbnailHeight
}, store, previousState = {}) => {
const { dispatch, getState } = store;
const state = getState(); const state = getState();
const { maxReceiverVideoQuality } = state['features/video-quality']; const {
maxReceiverVideoQualityForLargeVideo,
maxReceiverVideoQualityForScreenSharingFilmstrip,
maxReceiverVideoQualityForStageFilmstrip,
maxReceiverVideoQualityForTileView,
maxReceiverVideoQualityForVerticalFilmstrip
} = state['features/video-quality'];
const { maxFullResolutionParticipants = 2 } = state['features/base/config']; const { maxFullResolutionParticipants = 2 } = state['features/base/config'];
let maxVideoQualityChanged = false;
let newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.ULTRA;
if (reducedUI) { if (displayTileView) {
newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.LOW; let newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.STANDARD;
} else if (displayTileView && !Number.isNaN(thumbnailHeight)) {
newMaxRecvVideoQuality = getReceiverVideoQualityLevel(thumbnailHeight, getMinHeightForQualityLvlMap(state));
// Override HD level calculated for the thumbnail height when # of participants threshold is exceeded if (reducedUI) {
if (maxReceiverVideoQuality !== newMaxRecvVideoQuality && maxFullResolutionParticipants !== -1) { newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.LOW;
const override } else if (typeof tileViewThumbnailHeight === 'number' && !Number.isNaN(tileViewThumbnailHeight)) {
= participantCount > maxFullResolutionParticipants newMaxRecvVideoQuality
&& newMaxRecvVideoQuality > VIDEO_QUALITY_LEVELS.STANDARD; = getReceiverVideoQualityLevel(tileViewThumbnailHeight, getMinHeightForQualityLvlMap(state));
logger.info(`Video quality level for thumbnail height: ${thumbnailHeight}, ` // Override HD level calculated for the thumbnail height when # of participants threshold is exceeded
+ `is: ${newMaxRecvVideoQuality}, ` if (maxFullResolutionParticipants !== -1) {
+ `override: ${String(override)}, ` const override
+ `max full res N: ${maxFullResolutionParticipants}`); = participantCount > maxFullResolutionParticipants
&& newMaxRecvVideoQuality > VIDEO_QUALITY_LEVELS.STANDARD;
if (override) { logger.info(`Video quality level for thumbnail height: ${tileViewThumbnailHeight}, `
newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.STANDARD; + `is: ${newMaxRecvVideoQuality}, `
+ `override: ${String(override)}, `
+ `max full res N: ${maxFullResolutionParticipants}`);
if (override) {
newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.STANDARD;
}
} }
} }
if (maxReceiverVideoQualityForTileView !== newMaxRecvVideoQuality) {
maxVideoQualityChanged = true;
dispatch(setMaxReceiverVideoQualityForTileView(newMaxRecvVideoQuality));
}
} else {
let newMaxRecvVideoQualityForStageFilmstrip;
let newMaxRecvVideoQualityForVerticalFilmstrip;
let newMaxRecvVideoQualityForLargeVideo;
let newMaxRecvVideoQualityForScreenSharingFilmstrip;
if (reducedUI) {
newMaxRecvVideoQualityForVerticalFilmstrip
= newMaxRecvVideoQualityForStageFilmstrip
= newMaxRecvVideoQualityForLargeVideo
= newMaxRecvVideoQualityForScreenSharingFilmstrip
= VIDEO_QUALITY_LEVELS.LOW;
} else {
newMaxRecvVideoQualityForStageFilmstrip
= getVideoQualityForStageThumbnails(stageFilmstripThumbnailHeight, state);
newMaxRecvVideoQualityForVerticalFilmstrip
= getVideoQualityForResizableFilmstripThumbnails(verticalFilmstripThumbnailHeight, state);
newMaxRecvVideoQualityForLargeVideo = getVideoQualityForLargeVideo(largeVideoHeight);
newMaxRecvVideoQualityForScreenSharingFilmstrip
= getVideoQualityForScreenSharingFilmstrip(screenSharingFilmstripHeight, state);
// Override HD level calculated for the thumbnail height when # of participants threshold is exceeded
if (maxFullResolutionParticipants !== -1) {
if (activeParticipantsCount > 0
&& newMaxRecvVideoQualityForStageFilmstrip > VIDEO_QUALITY_LEVELS.STANDARD) {
const isScreenSharingFilmstripParticipantFullResolution
= newMaxRecvVideoQualityForScreenSharingFilmstrip > VIDEO_QUALITY_LEVELS.STANDARD;
if (activeParticipantsCount > maxFullResolutionParticipants
- (isScreenSharingFilmstripParticipantFullResolution ? 1 : 0)) {
newMaxRecvVideoQualityForStageFilmstrip = VIDEO_QUALITY_LEVELS.STANDARD;
newMaxRecvVideoQualityForVerticalFilmstrip
= Math.min(VIDEO_QUALITY_LEVELS.STANDARD, newMaxRecvVideoQualityForVerticalFilmstrip);
} else if (newMaxRecvVideoQualityForVerticalFilmstrip > VIDEO_QUALITY_LEVELS.STANDARD
&& participantCount > maxFullResolutionParticipants - activeParticipantsCount) {
newMaxRecvVideoQualityForVerticalFilmstrip = VIDEO_QUALITY_LEVELS.STANDARD;
}
} else if (newMaxRecvVideoQualityForVerticalFilmstrip > VIDEO_QUALITY_LEVELS.STANDARD
&& participantCount > maxFullResolutionParticipants
- (newMaxRecvVideoQualityForLargeVideo > VIDEO_QUALITY_LEVELS.STANDARD ? 1 : 0)) {
newMaxRecvVideoQualityForVerticalFilmstrip = VIDEO_QUALITY_LEVELS.STANDARD;
}
}
}
if (maxReceiverVideoQualityForStageFilmstrip !== newMaxRecvVideoQualityForStageFilmstrip) {
maxVideoQualityChanged = true;
dispatch(setMaxReceiverVideoQualityForStageFilmstrip(newMaxRecvVideoQualityForStageFilmstrip));
}
if (maxReceiverVideoQualityForVerticalFilmstrip !== newMaxRecvVideoQualityForVerticalFilmstrip) {
maxVideoQualityChanged = true;
dispatch(setMaxReceiverVideoQualityForVerticalFilmstrip(newMaxRecvVideoQualityForVerticalFilmstrip));
}
if (maxReceiverVideoQualityForLargeVideo !== newMaxRecvVideoQualityForLargeVideo) {
maxVideoQualityChanged = true;
dispatch(setMaxReceiverVideoQualityForLargeVideo(newMaxRecvVideoQualityForLargeVideo));
}
if (maxReceiverVideoQualityForScreenSharingFilmstrip !== newMaxRecvVideoQualityForScreenSharingFilmstrip) {
maxVideoQualityChanged = true;
dispatch(
setMaxReceiverVideoQualityForScreenSharingFilmstrip(
newMaxRecvVideoQualityForScreenSharingFilmstrip));
}
} }
if (maxReceiverVideoQuality !== newMaxRecvVideoQuality) { if (!maxVideoQualityChanged && Boolean(displayTileView) !== Boolean(previousState.displayTileView)) {
dispatch(setMaxReceiverVideoQuality(newMaxRecvVideoQuality)); _updateReceiverVideoConstraints(store);
} }
}, { }, {
deepEquals: true deepEquals: true
}); });
@ -235,31 +331,47 @@ function _updateReceiverVideoConstraints({ getState }) {
return; return;
} }
const { lastN } = state['features/base/lastn']; const { lastN } = state['features/base/lastn'];
const { maxReceiverVideoQuality, preferredVideoQuality } = state['features/video-quality']; const {
maxReceiverVideoQualityForTileView,
maxReceiverVideoQualityForStageFilmstrip,
maxReceiverVideoQualityForVerticalFilmstrip,
maxReceiverVideoQualityForLargeVideo,
maxReceiverVideoQualityForScreenSharingFilmstrip,
preferredVideoQuality
} = state['features/video-quality'];
const { participantId: largeVideoParticipantId } = state['features/large-video']; const { participantId: largeVideoParticipantId } = state['features/large-video'];
const maxFrameHeight = Math.min(maxReceiverVideoQuality, preferredVideoQuality); const maxFrameHeightForTileView = Math.min(maxReceiverVideoQualityForTileView, preferredVideoQuality);
const maxFrameHeightForStageFilmstrip = Math.min(maxReceiverVideoQualityForStageFilmstrip, preferredVideoQuality);
const maxFrameHeightForVerticalFilmstrip
= Math.min(maxReceiverVideoQualityForVerticalFilmstrip, preferredVideoQuality);
const maxFrameHeightForLargeVideo
= Math.min(maxReceiverVideoQualityForLargeVideo, preferredVideoQuality);
const maxFrameHeightForScreenSharingFilmstrip
= Math.min(maxReceiverVideoQualityForScreenSharingFilmstrip, preferredVideoQuality);
const { remoteScreenShares } = state['features/video-layout']; const { remoteScreenShares } = state['features/video-layout'];
const { visibleRemoteParticipants } = state['features/filmstrip']; const { visibleRemoteParticipants } = state['features/filmstrip'];
const tracks = state['features/base/tracks']; const tracks = state['features/base/tracks'];
const sourceNameSignaling = getSourceNameSignalingFeatureFlag(state); const sourceNameSignaling = getSourceNameSignalingFeatureFlag(state);
const localParticipantId = getLocalParticipant(state).id; const localParticipantId = getLocalParticipant(state).id;
const activeParticipantsIds = getActiveParticipantsIds(state); const activeParticipantsIds = getActiveParticipantsIds(state);
const screenshareFilmstripParticipantId = isTopPanelEnabled(state) && getScreenshareFilmstripParticipantId(state);
let receiverConstraints; const receiverConstraints = {
constraints: {},
defaultConstraints: { 'maxHeight': VIDEO_QUALITY_LEVELS.NONE },
lastN
};
let remoteScreenSharesSourceNames;
let visibleRemoteTrackSourceNames = [];
let largeVideoSourceName;
let activeParticipantsSources = [];
if (sourceNameSignaling) { if (sourceNameSignaling) {
const remoteScreenSharesSourceNames = getRemoteScreenSharesSourceNames(state, remoteScreenShares); receiverConstraints.onStageSources = [];
receiverConstraints.selectedSources = [];
receiverConstraints = { remoteScreenSharesSourceNames = getRemoteScreenSharesSourceNames(state, remoteScreenShares);
constraints: {},
defaultConstraints: { 'maxHeight': VIDEO_QUALITY_LEVELS.NONE },
lastN,
onStageSources: [],
selectedSources: []
};
const visibleRemoteTrackSourceNames = [];
let largeVideoSourceName;
const activeParticipantsSources = [];
if (visibleRemoteParticipants?.size) { if (visibleRemoteParticipants?.size) {
visibleRemoteParticipants.forEach(participantId => { visibleRemoteParticipants.forEach(participantId => {
@ -273,13 +385,27 @@ function _updateReceiverVideoConstraints({ getState }) {
if (sourceName) { if (sourceName) {
visibleRemoteTrackSourceNames.push(sourceName); visibleRemoteTrackSourceNames.push(sourceName);
if (activeParticipantsIds.find(id => id === participantId)) {
activeParticipantsSources.push(sourceName);
}
} }
}); });
} }
if (activeParticipantsIds?.length > 0) {
activeParticipantsIds.forEach(participantId => {
let sourceName;
if (remoteScreenSharesSourceNames.includes(participantId)) {
sourceName = participantId;
} else {
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
}
if (sourceName) {
activeParticipantsSources.push(sourceName);
}
});
}
if (localParticipantId !== largeVideoParticipantId) { if (localParticipantId !== largeVideoParticipantId) {
if (remoteScreenSharesSourceNames.includes(largeVideoParticipantId)) { if (remoteScreenSharesSourceNames.includes(largeVideoParticipantId)) {
largeVideoSourceName = largeVideoParticipantId; largeVideoSourceName = largeVideoParticipantId;
@ -289,110 +415,77 @@ function _updateReceiverVideoConstraints({ getState }) {
); );
} }
} }
// Tile view.
if (shouldDisplayTileView(state)) {
if (!visibleRemoteTrackSourceNames?.length) {
return;
}
visibleRemoteTrackSourceNames.forEach(sourceName => {
receiverConstraints.constraints[sourceName] = { 'maxHeight': maxFrameHeight };
});
// Prioritize screenshare in tile view.
if (remoteScreenSharesSourceNames?.length) {
receiverConstraints.selectedSources = remoteScreenSharesSourceNames;
}
// Stage view.
} else {
if (!visibleRemoteTrackSourceNames?.length && !largeVideoSourceName) {
return;
}
if (visibleRemoteTrackSourceNames?.length) {
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
const stageParticipantsLevel = getVideoQualityForStageThumbnails(state);
visibleRemoteTrackSourceNames.forEach(sourceName => {
const isStageParticipant = activeParticipantsSources.find(name => name === sourceName);
const quality = Math.min(maxFrameHeight, isStageParticipant
? stageParticipantsLevel : qualityLevel);
receiverConstraints.constraints[sourceName] = { 'maxHeight': quality };
});
}
if (largeVideoSourceName) {
let quality = maxFrameHeight;
if (navigator.product !== 'ReactNative'
&& !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
quality = getVideoQualityForLargeVideo();
}
receiverConstraints.constraints[largeVideoSourceName] = { 'maxHeight': quality };
receiverConstraints.onStageSources = [ largeVideoSourceName ];
}
}
if (remoteScreenSharesSourceNames?.length) {
remoteScreenSharesSourceNames.forEach(sourceName => {
receiverConstraints.constraints[sourceName] = { 'maxHeight': VIDEO_QUALITY_LEVELS.ULTRA };
});
}
} else { } else {
receiverConstraints = { receiverConstraints.onStageEndpoints = [];
constraints: {}, receiverConstraints.selectedEndpoints = [];
defaultConstraints: { 'maxHeight': VIDEO_QUALITY_LEVELS.NONE },
lastN,
onStageEndpoints: [],
selectedEndpoints: []
};
// Tile view. remoteScreenSharesSourceNames = remoteScreenShares;
if (shouldDisplayTileView(state)) { visibleRemoteTrackSourceNames = [ ...visibleRemoteParticipants ];
if (!visibleRemoteParticipants?.size) { largeVideoSourceName = largeVideoParticipantId;
return; activeParticipantsSources = activeParticipantsIds;
} }
visibleRemoteParticipants.forEach(participantId => { // Tile view.
receiverConstraints.constraints[participantId] = { 'maxHeight': maxFrameHeight }; if (shouldDisplayTileView(state)) {
if (!visibleRemoteTrackSourceNames?.length) {
return;
}
visibleRemoteTrackSourceNames.forEach(sourceName => {
receiverConstraints.constraints[sourceName] = { 'maxHeight': maxFrameHeightForTileView };
});
// Prioritize screenshare in tile view.
if (remoteScreenSharesSourceNames?.length) {
receiverConstraints[sourceNameSignaling ? 'selectedSources' : 'selectedEndpoints']
= remoteScreenSharesSourceNames;
}
// Stage view.
} else {
if (!visibleRemoteTrackSourceNames?.length && !largeVideoSourceName && !activeParticipantsSources?.length) {
return;
}
if (visibleRemoteTrackSourceNames?.length) {
visibleRemoteTrackSourceNames.forEach(sourceName => {
receiverConstraints.constraints[sourceName] = { 'maxHeight': maxFrameHeightForVerticalFilmstrip };
});
}
if (getCurrentLayout(state) === LAYOUTS.STAGE_FILMSTRIP_VIEW && activeParticipantsSources.length > 0) {
const onStageSources = [ ...activeParticipantsSources ];
activeParticipantsSources.forEach(sourceName => {
const isScreenSharing = remoteScreenShares.includes(sourceName);
const quality
= isScreenSharing && preferredVideoQuality >= MAX_VIDEO_QUALITY
? VIDEO_QUALITY_UNLIMITED : maxFrameHeightForStageFilmstrip;
receiverConstraints.constraints[sourceName] = { 'maxHeight': quality };
}); });
// Prioritize screenshare in tile view. if (screenshareFilmstripParticipantId) {
remoteScreenShares?.length && (receiverConstraints.selectedEndpoints = remoteScreenShares); onStageSources.push(screenshareFilmstripParticipantId);
receiverConstraints.constraints[screenshareFilmstripParticipantId]
// Stage view. = {
} else { 'maxHeight':
if (!visibleRemoteParticipants?.size && !largeVideoParticipantId) { preferredVideoQuality >= MAX_VIDEO_QUALITY
return; ? VIDEO_QUALITY_UNLIMITED : maxFrameHeightForScreenSharingFilmstrip
};
} }
if (visibleRemoteParticipants?.size > 0) { receiverConstraints[sourceNameSignaling ? 'onStageSources' : 'onStageEndpoints'] = onStageSources;
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state); } else if (largeVideoSourceName) {
const stageParticipantsLevel = getVideoQualityForStageThumbnails(state); let quality = VIDEO_QUALITY_UNLIMITED;
visibleRemoteParticipants.forEach(participantId => { if (preferredVideoQuality < MAX_VIDEO_QUALITY
const isStageParticipant = activeParticipantsIds.find(id => id === participantId); || !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
const quality = Math.min(maxFrameHeight, isStageParticipant quality = maxFrameHeightForLargeVideo;
? stageParticipantsLevel : qualityLevel);
receiverConstraints.constraints[participantId] = { 'maxHeight': quality };
});
}
if (largeVideoParticipantId) {
let quality = maxFrameHeight;
if (navigator.product !== 'ReactNative'
&& !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
quality = getVideoQualityForLargeVideo();
}
receiverConstraints.constraints[largeVideoParticipantId] = { 'maxHeight': quality };
receiverConstraints.onStageEndpoints = [ largeVideoParticipantId ];
} }
receiverConstraints.constraints[largeVideoSourceName] = { 'maxHeight': quality };
receiverConstraints[sourceNameSignaling ? 'onStageSources' : 'onStageEndpoints']
= [ largeVideoSourceName ];
} }
} }