2022-11-10 08:45:56 +00:00
|
|
|
import { IReduxState } from '../app/types';
|
|
|
|
import { IStateful } from '../base/app/types';
|
|
|
|
import { FILMSTRIP_ENABLED } from '../base/flags/constants';
|
|
|
|
import { getFeatureFlag } from '../base/flags/functions';
|
2022-05-06 10:18:57 +00:00
|
|
|
import {
|
|
|
|
getLocalParticipant,
|
|
|
|
getParticipantCountWithFake,
|
|
|
|
getPinnedParticipant
|
2022-11-10 08:45:56 +00:00
|
|
|
} from '../base/participants/functions';
|
|
|
|
import Platform from '../base/react/Platform.native';
|
|
|
|
import { toState } from '../base/redux/functions';
|
2021-08-20 23:32:38 +00:00
|
|
|
import { ASPECT_RATIO_NARROW } from '../base/responsive-ui/constants';
|
2022-11-10 08:45:56 +00:00
|
|
|
import { shouldHideSelfView } from '../base/settings/functions.native';
|
|
|
|
// eslint-disable-next-line lines-around-comment
|
|
|
|
// @ts-ignore
|
2022-05-06 10:18:57 +00:00
|
|
|
import conferenceStyles from '../conference/components/native/styles';
|
2022-11-10 08:45:56 +00:00
|
|
|
import { shouldDisplayTileView } from '../video-layout/functions.native';
|
2022-05-06 10:18:57 +00:00
|
|
|
|
2022-11-10 08:45:56 +00:00
|
|
|
// @ts-ignore
|
2022-05-06 10:18:57 +00:00
|
|
|
import { styles } from './components';
|
2018-06-14 09:14:32 +00:00
|
|
|
|
2021-08-23 23:02:41 +00:00
|
|
|
export * from './functions.any';
|
|
|
|
|
2018-06-14 09:14:32 +00:00
|
|
|
/**
|
|
|
|
* Returns true if the filmstrip on mobile is visible, false otherwise.
|
|
|
|
*
|
|
|
|
* NOTE: Filmstrip on mobile behaves differently to web, and is only visible
|
|
|
|
* when there are at least 2 participants.
|
|
|
|
*
|
|
|
|
* @param {Object | Function} stateful - The Object or Function that can be
|
|
|
|
* resolved to a Redux state object with the toState function.
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function isFilmstripVisible(stateful: IStateful) {
|
2018-06-14 09:14:32 +00:00
|
|
|
const state = toState(stateful);
|
2021-01-22 10:03:39 +00:00
|
|
|
|
|
|
|
const enabled = getFeatureFlag(state, FILMSTRIP_ENABLED, true);
|
|
|
|
|
|
|
|
if (!enabled) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
return getParticipantCountWithFake(state) > 1;
|
2018-06-14 09:14:32 +00:00
|
|
|
}
|
2021-06-30 07:30:38 +00:00
|
|
|
|
2021-08-06 08:59:17 +00:00
|
|
|
/**
|
|
|
|
* Determines whether the remote video thumbnails should be displayed/visible in
|
|
|
|
* the filmstrip.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The full redux state.
|
|
|
|
* @returns {boolean} - If remote video thumbnails should be displayed/visible
|
|
|
|
* in the filmstrip, then {@code true}; otherwise, {@code false}.
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function shouldRemoteVideosBeVisible(state: IReduxState) {
|
2021-08-06 08:59:17 +00:00
|
|
|
if (state['features/invite'].calleeInfoVisible) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-14 07:10:08 +00:00
|
|
|
// Include fake participants to derive how many thumbnails are displayed,
|
2021-08-06 08:59:17 +00:00
|
|
|
// as it is assumed all participants, including fake, will be displayed
|
|
|
|
// in the filmstrip.
|
|
|
|
const participantCount = getParticipantCountWithFake(state);
|
|
|
|
const pinnedParticipant = getPinnedParticipant(state);
|
|
|
|
const { disable1On1Mode } = state['features/base/config'];
|
|
|
|
|
|
|
|
return Boolean(
|
|
|
|
participantCount > 2
|
|
|
|
|
|
|
|
// Always show the filmstrip when there is another participant to
|
|
|
|
// show and the local video is pinned. Note we are not taking the
|
|
|
|
// toolbar visibility into account here (unlike web) because
|
|
|
|
// showing / hiding views in quick succession on mobile is taxing.
|
|
|
|
|| (participantCount > 1 && pinnedParticipant?.local)
|
|
|
|
|
|
|
|
|| disable1On1Mode);
|
|
|
|
}
|
2021-08-20 23:32:38 +00:00
|
|
|
|
2022-08-16 11:56:47 +00:00
|
|
|
/**
|
|
|
|
* Not implemented on mobile.
|
|
|
|
*
|
2022-11-10 08:45:56 +00:00
|
|
|
* @param {any} _state - Used on web.
|
2022-08-16 11:56:47 +00:00
|
|
|
* @returns {Array<string>}
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function getActiveParticipantsIds(_state: any) {
|
2022-08-16 11:56:47 +00:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2022-11-11 12:42:52 +00:00
|
|
|
/**
|
|
|
|
* Not implemented on mobile.
|
|
|
|
*
|
|
|
|
* @param {any} _state - Redux state.
|
|
|
|
* @returns {Array<Object>}
|
|
|
|
*/
|
|
|
|
export function getPinnedActiveParticipants(_state: any) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2022-05-06 10:18:57 +00:00
|
|
|
/**
|
|
|
|
* Returns the number of participants displayed in tile view.
|
|
|
|
*
|
|
|
|
* @param {Object | Function} stateful - The Object or Function that can be
|
|
|
|
* resolved to a Redux state object with the toState function.
|
|
|
|
* @returns {number} - The number of participants displayed in tile view.
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function getTileViewParticipantCount(stateful: IStateful) {
|
2022-05-06 10:18:57 +00:00
|
|
|
const state = toState(stateful);
|
|
|
|
const disableSelfView = shouldHideSelfView(state);
|
|
|
|
const localParticipant = getLocalParticipant(state);
|
|
|
|
const participantCount = getParticipantCountWithFake(state) - (disableSelfView && localParticipant ? 1 : 0);
|
|
|
|
|
|
|
|
return participantCount;
|
|
|
|
}
|
|
|
|
|
2021-08-20 23:32:38 +00:00
|
|
|
/**
|
|
|
|
* Returns how many columns should be displayed for tile view.
|
|
|
|
*
|
|
|
|
* @param {Object | Function} stateful - The Object or Function that can be
|
|
|
|
* resolved to a Redux state object with the toState function.
|
|
|
|
* @returns {number} - The number of columns to be rendered in tile view.
|
|
|
|
* @private
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function getColumnCount(stateful: IStateful) {
|
2021-08-20 23:32:38 +00:00
|
|
|
const state = toState(stateful);
|
2022-05-06 10:18:57 +00:00
|
|
|
const participantCount = getTileViewParticipantCount(state);
|
2021-08-20 23:32:38 +00:00
|
|
|
const { aspectRatio } = state['features/base/responsive-ui'];
|
|
|
|
|
|
|
|
// For narrow view, tiles should stack on top of each other for a lonely
|
|
|
|
// call and a 1:1 call. Otherwise tiles should be grouped into rows of
|
|
|
|
// two.
|
|
|
|
if (aspectRatio === ASPECT_RATIO_NARROW) {
|
|
|
|
return participantCount >= 3 ? 2 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (participantCount === 4) {
|
|
|
|
// In wide view, a four person call should display as a 2x2 grid.
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
2022-06-27 12:20:58 +00:00
|
|
|
return Math.min(participantCount <= 6 ? 3 : 4, participantCount);
|
2021-08-20 23:32:38 +00:00
|
|
|
}
|
2022-03-15 17:34:46 +00:00
|
|
|
|
|
|
|
/**
|
2022-05-06 10:18:57 +00:00
|
|
|
* Returns true if the filmstrip has a scroll and false otherwise.
|
2022-03-15 17:34:46 +00:00
|
|
|
*
|
|
|
|
* @param {Object} state - The redux state.
|
2022-05-06 10:18:57 +00:00
|
|
|
* @returns {boolean} - True if the scroll is displayed and false otherwise.
|
2022-03-15 17:34:46 +00:00
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function isFilmstripScrollVisible(state: IReduxState) {
|
2022-05-06 10:18:57 +00:00
|
|
|
if (shouldDisplayTileView(state)) {
|
|
|
|
return state['features/filmstrip']?.tileViewDimensions?.hasScroll;
|
|
|
|
}
|
2022-03-15 17:34:46 +00:00
|
|
|
|
2022-05-06 10:18:57 +00:00
|
|
|
const { aspectRatio, clientWidth, clientHeight, safeAreaInsets = {} } = state['features/base/responsive-ui'];
|
|
|
|
const isNarrowAspectRatio = aspectRatio === ASPECT_RATIO_NARROW;
|
|
|
|
const disableSelfView = shouldHideSelfView(state);
|
|
|
|
const localParticipant = Boolean(getLocalParticipant(state));
|
|
|
|
const localParticipantVisible = localParticipant && !disableSelfView;
|
|
|
|
const participantCount
|
|
|
|
= getParticipantCountWithFake(state)
|
|
|
|
- (localParticipant && (shouldDisplayLocalThumbnailSeparately() || disableSelfView) ? 1 : 0);
|
|
|
|
const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
|
|
|
|
const { height, width } = getFilmstripDimensions({
|
|
|
|
aspectRatio,
|
|
|
|
clientWidth,
|
|
|
|
clientHeight,
|
|
|
|
insets: safeAreaInsets,
|
|
|
|
localParticipantVisible
|
|
|
|
});
|
|
|
|
|
|
|
|
if (isNarrowAspectRatio) {
|
|
|
|
return width < (thumbnailWidth + (2 * margin)) * participantCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
return height < (thumbnailHeight + (2 * margin)) * participantCount;
|
2022-03-15 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2022-03-29 10:42:01 +00:00
|
|
|
/**
|
2022-04-12 13:19:10 +00:00
|
|
|
* Whether the stage filmstrip is available or not.
|
2022-03-29 10:42:01 +00:00
|
|
|
*
|
2022-11-10 08:45:56 +00:00
|
|
|
* @param {any} _state - Used on web.
|
|
|
|
* @param {any} _count - Used on web.
|
2022-03-29 10:42:01 +00:00
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function isStageFilmstripAvailable(_state: any, _count?: any) {
|
2022-03-29 10:42:01 +00:00
|
|
|
return false;
|
|
|
|
}
|
2022-04-20 08:00:20 +00:00
|
|
|
|
2022-04-20 09:36:59 +00:00
|
|
|
/**
|
|
|
|
* Whether the stage filmstrip is enabled.
|
|
|
|
*
|
2022-11-10 08:45:56 +00:00
|
|
|
* @param {any} _state - Used on web.
|
2022-04-20 09:36:59 +00:00
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function isStageFilmstripEnabled(_state: any) {
|
2022-04-20 09:36:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
2022-05-06 10:18:57 +00:00
|
|
|
|
2022-08-16 11:56:47 +00:00
|
|
|
/**
|
|
|
|
* Whether or not the top panel is enabled.
|
|
|
|
*
|
2022-11-10 08:45:56 +00:00
|
|
|
* @param {any} _state - Used on web.
|
2022-08-16 11:56:47 +00:00
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function isTopPanelEnabled(_state: any) {
|
2022-08-16 11:56:47 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-05-06 10:18:57 +00:00
|
|
|
/**
|
|
|
|
* Calculates the width and height of the filmstrip based on the screen size and aspect ratio.
|
|
|
|
*
|
|
|
|
* @param {Object} options - The screen aspect ratio, width, height and safe are insets.
|
|
|
|
* @returns {Object} - The width and the height.
|
|
|
|
*/
|
|
|
|
export function getFilmstripDimensions({
|
|
|
|
aspectRatio,
|
|
|
|
clientWidth,
|
|
|
|
clientHeight,
|
|
|
|
insets = {},
|
|
|
|
localParticipantVisible = true
|
2022-11-10 08:45:56 +00:00
|
|
|
}: {
|
|
|
|
aspectRatio: Symbol;
|
|
|
|
clientHeight: number;
|
|
|
|
clientWidth: number;
|
|
|
|
insets: {
|
|
|
|
bottom?: number;
|
|
|
|
left?: number;
|
|
|
|
right?: number;
|
|
|
|
top?: number;
|
|
|
|
};
|
|
|
|
localParticipantVisible?: boolean;
|
2022-05-06 10:18:57 +00:00
|
|
|
}) {
|
|
|
|
const { height, width, margin } = styles.thumbnail;
|
|
|
|
const conferenceBorder = conferenceStyles.conference.borderWidth || 0;
|
|
|
|
const { left = 0, right = 0, top = 0, bottom = 0 } = insets;
|
|
|
|
|
|
|
|
if (aspectRatio === ASPECT_RATIO_NARROW) {
|
|
|
|
return {
|
|
|
|
height,
|
|
|
|
width:
|
|
|
|
(shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
|
|
|
|
? clientWidth - width - (margin * 2) : clientWidth)
|
|
|
|
- left - right - (styles.filmstripNarrow.margin * 2) - (conferenceBorder * 2)
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
height:
|
|
|
|
(shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
|
|
|
|
? clientHeight - height - (margin * 2) : clientHeight)
|
|
|
|
- top - bottom - (conferenceBorder * 2),
|
|
|
|
width
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the local thumbnail should be displayed separately and false otherwise.
|
|
|
|
*
|
|
|
|
* @returns {boolean} - True if the local thumbnail should be displayed separately and flase otherwise.
|
|
|
|
*/
|
|
|
|
export function shouldDisplayLocalThumbnailSeparately() {
|
|
|
|
// XXX Our current design is to have the local participant separate from
|
|
|
|
// the remote participants. Unfortunately, Android's Video
|
|
|
|
// implementation cannot accommodate that because remote participants'
|
|
|
|
// videos appear on top of the local participant's video at times.
|
|
|
|
// That's because Android's Video utilizes EGL and EGL gives us only two
|
|
|
|
// practical layers in which we can place our participants' videos:
|
|
|
|
// layer #0 sits behind the window, creates a hole in the window, and
|
|
|
|
// there we render the LargeVideo; layer #1 is known as media overlay in
|
|
|
|
// EGL terms, renders on top of layer #0, and, consequently, is for the
|
|
|
|
// Filmstrip. With the separate LocalThumbnail, we should have left the
|
|
|
|
// remote participants' Thumbnails in layer #1 and utilized layer #2 for
|
|
|
|
// LocalThumbnail. Unfortunately, layer #2 is not practical (that's why
|
|
|
|
// I said we had two practical layers only) because it renders on top of
|
|
|
|
// everything which in our case means on top of participant-related
|
|
|
|
// indicators such as moderator, audio and video muted, etc. For now we
|
|
|
|
// do not have much of a choice but to continue rendering LocalThumbnail
|
|
|
|
// as any other remote Thumbnail on Android.
|
|
|
|
return Platform.OS !== 'android';
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:56:47 +00:00
|
|
|
/**
|
|
|
|
* Not implemented on mobile.
|
|
|
|
*
|
2022-11-10 08:45:56 +00:00
|
|
|
* @param {any} _state - Used on web.
|
2022-08-16 11:56:47 +00:00
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2022-11-10 08:45:56 +00:00
|
|
|
export function getScreenshareFilmstripParticipantId(_state: any) {
|
2022-08-16 11:56:47 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2022-05-06 10:18:57 +00:00
|
|
|
|