feat(filmstrip-pagination): mobile support.
This commit is contained in:
parent
37acce3764
commit
7dd43d93b6
|
@ -551,7 +551,7 @@ function _updateLocalParticipantInConference({ dispatch, getState }, next, actio
|
||||||
|
|
||||||
const localParticipant = getLocalParticipant(getState);
|
const localParticipant = getLocalParticipant(getState);
|
||||||
|
|
||||||
if (conference && participant.id === localParticipant.id) {
|
if (conference && participant.id === localParticipant?.id) {
|
||||||
if ('name' in participant) {
|
if ('name' in participant) {
|
||||||
conference.setDisplayName(participant.name);
|
conference.setDisplayName(participant.name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,13 @@ const REDUCED_UI_THRESHOLD = 300;
|
||||||
*/
|
*/
|
||||||
export function clientResized(clientWidth: number, clientHeight: number) {
|
export function clientResized(clientWidth: number, clientHeight: number) {
|
||||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||||
const state = getState();
|
|
||||||
const { isOpen: isChatOpen } = state['features/chat'];
|
|
||||||
const isParticipantsPaneOpen = getParticipantsPaneOpen(state);
|
|
||||||
let availableWidth = clientWidth;
|
let availableWidth = clientWidth;
|
||||||
|
|
||||||
if (navigator.product !== 'ReactNative') {
|
if (navigator.product !== 'ReactNative') {
|
||||||
|
const state = getState();
|
||||||
|
const { isOpen: isChatOpen } = state['features/chat'];
|
||||||
|
const isParticipantsPaneOpen = getParticipantsPaneOpen(state);
|
||||||
|
|
||||||
if (isChatOpen) {
|
if (isChatOpen) {
|
||||||
availableWidth -= CHAT_SIZE;
|
availableWidth -= CHAT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { SET_FILMSTRIP_ENABLED, SET_FILMSTRIP_VISIBLE, SET_REMOTE_PARTICIPANTS } from './actionTypes';
|
import {
|
||||||
|
SET_FILMSTRIP_ENABLED,
|
||||||
|
SET_FILMSTRIP_VISIBLE,
|
||||||
|
SET_REMOTE_PARTICIPANTS,
|
||||||
|
SET_VISIBLE_REMOTE_PARTICIPANTS
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether the filmstrip is enabled.
|
* Sets whether the filmstrip is enabled.
|
||||||
|
@ -50,3 +55,23 @@ export function setRemoteParticipants(participants: Array<string>) {
|
||||||
participants
|
participants
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of the visible participants in the filmstrip by storing the start and end index from the remote
|
||||||
|
* participants array.
|
||||||
|
*
|
||||||
|
* @param {number} startIndex - The start index from the remote participants array.
|
||||||
|
* @param {number} endIndex - The end index from the remote participants array.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||||
|
* startIndex: number,
|
||||||
|
* endIndex: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setVisibleRemoteParticipants(startIndex: number, endIndex: number) {
|
||||||
|
return {
|
||||||
|
type: SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||||
|
startIndex,
|
||||||
|
endIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import { getParticipantCountWithFake } from '../base/participants';
|
||||||
|
|
||||||
import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
|
import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
|
||||||
|
import { SQUARE_TILE_ASPECT_RATIO, TILE_MARGIN } from './constants';
|
||||||
|
import { getColumnCount } from './functions.native';
|
||||||
|
|
||||||
export * from './actions.any';
|
export * from './actions.any';
|
||||||
|
|
||||||
|
@ -9,20 +13,40 @@ export * from './actions.any';
|
||||||
* of the values are currently used. Check the description of {@link SET_TILE_VIEW_DIMENSIONS} for the full set
|
* of the values are currently used. Check the description of {@link SET_TILE_VIEW_DIMENSIONS} for the full set
|
||||||
* of properties.
|
* of properties.
|
||||||
*
|
*
|
||||||
* @param {Object} dimensions - The tile view dimensions.
|
* @returns {Function}
|
||||||
* @param {Object} thumbnailSize - The size of an individual video thumbnail.
|
|
||||||
* @param {number} thumbnailSize.height - The height of an individual video thumbnail.
|
|
||||||
* @param {number} thumbnailSize.width - The width of an individual video thumbnail.
|
|
||||||
* @returns {{
|
|
||||||
* type: SET_TILE_VIEW_DIMENSIONS,
|
|
||||||
* dimensions: Object
|
|
||||||
* }}
|
|
||||||
*/
|
*/
|
||||||
export function setTileViewDimensions({ thumbnailSize }: Object) {
|
export function setTileViewDimensions() {
|
||||||
return {
|
return (dispatch: Function, getState: Function) => {
|
||||||
|
const state = getState();
|
||||||
|
const participantCount = getParticipantCountWithFake(state);
|
||||||
|
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
|
||||||
|
const columns = getColumnCount(state);
|
||||||
|
const heightToUse = height - (TILE_MARGIN * 2);
|
||||||
|
const widthToUse = width - (TILE_MARGIN * 2);
|
||||||
|
let tileWidth;
|
||||||
|
|
||||||
|
// If there is going to be at least two rows, ensure that at least two
|
||||||
|
// rows display fully on screen.
|
||||||
|
if (participantCount / columns > 1) {
|
||||||
|
tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
|
||||||
|
} else {
|
||||||
|
tileWidth = Math.min(widthToUse / columns, heightToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileHeight = Math.floor(tileWidth / SQUARE_TILE_ASPECT_RATIO);
|
||||||
|
|
||||||
|
tileWidth = Math.floor(tileWidth);
|
||||||
|
|
||||||
|
|
||||||
|
dispatch({
|
||||||
type: SET_TILE_VIEW_DIMENSIONS,
|
type: SET_TILE_VIEW_DIMENSIONS,
|
||||||
dimensions: {
|
dimensions: {
|
||||||
thumbnailSize
|
columns,
|
||||||
|
thumbnailSize: {
|
||||||
|
height: tileHeight,
|
||||||
|
width: tileWidth
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
SET_HORIZONTAL_VIEW_DIMENSIONS,
|
SET_HORIZONTAL_VIEW_DIMENSIONS,
|
||||||
SET_TILE_VIEW_DIMENSIONS,
|
SET_TILE_VIEW_DIMENSIONS,
|
||||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||||
SET_VISIBLE_REMOTE_PARTICIPANTS,
|
|
||||||
SET_VOLUME
|
SET_VOLUME
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import {
|
import {
|
||||||
|
@ -159,23 +158,3 @@ export function setVolume(participantId: string, volume: number) {
|
||||||
volume
|
volume
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the list of the visible participants in the filmstrip by storing the start and end index from the remote
|
|
||||||
* participants array.
|
|
||||||
*
|
|
||||||
* @param {number} startIndex - The start index from the remote participants array.
|
|
||||||
* @param {number} endIndex - The end index from the remote participants array.
|
|
||||||
* @returns {{
|
|
||||||
* type: SET_VISIBLE_REMOTE_PARTICIPANTS,
|
|
||||||
* startIndex: number,
|
|
||||||
* endIndex: number
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function setVisibleRemoteParticipants(startIndex: number, endIndex: number) {
|
|
||||||
return {
|
|
||||||
type: SET_VISIBLE_REMOTE_PARTICIPANTS,
|
|
||||||
startIndex,
|
|
||||||
endIndex
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { SafeAreaView, ScrollView } from 'react-native';
|
import { FlatList, SafeAreaView } from 'react-native';
|
||||||
|
|
||||||
|
import { getLocalParticipant } from '../../../base/participants';
|
||||||
import { Platform } from '../../../base/react';
|
import { Platform } from '../../../base/react';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||||
|
import { setVisibleRemoteParticipants } from '../../actions';
|
||||||
import { isFilmstripVisible, shouldRemoteVideosBeVisible } from '../../functions';
|
import { isFilmstripVisible, shouldRemoteVideosBeVisible } from '../../functions';
|
||||||
|
|
||||||
import LocalThumbnail from './LocalThumbnail';
|
import LocalThumbnail from './LocalThumbnail';
|
||||||
|
@ -25,6 +27,12 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_aspectRatio: Symbol,
|
_aspectRatio: Symbol,
|
||||||
|
|
||||||
|
_clientWidth: number,
|
||||||
|
|
||||||
|
_clientHeight: number,
|
||||||
|
|
||||||
|
_localParticipantId: string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The participants in the conference.
|
* The participants in the conference.
|
||||||
*/
|
*/
|
||||||
|
@ -33,7 +41,12 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* The indicator which determines whether the filmstrip is visible.
|
* The indicator which determines whether the filmstrip is visible.
|
||||||
*/
|
*/
|
||||||
_visible: boolean
|
_visible: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to trigger state changes in Redux.
|
||||||
|
*/
|
||||||
|
dispatch: Function,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,13 +55,18 @@ type Props = {
|
||||||
*
|
*
|
||||||
* @extends Component
|
* @extends Component
|
||||||
*/
|
*/
|
||||||
class Filmstrip extends Component<Props> {
|
class Filmstrip extends PureComponent<Props> {
|
||||||
/**
|
/**
|
||||||
* Whether the local participant should be rendered separately from the
|
* Whether the local participant should be rendered separately from the
|
||||||
* remote participants i.e. outside of their {@link ScrollView}.
|
* remote participants i.e. outside of their {@link ScrollView}.
|
||||||
*/
|
*/
|
||||||
_separateLocalThumbnail: boolean;
|
_separateLocalThumbnail: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FlatList's viewabilityConfig.
|
||||||
|
*/
|
||||||
|
_viewabilityConfig: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of the component.
|
* Constructor of the component.
|
||||||
*
|
*
|
||||||
|
@ -75,6 +93,107 @@ class Filmstrip extends Component<Props> {
|
||||||
// do not have much of a choice but to continue rendering LocalThumbnail
|
// do not have much of a choice but to continue rendering LocalThumbnail
|
||||||
// as any other remote Thumbnail on Android.
|
// as any other remote Thumbnail on Android.
|
||||||
this._separateLocalThumbnail = Platform.OS !== 'android';
|
this._separateLocalThumbnail = Platform.OS !== 'android';
|
||||||
|
|
||||||
|
this._viewabilityConfig = {
|
||||||
|
itemVisiblePercentThreshold: 30
|
||||||
|
};
|
||||||
|
this._keyExtractor = this._keyExtractor.bind(this);
|
||||||
|
this._getItemLayout = this._getItemLayout.bind(this);
|
||||||
|
this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this);
|
||||||
|
this._renderThumbnail = this._renderThumbnail.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_keyExtractor: string => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a key for a passed item of the list.
|
||||||
|
*
|
||||||
|
* @param {string} item - The user ID.
|
||||||
|
* @returns {string} - The user ID.
|
||||||
|
*/
|
||||||
|
_keyExtractor(item) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the width and height of the filmstrip based on the screen size and aspect ratio.
|
||||||
|
*
|
||||||
|
* @returns {Object} - The width and the height.
|
||||||
|
*/
|
||||||
|
_getDimensions() {
|
||||||
|
const { _aspectRatio, _clientWidth, _clientHeight } = this.props;
|
||||||
|
const { height, width, margin } = styles.thumbnail;
|
||||||
|
|
||||||
|
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
||||||
|
return {
|
||||||
|
height,
|
||||||
|
width: this._separateLocalThumbnail ? _clientWidth - width - (margin * 2) : _clientWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
height: this._separateLocalThumbnail ? _clientHeight - height - (margin * 2) : _clientHeight,
|
||||||
|
width
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_getItemLayout: (?Array<string>, number) => {length: number, offset: number, index: number};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optimization for FlatList. Returns the length, offset and index for an item.
|
||||||
|
*
|
||||||
|
* @param {Array<string>} data - The data array with user IDs.
|
||||||
|
* @param {number} index - The index number of the item.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
_getItemLayout(data, index) {
|
||||||
|
const { _aspectRatio } = this.props;
|
||||||
|
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||||
|
const length = isNarrowAspectRatio ? styles.thumbnail.width : styles.thumbnail.height;
|
||||||
|
|
||||||
|
return {
|
||||||
|
length,
|
||||||
|
offset: length * index,
|
||||||
|
index
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onViewableItemsChanged: Object => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for visible items changes.
|
||||||
|
*
|
||||||
|
* @param {Object} data - The visible items data.
|
||||||
|
* @param {Array<Object>} data.viewableItems - The visible items array.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onViewableItemsChanged({ viewableItems = [] }) {
|
||||||
|
const indexArray: Array<number> = viewableItems.map(i => i.index);
|
||||||
|
|
||||||
|
// If the local video placed at the beginning we need to shift the start index of the remoteParticipants array
|
||||||
|
// with 1 because and in the same time we don't need to adjust the end index because the end index will not be
|
||||||
|
// included.
|
||||||
|
const startIndex
|
||||||
|
= this._separateLocalThumbnail ? Math.min(...indexArray) : Math.max(Math.min(...indexArray) - 1, 0);
|
||||||
|
const endIndex = Math.max(...indexArray) + (this._separateLocalThumbnail ? 1 : 0);
|
||||||
|
|
||||||
|
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderThumbnail: Object => Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates React Element to display each participant in a thumbnail.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderThumbnail({ item /* , index , separators */ }) {
|
||||||
|
return (
|
||||||
|
<Thumbnail
|
||||||
|
key = { item }
|
||||||
|
participantID = { item } />)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,7 +203,7 @@ class Filmstrip extends Component<Props> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _aspectRatio, _participants, _visible } = this.props;
|
const { _aspectRatio, _localParticipantId, _participants, _visible } = this.props;
|
||||||
|
|
||||||
if (!_visible) {
|
if (!_visible) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -92,6 +211,13 @@ class Filmstrip extends Component<Props> {
|
||||||
|
|
||||||
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||||
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
|
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
|
||||||
|
const { height, width } = this._getDimensions();
|
||||||
|
const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
|
||||||
|
const initialNumToRender = Math.ceil(isNarrowAspectRatio
|
||||||
|
? width / (thumbnailWidth + (2 * margin))
|
||||||
|
: height / (thumbnailHeight + (2 * margin))
|
||||||
|
);
|
||||||
|
const participants = this._separateLocalThumbnail ? _participants : [ _localParticipantId, ..._participants ];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style = { filmstripStyle }>
|
<SafeAreaView style = { filmstripStyle }>
|
||||||
|
@ -100,29 +226,20 @@ class Filmstrip extends Component<Props> {
|
||||||
&& !isNarrowAspectRatio
|
&& !isNarrowAspectRatio
|
||||||
&& <LocalThumbnail />
|
&& <LocalThumbnail />
|
||||||
}
|
}
|
||||||
<ScrollView
|
<FlatList
|
||||||
|
data = { participants }
|
||||||
|
getItemLayout = { this._getItemLayout }
|
||||||
horizontal = { isNarrowAspectRatio }
|
horizontal = { isNarrowAspectRatio }
|
||||||
|
initialNumToRender = { initialNumToRender }
|
||||||
|
key = { isNarrowAspectRatio ? 'narrow' : 'wide' }
|
||||||
|
keyExtractor = { this._keyExtractor }
|
||||||
|
onViewableItemsChanged = { this._onViewableItemsChanged }
|
||||||
|
renderItem = { this._renderThumbnail }
|
||||||
showsHorizontalScrollIndicator = { false }
|
showsHorizontalScrollIndicator = { false }
|
||||||
showsVerticalScrollIndicator = { false }
|
showsVerticalScrollIndicator = { false }
|
||||||
style = { styles.scrollView } >
|
style = { styles.scrollView }
|
||||||
{
|
viewabilityConfig = { this._viewabilityConfig }
|
||||||
!this._separateLocalThumbnail && !isNarrowAspectRatio
|
windowSize = { 2 } />
|
||||||
&& <LocalThumbnail />
|
|
||||||
}
|
|
||||||
{
|
|
||||||
|
|
||||||
this._sort(_participants, isNarrowAspectRatio)
|
|
||||||
.map(id => (
|
|
||||||
<Thumbnail
|
|
||||||
key = { id }
|
|
||||||
participantID = { id } />))
|
|
||||||
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!this._separateLocalThumbnail && isNarrowAspectRatio
|
|
||||||
&& <LocalThumbnail />
|
|
||||||
}
|
|
||||||
</ScrollView>
|
|
||||||
{
|
{
|
||||||
this._separateLocalThumbnail && isNarrowAspectRatio
|
this._separateLocalThumbnail && isNarrowAspectRatio
|
||||||
&& <LocalThumbnail />
|
&& <LocalThumbnail />
|
||||||
|
@ -130,35 +247,6 @@ class Filmstrip extends Component<Props> {
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts a specific array of {@code Participant}s in display order.
|
|
||||||
*
|
|
||||||
* @param {Participant[]} participants - The array of {@code Participant}s
|
|
||||||
* to sort in display order.
|
|
||||||
* @param {boolean} isNarrowAspectRatio - Indicates if the aspect ratio is
|
|
||||||
* wide or narrow.
|
|
||||||
* @private
|
|
||||||
* @returns {Participant[]} A new array containing the elements of the
|
|
||||||
* specified {@code participants} array sorted in display order.
|
|
||||||
*/
|
|
||||||
_sort(participants, isNarrowAspectRatio) {
|
|
||||||
// XXX Array.prototype.sort() is not appropriate because (1) it operates
|
|
||||||
// in place and (2) it is not necessarily stable.
|
|
||||||
|
|
||||||
const sortedParticipants = [
|
|
||||||
...participants
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isNarrowAspectRatio) {
|
|
||||||
// When the narrow aspect ratio is used, we want to have the remote
|
|
||||||
// participants from right to left with the newest added/joined to
|
|
||||||
// the leftmost side. The local participant is the leftmost item.
|
|
||||||
sortedParticipants.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortedParticipants;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,9 +259,13 @@ class Filmstrip extends Component<Props> {
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
const { enabled, remoteParticipants } = state['features/filmstrip'];
|
const { enabled, remoteParticipants } = state['features/filmstrip'];
|
||||||
const showRemoteVideos = shouldRemoteVideosBeVisible(state);
|
const showRemoteVideos = shouldRemoteVideosBeVisible(state);
|
||||||
|
const responsiveUI = state['features/base/responsive-ui'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||||
|
_clientHeight: responsiveUI.clientHeight,
|
||||||
|
_clientWidth: responsiveUI.clientWidth,
|
||||||
|
_localParticipantId: getLocalParticipant(state)?.id,
|
||||||
_participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS,
|
_participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS,
|
||||||
_visible: enabled && isFilmstripVisible(state)
|
_visible: enabled && isFilmstripVisible(state)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ import { DisplayNameLabel } from '../../../display-name';
|
||||||
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
|
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
|
||||||
import { RemoteVideoMenu } from '../../../video-menu';
|
import { RemoteVideoMenu } from '../../../video-menu';
|
||||||
import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
|
import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
|
||||||
import SharedVideoMenu
|
import SharedVideoMenu from '../../../video-menu/components/native/SharedVideoMenu';
|
||||||
from '../../../video-menu/components/native/SharedVideoMenu';
|
import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
|
||||||
|
|
||||||
import AudioMutedIndicator from './AudioMutedIndicator';
|
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||||
import DominantSpeakerIndicator from './DominantSpeakerIndicator';
|
import DominantSpeakerIndicator from './DominantSpeakerIndicator';
|
||||||
|
@ -47,9 +47,19 @@ type Props = {
|
||||||
_audioMuted: boolean,
|
_audioMuted: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Redux representation of the state "features/large-video".
|
* Indicates whether the participant is fake.
|
||||||
*/
|
*/
|
||||||
_largeVideo: Object,
|
_isFakeParticipant: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the participant is fake.
|
||||||
|
*/
|
||||||
|
_isScreenShare: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the participant is local.
|
||||||
|
*/
|
||||||
|
_local: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared video local participant owner.
|
* Shared video local participant owner.
|
||||||
|
@ -57,9 +67,22 @@ type Props = {
|
||||||
_localVideoOwner: boolean,
|
_localVideoOwner: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Redux representation of the participant to display.
|
* The ID of the participant obtain from the participant object in Redux.
|
||||||
|
*
|
||||||
|
* NOTE: Generally it should be the same as the participantID prop except the case where the passed
|
||||||
|
* participantID doesn't corespond to any of the existing participants.
|
||||||
*/
|
*/
|
||||||
_participant: Object,
|
_participantId: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the participant is displayed on the large video.
|
||||||
|
*/
|
||||||
|
_participantInLargeVideo: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the participant is pinned or not.
|
||||||
|
*/
|
||||||
|
_pinned: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to show the dominant speaker indicator or not.
|
* Whether to show the dominant speaker indicator or not.
|
||||||
|
@ -77,9 +100,9 @@ type Props = {
|
||||||
_styles: StyleType,
|
_styles: StyleType,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Redux representation of the participant's video track.
|
* Indicates whether the participant is video muted.
|
||||||
*/
|
*/
|
||||||
_videoTrack: Object,
|
_videoMuted: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, there will be no color overlay (tint) on the thumbnail
|
* If true, there will be no color overlay (tint) on the thumbnail
|
||||||
|
@ -93,6 +116,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
dispatch: Dispatch<any>,
|
dispatch: Dispatch<any>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of the thumnail.
|
||||||
|
*/
|
||||||
|
height: ?number,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the participant related to the thumbnail.
|
* The ID of the participant related to the thumbnail.
|
||||||
*/
|
*/
|
||||||
|
@ -103,11 +131,6 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
renderDisplayName: ?boolean,
|
renderDisplayName: ?boolean,
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional styling to add or override on the Thumbnail component root.
|
|
||||||
*/
|
|
||||||
styleOverrides?: Object,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, it tells the thumbnail that it needs to behave differently. E.g. react differently to a single tap.
|
* If true, it tells the thumbnail that it needs to behave differently. E.g. react differently to a single tap.
|
||||||
*/
|
*/
|
||||||
|
@ -116,121 +139,159 @@ type Props = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React component for video thumbnail.
|
* React component for video thumbnail.
|
||||||
*
|
|
||||||
* @param {Props} props - Properties passed to this functional component.
|
|
||||||
* @returns {Component} - A React component.
|
|
||||||
*/
|
*/
|
||||||
function Thumbnail(props: Props) {
|
class Thumbnail extends PureComponent<Props> {
|
||||||
const {
|
|
||||||
_audioMuted: audioMuted,
|
|
||||||
_largeVideo: largeVideo,
|
|
||||||
_localVideoOwner,
|
|
||||||
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
|
||||||
_renderModeratorIndicator: renderModeratorIndicator,
|
|
||||||
_participant: participant,
|
|
||||||
_styles,
|
|
||||||
_videoTrack: videoTrack,
|
|
||||||
dispatch,
|
|
||||||
disableTint,
|
|
||||||
renderDisplayName,
|
|
||||||
tileView
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
// It seems that on leave the Thumbnail for the left participant can be re-rendered.
|
/**
|
||||||
// This will happen when mapStateToProps is executed before the remoteParticipants list in redux is updated.
|
* Creates new Thumbnail component.
|
||||||
if (typeof participant === 'undefined') {
|
*
|
||||||
|
* @param {Props} props - The props of the component.
|
||||||
|
* @returns {Thumbnail}
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
return null;
|
this._onClick = this._onClick.bind(this);
|
||||||
|
this._onThumbnailLongPress = this._onThumbnailLongPress.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const participantId = participant.id;
|
_onClick: () => void;
|
||||||
const participantInLargeVideo
|
|
||||||
= participantId === largeVideo.participantId;
|
/**
|
||||||
const videoMuted = !videoTrack || videoTrack.muted;
|
* Thumbnail click handler.
|
||||||
const isScreenShare = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
*
|
||||||
const onClick = useCallback(() => {
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onClick() {
|
||||||
|
const { _participantId, _pinned, dispatch, tileView } = this.props;
|
||||||
|
|
||||||
if (tileView) {
|
if (tileView) {
|
||||||
dispatch(toggleToolboxVisible());
|
dispatch(toggleToolboxVisible());
|
||||||
} else {
|
} else {
|
||||||
dispatch(pinParticipant(participant.pinned ? null : participant.id));
|
dispatch(pinParticipant(_pinned ? null : _participantId));
|
||||||
}
|
}
|
||||||
}, [ participant, tileView, dispatch ]);
|
}
|
||||||
const onThumbnailLongPress = useCallback(() => {
|
|
||||||
if (participant.local) {
|
_onThumbnailLongPress: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thumbnail long press handler.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onThumbnailLongPress() {
|
||||||
|
const { _participantId, _local, _isFakeParticipant, _localVideoOwner, dispatch } = this.props;
|
||||||
|
|
||||||
|
if (_local) {
|
||||||
dispatch(openDialog(ConnectionStatusComponent, {
|
dispatch(openDialog(ConnectionStatusComponent, {
|
||||||
participantID: participant.id
|
participantID: _participantId
|
||||||
}));
|
}));
|
||||||
} else if (participant.isFakeParticipant) {
|
} else if (_isFakeParticipant) {
|
||||||
if (_localVideoOwner) {
|
if (_localVideoOwner) {
|
||||||
dispatch(openDialog(SharedVideoMenu, {
|
dispatch(openDialog(SharedVideoMenu, {
|
||||||
participant
|
_participantId
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dispatch(openDialog(RemoteVideoMenu, {
|
dispatch(openDialog(RemoteVideoMenu, {
|
||||||
participant
|
participantId: _participantId
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [ participant, dispatch ]);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
_audioMuted: audioMuted,
|
||||||
|
_isScreenShare: isScreenShare,
|
||||||
|
_isFakeParticipant,
|
||||||
|
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
||||||
|
_renderModeratorIndicator: renderModeratorIndicator,
|
||||||
|
_participantId: participantId,
|
||||||
|
_participantInLargeVideo: participantInLargeVideo,
|
||||||
|
_pinned,
|
||||||
|
_styles,
|
||||||
|
_videoMuted: videoMuted,
|
||||||
|
disableTint,
|
||||||
|
height,
|
||||||
|
renderDisplayName,
|
||||||
|
tileView
|
||||||
|
} = this.props;
|
||||||
|
const styleOverrides = tileView ? {
|
||||||
|
aspectRatio: SQUARE_TILE_ASPECT_RATIO,
|
||||||
|
flex: 0,
|
||||||
|
height,
|
||||||
|
maxHeight: null,
|
||||||
|
maxWidth: null,
|
||||||
|
width: null
|
||||||
|
} : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
onClick = { onClick }
|
onClick = { this._onClick }
|
||||||
onLongPress = { onThumbnailLongPress }
|
onLongPress = { this._onThumbnailLongPress }
|
||||||
style = { [
|
style = { [
|
||||||
styles.thumbnail,
|
styles.thumbnail,
|
||||||
participant.pinned && !tileView
|
_pinned && !tileView ? _styles.thumbnailPinned : null,
|
||||||
? _styles.thumbnailPinned : null,
|
styleOverrides
|
||||||
props.styleOverrides || null
|
|
||||||
] }
|
] }
|
||||||
touchFeedback = { false }>
|
touchFeedback = { false }>
|
||||||
|
|
||||||
<ParticipantView
|
<ParticipantView
|
||||||
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
|
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
|
||||||
disableVideo = { isScreenShare || participant.isFakeParticipant }
|
disableVideo = { isScreenShare || _isFakeParticipant }
|
||||||
participantId = { participantId }
|
participantId = { participantId }
|
||||||
style = { _styles.participantViewStyle }
|
style = { _styles.participantViewStyle }
|
||||||
tintEnabled = { participantInLargeVideo && !disableTint }
|
tintEnabled = { participantInLargeVideo && !disableTint }
|
||||||
tintStyle = { _styles.activeThumbnailTint }
|
tintStyle = { _styles.activeThumbnailTint }
|
||||||
zOrder = { 1 } />
|
zOrder = { 1 } />
|
||||||
|
{
|
||||||
{ renderDisplayName && <Container style = { styles.displayNameContainer }>
|
renderDisplayName
|
||||||
|
&& <Container style = { styles.displayNameContainer }>
|
||||||
<DisplayNameLabel participantId = { participantId } />
|
<DisplayNameLabel participantId = { participantId } />
|
||||||
</Container> }
|
</Container>
|
||||||
|
}
|
||||||
{ renderModeratorIndicator
|
{ renderModeratorIndicator
|
||||||
&& <View style = { styles.moderatorIndicatorContainer }>
|
&& <View style = { styles.moderatorIndicatorContainer }>
|
||||||
<ModeratorIndicator />
|
<ModeratorIndicator />
|
||||||
</View>}
|
</View>
|
||||||
|
}
|
||||||
{ !participant.isFakeParticipant && <View
|
{
|
||||||
|
!_isFakeParticipant
|
||||||
|
&& <View
|
||||||
style = { [
|
style = { [
|
||||||
styles.thumbnailTopIndicatorContainer,
|
styles.thumbnailTopIndicatorContainer,
|
||||||
styles.thumbnailTopLeftIndicatorContainer
|
styles.thumbnailTopLeftIndicatorContainer
|
||||||
] }>
|
] }>
|
||||||
<RaisedHandIndicator participantId = { participant.id } />
|
<RaisedHandIndicator participantId = { participantId } />
|
||||||
{ renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
|
{ renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
|
||||||
</View> }
|
</View>
|
||||||
|
}
|
||||||
{ !participant.isFakeParticipant && <View
|
{
|
||||||
|
!_isFakeParticipant
|
||||||
|
&& <View
|
||||||
style = { [
|
style = { [
|
||||||
styles.thumbnailTopIndicatorContainer,
|
styles.thumbnailTopIndicatorContainer,
|
||||||
styles.thumbnailTopRightIndicatorContainer
|
styles.thumbnailTopRightIndicatorContainer
|
||||||
] }>
|
] }>
|
||||||
<ConnectionIndicator participantId = { participant.id } />
|
<ConnectionIndicator participantId = { participantId } />
|
||||||
</View> }
|
</View>
|
||||||
|
}
|
||||||
{ !participant.isFakeParticipant && <Container style = { styles.thumbnailIndicatorContainer }>
|
{
|
||||||
{ audioMuted
|
!_isFakeParticipant
|
||||||
&& <AudioMutedIndicator /> }
|
&& <Container style = { styles.thumbnailIndicatorContainer }>
|
||||||
{ videoMuted
|
{ audioMuted && <AudioMutedIndicator /> }
|
||||||
&& <VideoMutedIndicator /> }
|
{ videoMuted && <VideoMutedIndicator /> }
|
||||||
{ isScreenShare
|
{ isScreenShare && <ScreenShareIndicator /> }
|
||||||
&& <ScreenShareIndicator /> }
|
</Container>
|
||||||
</Container> }
|
}
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,21 +316,28 @@ function _mapStateToProps(state, ownProps) {
|
||||||
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
|
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
|
||||||
const videoTrack
|
const videoTrack
|
||||||
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
|
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
|
||||||
|
const videoMuted = !videoTrack || videoTrack.muted;
|
||||||
|
const isScreenShare = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||||
const participantCount = getParticipantCount(state);
|
const participantCount = getParticipantCount(state);
|
||||||
const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
|
const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
|
||||||
const _isEveryoneModerator = isEveryoneModerator(state);
|
const _isEveryoneModerator = isEveryoneModerator(state);
|
||||||
const renderModeratorIndicator = !_isEveryoneModerator
|
const renderModeratorIndicator = !_isEveryoneModerator
|
||||||
&& participant && participant.role === PARTICIPANT_ROLE.MODERATOR;
|
&& participant && participant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||||
|
const participantInLargeVideo = id === largeVideo.participantId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_audioMuted: audioTrack?.muted ?? true,
|
_audioMuted: audioTrack?.muted ?? true,
|
||||||
_largeVideo: largeVideo,
|
_isScreenShare: isScreenShare,
|
||||||
|
_isFakeParticipant: participant?.isFakeParticipant,
|
||||||
|
_local: participant?.local,
|
||||||
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||||
_participant: participant,
|
_participantInLargeVideo: participantInLargeVideo,
|
||||||
|
_participantId: id,
|
||||||
|
_pinned: participant?.pinned,
|
||||||
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
||||||
_renderModeratorIndicator: renderModeratorIndicator,
|
_renderModeratorIndicator: renderModeratorIndicator,
|
||||||
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
|
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
|
||||||
_videoTrack: videoTrack
|
_videoMuted: videoMuted
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import {
|
import {
|
||||||
ScrollView,
|
FlatList,
|
||||||
TouchableWithoutFeedback,
|
TouchableWithoutFeedback,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
@ -10,13 +10,11 @@ import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
|
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
import { setVisibleRemoteParticipants } from '../../actions.web';
|
||||||
import { setTileViewDimensions } from '../../actions.native';
|
|
||||||
|
|
||||||
import Thumbnail from './Thumbnail';
|
import Thumbnail from './Thumbnail';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@link Component} props of {@link TileView}.
|
* The type of the React {@link Component} props of {@link TileView}.
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +25,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_aspectRatio: Symbol,
|
_aspectRatio: Symbol,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of columns.
|
||||||
|
*/
|
||||||
|
_columns: number,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application's viewport height.
|
* Application's viewport height.
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +50,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_remoteParticipants: Array<string>,
|
_remoteParticipants: Array<string>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The thumbnail height.
|
||||||
|
*/
|
||||||
|
_thumbnailHeight: number,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application's viewport height.
|
* Application's viewport height.
|
||||||
*/
|
*/
|
||||||
|
@ -64,45 +72,81 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The margin for each side of the tile view. Taken away from the available
|
* Implements a React {@link PureComponent} which displays thumbnails in a two
|
||||||
* height and width for the tile container to display in.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
const MARGIN = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The aspect ratio the tiles should display in.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
const TILE_ASPECT_RATIO = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements a React {@link Component} which displays thumbnails in a two
|
|
||||||
* dimensional grid.
|
* dimensional grid.
|
||||||
*
|
*
|
||||||
* @extends Component
|
* @extends PureComponent
|
||||||
*/
|
*/
|
||||||
class TileView extends Component<Props> {
|
class TileView extends PureComponent<Props> {
|
||||||
/**
|
|
||||||
* Implements React's {@link Component#componentDidMount}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
this._updateReceiverQuality();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#componentDidUpdate}.
|
* The FlatList's viewabilityConfig.
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
*/
|
||||||
componentDidUpdate() {
|
_viewabilityConfig: Object;
|
||||||
this._updateReceiverQuality();
|
|
||||||
|
/**
|
||||||
|
* The styles for the FlatList.
|
||||||
|
*/
|
||||||
|
_flatListStyles: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The styles for the content container of the FlatList.
|
||||||
|
*/
|
||||||
|
_contentContainerStyles: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new TileView component.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The props of the component.
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._keyExtractor = this._keyExtractor.bind(this);
|
||||||
|
this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this);
|
||||||
|
this._renderThumbnail = this._renderThumbnail.bind(this);
|
||||||
|
this._viewabilityConfig = {
|
||||||
|
itemVisiblePercentThreshold: 30
|
||||||
|
};
|
||||||
|
this._flatListStyles = {
|
||||||
|
...styles.flatList
|
||||||
|
};
|
||||||
|
this._contentContainerStyles = {
|
||||||
|
...styles.contentContainer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_keyExtractor: string => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a key for a passed item of the list.
|
||||||
|
*
|
||||||
|
* @param {string} item - The user ID.
|
||||||
|
* @returns {string} - The user ID.
|
||||||
|
*/
|
||||||
|
_keyExtractor(item) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onViewableItemsChanged: Object => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for visible items changes.
|
||||||
|
*
|
||||||
|
* @param {Object} data - The visible items data.
|
||||||
|
* @param {Array<Object>} data.viewableItems - The visible items array.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
|
||||||
|
const indexArray = viewableItems.map(i => i.index);
|
||||||
|
|
||||||
|
// We need to shift the start index of the remoteParticipants array with 1 because of the local video placed
|
||||||
|
// at the beginning and in the same time we don't need to adjust the end index because the end index will not be
|
||||||
|
// included.
|
||||||
|
const startIndex = Math.max(Math.min(...indexArray) - 1, 0);
|
||||||
|
const endIndex = Math.max(...indexArray);
|
||||||
|
|
||||||
|
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,54 +156,49 @@ class TileView extends Component<Props> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _height, _width, onClick } = this.props;
|
const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
|
||||||
const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
|
const participants = this._getSortedParticipants();
|
||||||
|
const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));
|
||||||
|
|
||||||
return (
|
if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
|
||||||
<ScrollView
|
this._flatListStyles = {
|
||||||
style = {{
|
...styles.flatList,
|
||||||
...styles.tileView,
|
|
||||||
height: _height,
|
|
||||||
width: _width
|
|
||||||
}}>
|
|
||||||
<TouchableWithoutFeedback onPress = { onClick }>
|
|
||||||
<View
|
|
||||||
style = {{
|
|
||||||
...styles.tileViewRows,
|
|
||||||
minHeight: _height,
|
minHeight: _height,
|
||||||
minWidth: _width
|
minWidth: _width
|
||||||
}}>
|
};
|
||||||
{ rowElements }
|
}
|
||||||
|
|
||||||
|
if (this._contentContainerStyles.minHeight !== _height || this._contentContainerStyles.minWidth !== _width) {
|
||||||
|
this._contentContainerStyles = {
|
||||||
|
...styles.contentContainer,
|
||||||
|
minHeight: _height,
|
||||||
|
minWidth: _width
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback onPress = { onClick }>
|
||||||
|
<View style = { styles.flatListContainer }>
|
||||||
|
<FlatList
|
||||||
|
contentContainerStyle = { this._contentContainerStyles }
|
||||||
|
data = { participants }
|
||||||
|
horizontal = { false }
|
||||||
|
initialNumToRender = { initialRowsToRender }
|
||||||
|
key = { _columns }
|
||||||
|
keyExtractor = { this._keyExtractor }
|
||||||
|
numColumns = { _columns }
|
||||||
|
onViewableItemsChanged = { this._onViewableItemsChanged }
|
||||||
|
renderItem = { this._renderThumbnail }
|
||||||
|
showsHorizontalScrollIndicator = { false }
|
||||||
|
showsVerticalScrollIndicator = { false }
|
||||||
|
style = { this._flatListStyles }
|
||||||
|
viewabilityConfig = { this._viewabilityConfig }
|
||||||
|
windowSize = { 2 } />
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
</ScrollView>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns how many columns should be displayed for tile view.
|
|
||||||
*
|
|
||||||
* @returns {number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getColumnCount() {
|
|
||||||
const participantCount = this.props._participantCount;
|
|
||||||
|
|
||||||
// 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 (this.props._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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.min(3, participantCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all participants with the local participant at the end.
|
* Returns all participants with the local participant at the end.
|
||||||
*
|
*
|
||||||
|
@ -168,114 +207,33 @@ class TileView extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
_getSortedParticipants() {
|
_getSortedParticipants() {
|
||||||
const { _localParticipant, _remoteParticipants } = this.props;
|
const { _localParticipant, _remoteParticipants } = this.props;
|
||||||
const participants = [ ..._remoteParticipants ];
|
const participants = [];
|
||||||
|
|
||||||
_localParticipant && participants.push(_localParticipant.id);
|
_localParticipant && participants.push(_localParticipant.id);
|
||||||
|
|
||||||
return participants;
|
return [ ...participants, ..._remoteParticipants ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderThumbnail: Object => Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the height and width for the tiles.
|
* Creates React Element to display each participant in a thumbnail.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @returns {Object}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
_getTileDimensions() {
|
_renderThumbnail({ item/* , index , separators */ }) {
|
||||||
const { _height, _participantCount, _width } = this.props;
|
const { _thumbnailHeight } = this.props;
|
||||||
const columns = this._getColumnCount();
|
|
||||||
const heightToUse = _height - (MARGIN * 2);
|
|
||||||
const widthToUse = _width - (MARGIN * 2);
|
|
||||||
let tileWidth;
|
|
||||||
|
|
||||||
// If there is going to be at least two rows, ensure that at least two
|
return (
|
||||||
// rows display fully on screen.
|
|
||||||
if (_participantCount / columns > 1) {
|
|
||||||
tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
|
|
||||||
} else {
|
|
||||||
tileWidth = Math.min(widthToUse / columns, heightToUse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
height: tileWidth / TILE_ASPECT_RATIO,
|
|
||||||
width: tileWidth
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Splits a list of thumbnails into React Elements with a maximum of
|
|
||||||
* {@link rowLength} thumbnails in each.
|
|
||||||
*
|
|
||||||
* @param {Array} thumbnails - The list of thumbnails that should be split
|
|
||||||
* into separate row groupings.
|
|
||||||
* @param {number} rowLength - How many thumbnails should be in each row.
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement[]}
|
|
||||||
*/
|
|
||||||
_groupIntoRows(thumbnails, rowLength) {
|
|
||||||
const rowElements = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < thumbnails.length; i++) {
|
|
||||||
if (i % rowLength === 0) {
|
|
||||||
const thumbnailsInRow = thumbnails.slice(i, i + rowLength);
|
|
||||||
|
|
||||||
rowElements.push(
|
|
||||||
<View
|
|
||||||
key = { rowElements.length }
|
|
||||||
style = { styles.tileViewRow }>
|
|
||||||
{ thumbnailsInRow }
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rowElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates React Elements to display each participant in a thumbnail. Each
|
|
||||||
* tile will be.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement[]}
|
|
||||||
*/
|
|
||||||
_renderThumbnails() {
|
|
||||||
const styleOverrides = {
|
|
||||||
aspectRatio: TILE_ASPECT_RATIO,
|
|
||||||
flex: 0,
|
|
||||||
height: this._getTileDimensions().height,
|
|
||||||
maxHeight: null,
|
|
||||||
maxWidth: null,
|
|
||||||
width: null
|
|
||||||
};
|
|
||||||
|
|
||||||
return this._getSortedParticipants()
|
|
||||||
.map(id => (
|
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
disableTint = { true }
|
disableTint = { true }
|
||||||
key = { id }
|
height = { _thumbnailHeight }
|
||||||
participantID = { id }
|
key = { item }
|
||||||
|
participantID = { item }
|
||||||
renderDisplayName = { true }
|
renderDisplayName = { true }
|
||||||
styleOverrides = { styleOverrides }
|
tileView = { true } />)
|
||||||
tileView = { true } />));
|
;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the receiver video quality based on the dimensions of the thumbnails
|
|
||||||
* that are displayed.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_updateReceiverQuality() {
|
|
||||||
const { height, width } = this._getTileDimensions();
|
|
||||||
|
|
||||||
this.props.dispatch(setTileViewDimensions({
|
|
||||||
thumbnailSize: {
|
|
||||||
height,
|
|
||||||
width
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,14 +246,18 @@ class TileView extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
const responsiveUi = state['features/base/responsive-ui'];
|
const responsiveUi = state['features/base/responsive-ui'];
|
||||||
const { remoteParticipants } = state['features/filmstrip'];
|
const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
|
||||||
|
const { height } = tileViewDimensions.thumbnailSize;
|
||||||
|
const { columns } = tileViewDimensions;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_aspectRatio: responsiveUi.aspectRatio,
|
_aspectRatio: responsiveUi.aspectRatio,
|
||||||
|
_columns: columns,
|
||||||
_height: responsiveUi.clientHeight,
|
_height: responsiveUi.clientHeight,
|
||||||
_localParticipant: getLocalParticipant(state),
|
_localParticipant: getLocalParticipant(state),
|
||||||
_participantCount: getParticipantCountWithFake(state),
|
_participantCount: getParticipantCountWithFake(state),
|
||||||
_remoteParticipants: remoteParticipants,
|
_remoteParticipants: remoteParticipants,
|
||||||
|
_thumbnailHeight: height,
|
||||||
_width: responsiveUi.clientWidth
|
_width: responsiveUi.clientWidth
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,15 @@ export const AVATAR_SIZE = 50;
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FlatList content container styles
|
||||||
|
*/
|
||||||
|
contentContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flex: 0
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The display name container.
|
* The display name container.
|
||||||
*/
|
*/
|
||||||
|
@ -52,6 +61,22 @@ export default {
|
||||||
top: 0
|
top: 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The styles for the FlatList container.
|
||||||
|
*/
|
||||||
|
flatListContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
flex: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The styles for the FlatList.
|
||||||
|
*/
|
||||||
|
flatList: {
|
||||||
|
flex: 0
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container of the {@link LocalThumbnail}.
|
* Container of the {@link LocalThumbnail}.
|
||||||
*/
|
*/
|
||||||
|
@ -122,19 +147,6 @@ export default {
|
||||||
|
|
||||||
thumbnailTopRightIndicatorContainer: {
|
thumbnailTopRightIndicatorContainer: {
|
||||||
right: 0
|
right: 0
|
||||||
},
|
|
||||||
|
|
||||||
tileView: {
|
|
||||||
alignSelf: 'center'
|
|
||||||
},
|
|
||||||
|
|
||||||
tileViewRows: {
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
|
|
||||||
tileViewRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -220,3 +220,14 @@ export const HORIZONTAL_FILMSTRIP_MARGIN = 39;
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
export const SHOW_TOOLBAR_CONTEXT_MENU_AFTER = 600;
|
export const SHOW_TOOLBAR_CONTEXT_MENU_AFTER = 600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The margin for each side of the tile view. Taken away from the available
|
||||||
|
* height and width for the tile container to display in.
|
||||||
|
*
|
||||||
|
* NOTE: Mobile specific.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
export const TILE_MARGIN = 10;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags';
|
import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags';
|
||||||
import { getParticipantCountWithFake, getPinnedParticipant } from '../base/participants';
|
import { getParticipantCountWithFake, getPinnedParticipant } from '../base/participants';
|
||||||
import { toState } from '../base/redux';
|
import { toState } from '../base/redux';
|
||||||
|
import { ASPECT_RATIO_NARROW } from '../base/responsive-ui/constants';
|
||||||
|
|
||||||
export * from './functions.any';
|
export * from './functions.any';
|
||||||
|
|
||||||
|
@ -59,3 +60,31 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|
||||||
|
|
||||||
|| disable1On1Mode);
|
|| disable1On1Mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
export function getColumnCount(stateful: Object | Function) {
|
||||||
|
const state = toState(stateful);
|
||||||
|
const participantCount = getParticipantCountWithFake(state);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(3, participantCount);
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
|
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
import { CLIENT_RESIZED, SET_ASPECT_RATIO } from '../base/responsive-ui';
|
||||||
|
|
||||||
|
import { setTileViewDimensions } from './actions';
|
||||||
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
|
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
|
||||||
import './subscriber';
|
import './subscriber';
|
||||||
|
|
||||||
|
@ -10,19 +12,22 @@ import './subscriber';
|
||||||
* The middleware of the feature Filmstrip.
|
* The middleware of the feature Filmstrip.
|
||||||
*/
|
*/
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
if (action.type === PARTICIPANT_LEFT) {
|
||||||
|
updateRemoteParticipantsOnLeave(store, action.participant?.id);
|
||||||
|
}
|
||||||
|
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case CLIENT_RESIZED:
|
||||||
|
case SET_ASPECT_RATIO:
|
||||||
|
store.dispatch(setTileViewDimensions());
|
||||||
|
break;
|
||||||
case PARTICIPANT_JOINED: {
|
case PARTICIPANT_JOINED: {
|
||||||
updateRemoteParticipants(store);
|
updateRemoteParticipants(store);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PARTICIPANT_LEFT: {
|
|
||||||
updateRemoteParticipantsOnLeave(store, action.participant?.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -118,13 +118,9 @@ ReducerRegistry.register(
|
||||||
};
|
};
|
||||||
case SET_REMOTE_PARTICIPANTS: {
|
case SET_REMOTE_PARTICIPANTS: {
|
||||||
state.remoteParticipants = action.participants;
|
state.remoteParticipants = action.participants;
|
||||||
|
|
||||||
// TODO: implement this on mobile.
|
|
||||||
if (navigator.product !== 'ReactNative') {
|
|
||||||
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
|
const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state;
|
||||||
|
|
||||||
state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex + 1));
|
state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex + 1));
|
||||||
}
|
|
||||||
|
|
||||||
return { ...state };
|
return { ...state };
|
||||||
}
|
}
|
||||||
|
@ -167,7 +163,9 @@ ReducerRegistry.register(
|
||||||
}
|
}
|
||||||
delete state.participantsVolume[id];
|
delete state.participantsVolume[id];
|
||||||
|
|
||||||
return state;
|
return {
|
||||||
|
...state
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,42 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import { getParticipantCountWithFake } from '../base/participants';
|
||||||
|
import { StateListenerRegistry } from '../base/redux';
|
||||||
|
import { getTileViewGridDimensions, shouldDisplayTileView } from '../video-layout';
|
||||||
|
|
||||||
|
import { setTileViewDimensions } from './actions';
|
||||||
import './subscriber.any';
|
import './subscriber.any';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
|
||||||
|
*/
|
||||||
|
StateListenerRegistry.register(
|
||||||
|
/* selector */ state => {
|
||||||
|
const participantCount = getParticipantCountWithFake(state);
|
||||||
|
|
||||||
|
if (participantCount < 5) { // the dimensions are updated only when the participant count is lower than 5.
|
||||||
|
return participantCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 4; // make sure we don't update the dimensions.
|
||||||
|
},
|
||||||
|
/* listener */ (_, store) => {
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
if (shouldDisplayTileView(state)) {
|
||||||
|
store.dispatch(setTileViewDimensions());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for changes in the selected layout to calculate the dimensions of the tile view grid and horizontal view.
|
||||||
|
*/
|
||||||
|
StateListenerRegistry.register(
|
||||||
|
/* selector */ state => shouldDisplayTileView(state),
|
||||||
|
/* listener */ (isTileView, store) => {
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
if (isTileView) {
|
||||||
|
store.dispatch(setTileViewDimensions(getTileViewGridDimensions(state)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -439,7 +439,7 @@ export function isDialOutEnabled(state: Object): boolean {
|
||||||
*/
|
*/
|
||||||
export function isSipInviteEnabled(state: Object): boolean {
|
export function isSipInviteEnabled(state: Object): boolean {
|
||||||
const { sipInviteUrl } = state['features/base/config'];
|
const { sipInviteUrl } = state['features/base/config'];
|
||||||
const { features = {} } = getLocalParticipant(state);
|
const { features = {} } = getLocalParticipant(state) || {};
|
||||||
|
|
||||||
return state['features/base/jwt'].jwt
|
return state['features/base/jwt'].jwt
|
||||||
&& Boolean(sipInviteUrl)
|
&& Boolean(sipInviteUrl)
|
||||||
|
|
|
@ -42,9 +42,9 @@ type Props = {
|
||||||
dispatch: Function,
|
dispatch: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The participant for which this menu opened for.
|
* The ID of the participant for which this menu opened for.
|
||||||
*/
|
*/
|
||||||
participant: Object,
|
participantId: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The color-schemed stylesheet of the BottomSheet.
|
* The color-schemed stylesheet of the BottomSheet.
|
||||||
|
@ -79,12 +79,7 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* Display name of the participant retrieved from Redux.
|
* Display name of the participant retrieved from Redux.
|
||||||
*/
|
*/
|
||||||
_participantDisplayName: string,
|
_participantDisplayName: string
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the participant.
|
|
||||||
*/
|
|
||||||
_participantID: ?string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
|
@ -117,12 +112,12 @@ class RemoteVideoMenu extends PureComponent<Props> {
|
||||||
_disableRemoteMute,
|
_disableRemoteMute,
|
||||||
_disableGrantModerator,
|
_disableGrantModerator,
|
||||||
_isParticipantAvailable,
|
_isParticipantAvailable,
|
||||||
participant
|
participantId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const buttonProps = {
|
const buttonProps = {
|
||||||
afterClick: this._onCancel,
|
afterClick: this._onCancel,
|
||||||
showLabel: true,
|
showLabel: true,
|
||||||
participantID: participant.id,
|
participantID: participantId,
|
||||||
styles: this.props._bottomSheetStyles.buttons
|
styles: this.props._bottomSheetStyles.buttons
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,7 +136,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
|
||||||
<PrivateMessageButton { ...buttonProps } />
|
<PrivateMessageButton { ...buttonProps } />
|
||||||
<ConnectionStatusButton { ...buttonProps } />
|
<ConnectionStatusButton { ...buttonProps } />
|
||||||
{/* <Divider style = { styles.divider } />*/}
|
{/* <Divider style = { styles.divider } />*/}
|
||||||
{/* <VolumeSlider participantID = { _participantID } />*/}
|
{/* <VolumeSlider participantID = { participantId } />*/}
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -172,7 +167,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
|
||||||
* @returns {React$Element}
|
* @returns {React$Element}
|
||||||
*/
|
*/
|
||||||
_renderMenuHeader() {
|
_renderMenuHeader() {
|
||||||
const { _bottomSheetStyles, participant } = this.props;
|
const { _bottomSheetStyles, participantId } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -180,7 +175,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
|
||||||
_bottomSheetStyles.sheet,
|
_bottomSheetStyles.sheet,
|
||||||
styles.participantNameContainer ] }>
|
styles.participantNameContainer ] }>
|
||||||
<Avatar
|
<Avatar
|
||||||
participantId = { participant.id }
|
participantId = { participantId }
|
||||||
size = { AVATAR_SIZE } />
|
size = { AVATAR_SIZE } />
|
||||||
<Text style = { styles.participantNameLabel }>
|
<Text style = { styles.participantNameLabel }>
|
||||||
{ this.props._participantDisplayName }
|
{ this.props._participantDisplayName }
|
||||||
|
@ -200,9 +195,9 @@ class RemoteVideoMenu extends PureComponent<Props> {
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state, ownProps) {
|
function _mapStateToProps(state, ownProps) {
|
||||||
const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true);
|
const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true);
|
||||||
const { participant } = ownProps;
|
const { participantId } = ownProps;
|
||||||
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
|
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
|
||||||
const isParticipantAvailable = getParticipantById(state, participant.id);
|
const isParticipantAvailable = getParticipantById(state, participantId);
|
||||||
let { disableKick } = remoteVideoMenu;
|
let { disableKick } = remoteVideoMenu;
|
||||||
|
|
||||||
disableKick = disableKick || !kickOutEnabled;
|
disableKick = disableKick || !kickOutEnabled;
|
||||||
|
@ -213,8 +208,7 @@ function _mapStateToProps(state, ownProps) {
|
||||||
_disableRemoteMute: Boolean(disableRemoteMute),
|
_disableRemoteMute: Boolean(disableRemoteMute),
|
||||||
_isOpen: isDialogOpen(state, RemoteVideoMenu_),
|
_isOpen: isDialogOpen(state, RemoteVideoMenu_),
|
||||||
_isParticipantAvailable: Boolean(isParticipantAvailable),
|
_isParticipantAvailable: Boolean(isParticipantAvailable),
|
||||||
_participantDisplayName: getParticipantDisplayName(state, participant.id),
|
_participantDisplayName: getParticipantDisplayName(state, participantId)
|
||||||
_participantID: participant.id
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,9 @@ type Props = {
|
||||||
dispatch: Function,
|
dispatch: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The participant for which this menu opened for.
|
* The ID of the participant for which this menu opened for.
|
||||||
*/
|
*/
|
||||||
participant: Object,
|
participantId: string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The color-schemed stylesheet of the BottomSheet.
|
* The color-schemed stylesheet of the BottomSheet.
|
||||||
|
@ -55,11 +55,6 @@ type Props = {
|
||||||
* Display name of the participant retrieved from Redux.
|
* Display name of the participant retrieved from Redux.
|
||||||
*/
|
*/
|
||||||
_participantDisplayName: string,
|
_participantDisplayName: string,
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the participant.
|
|
||||||
*/
|
|
||||||
_participantID: ?string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
|
@ -89,13 +84,13 @@ class SharedVideoMenu extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
_isParticipantAvailable,
|
_isParticipantAvailable,
|
||||||
participant
|
participantId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const buttonProps = {
|
const buttonProps = {
|
||||||
afterClick: this._onCancel,
|
afterClick: this._onCancel,
|
||||||
showLabel: true,
|
showLabel: true,
|
||||||
participantID: participant.id,
|
participantID: participantId,
|
||||||
styles: this.props._bottomSheetStyles.buttons
|
styles: this.props._bottomSheetStyles.buttons
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,7 +131,7 @@ class SharedVideoMenu extends PureComponent<Props> {
|
||||||
* @returns {React$Element}
|
* @returns {React$Element}
|
||||||
*/
|
*/
|
||||||
_renderMenuHeader() {
|
_renderMenuHeader() {
|
||||||
const { _bottomSheetStyles, participant } = this.props;
|
const { _bottomSheetStyles, participantId } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -144,7 +139,7 @@ class SharedVideoMenu extends PureComponent<Props> {
|
||||||
_bottomSheetStyles.sheet,
|
_bottomSheetStyles.sheet,
|
||||||
styles.participantNameContainer ] }>
|
styles.participantNameContainer ] }>
|
||||||
<Avatar
|
<Avatar
|
||||||
participantId = { participant.id }
|
participantId = { participantId }
|
||||||
size = { AVATAR_SIZE } />
|
size = { AVATAR_SIZE } />
|
||||||
<Text style = { styles.participantNameLabel }>
|
<Text style = { styles.participantNameLabel }>
|
||||||
{ this.props._participantDisplayName }
|
{ this.props._participantDisplayName }
|
||||||
|
@ -163,15 +158,14 @@ class SharedVideoMenu extends PureComponent<Props> {
|
||||||
* @returns {Props}
|
* @returns {Props}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state, ownProps) {
|
function _mapStateToProps(state, ownProps) {
|
||||||
const { participant } = ownProps;
|
const { participantId } = ownProps;
|
||||||
const isParticipantAvailable = getParticipantById(state, participant.id);
|
const isParticipantAvailable = getParticipantById(state, participantId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
|
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
|
||||||
_isOpen: isDialogOpen(state, SharedVideoMenu_),
|
_isOpen: isDialogOpen(state, SharedVideoMenu_),
|
||||||
_isParticipantAvailable: Boolean(isParticipantAvailable),
|
_isParticipantAvailable: Boolean(isParticipantAvailable),
|
||||||
_participantDisplayName: getParticipantDisplayName(state, participant.id),
|
_participantDisplayName: getParticipantDisplayName(state, participantId)
|
||||||
_participantID: participant.id
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -191,12 +191,7 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
const { maxReceiverVideoQuality, preferredVideoQuality } = state['features/video-quality'];
|
const { maxReceiverVideoQuality, 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 maxFrameHeight = Math.min(maxReceiverVideoQuality, preferredVideoQuality);
|
||||||
let { visibleRemoteParticipants } = state['features/filmstrip'];
|
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||||
|
|
||||||
// TODO: implement this on mobile.
|
|
||||||
if (navigator.product === 'ReactNative') {
|
|
||||||
visibleRemoteParticipants = new Set(Array.from(state['features/base/participants'].remote.keys()));
|
|
||||||
}
|
|
||||||
|
|
||||||
const receiverConstraints = {
|
const receiverConstraints = {
|
||||||
constraints: {},
|
constraints: {},
|
||||||
|
|
Loading…
Reference in New Issue