Compare commits

...

1 Commits

Author SHA1 Message Date
Hristo Terezov
a21a515c40 tmp 2021-08-09 16:57:47 -05:00
18 changed files with 603 additions and 332 deletions

View File

@@ -551,7 +551,7 @@ function _updateLocalParticipantInConference({ dispatch, getState }, next, actio
const localParticipant = getLocalParticipant(getState);
if (conference && participant.id === localParticipant.id) {
if (conference && participant.id === localParticipant?.id) {
if ('name' in participant) {
conference.setDisplayName(participant.name);
}

View File

@@ -21,5 +21,5 @@ import './native';
// used it from source. As an intermediate step, start using the library
// lib-jitsi-meet as a binary on mobile at the time of this writing. In the
// future, implement not packaging it in the application bundle.
import JitsiMeetJS from 'lib-jitsi-meet/lib-jitsi-meet.min';
import JitsiMeetJS from 'lib-jitsi-meet/lib-jitsi-meet.js';
export { JitsiMeetJS as default };

View File

@@ -3,3 +3,24 @@ export { default as MiddlewareRegistry } from './MiddlewareRegistry';
export { default as PersistenceRegistry } from './PersistenceRegistry';
export { default as ReducerRegistry } from './ReducerRegistry';
export { default as StateListenerRegistry } from './StateListenerRegistry';
// /**
// *
// */
// MiddlewareRegistry.register(() => next => action => {
// if (performance.mark === undefined) {
// return next(action);
// }
// performance.mark(`${action.type}_start`);
// const result = next(action);
// performance.mark(`${action.type}_end`);
// performance.measure(
// `${action.type}`,
// `${action.type}_start`,
// `${action.type}_end`,
// );
// return result;
// });

View File

@@ -29,12 +29,13 @@ const REDUCED_UI_THRESHOLD = 300;
*/
export function clientResized(clientWidth: number, clientHeight: number) {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const { isOpen: isChatOpen } = state['features/chat'];
const isParticipantsPaneOpen = getParticipantsPaneOpen(state);
let availableWidth = clientWidth;
if (navigator.product !== 'ReactNative') {
const state = getState();
const { isOpen: isChatOpen } = state['features/chat'];
const isParticipantsPaneOpen = getParticipantsPaneOpen(state);
if (isChatOpen) {
availableWidth -= CHAT_SIZE;
}

View File

@@ -0,0 +1,59 @@
// @flow
import {
SET_FILMSTRIP_ENABLED,
SET_FILMSTRIP_VISIBLE,
SET_VISIBLE_REMOTE_PARTICIPANTS
} from './actionTypes';
/**
* Sets whether the filmstrip is enabled.
*
* @param {boolean} enabled - Whether the filmstrip is enabled.
* @returns {{
* type: SET_FILMSTRIP_ENABLED,
* enabled: boolean
* }}
*/
export function setFilmstripEnabled(enabled: boolean) {
return {
type: SET_FILMSTRIP_ENABLED,
enabled
};
}
/**
* Sets whether the filmstrip is visible.
*
* @param {boolean} visible - Whether the filmstrip is visible.
* @returns {{
* type: SET_FILMSTRIP_VISIBLE,
* visible: boolean
* }}
*/
export function setFilmstripVisible(visible: boolean) {
return {
type: SET_FILMSTRIP_VISIBLE,
visible
};
}
/**
* 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
};
}

View File

@@ -1,42 +1,10 @@
// @flow
import {
SET_FILMSTRIP_ENABLED,
SET_FILMSTRIP_VISIBLE,
SET_TILE_VIEW_DIMENSIONS
} from './actionTypes';
import { getParticipantCountWithFake } from '../base/participants';
/**
* Sets whether the filmstrip is enabled.
*
* @param {boolean} enabled - Whether the filmstrip is enabled.
* @returns {{
* type: SET_FILMSTRIP_ENABLED,
* enabled: boolean
* }}
*/
export function setFilmstripEnabled(enabled: boolean) {
return {
type: SET_FILMSTRIP_ENABLED,
enabled
};
}
/**
* Sets whether the filmstrip is visible.
*
* @param {boolean} visible - Whether the filmstrip is visible.
* @returns {{
* type: SET_FILMSTRIP_VISIBLE,
* visible: boolean
* }}
*/
export function setFilmstripVisible(visible: boolean) {
return {
type: SET_FILMSTRIP_VISIBLE,
visible
};
}
import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
import { SQUARE_TILE_ASPECT_RATIO, TILE_MARGIN } from './constants';
import { getColumnCount } from './functions.native';
/**
* Sets the dimensions of the tile view grid. The action is only partially implemented on native as not all
@@ -52,11 +20,40 @@ export function setFilmstripVisible(visible: boolean) {
* dimensions: Object
* }}
*/
export function setTileViewDimensions({ thumbnailSize }: Object) {
return {
type: SET_TILE_VIEW_DIMENSIONS,
dimensions: {
thumbnailSize
export function setTileViewDimensions() {
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,
dimensions: {
columns,
thumbnailSize: {
height: tileHeight,
width: tileWidth
}
}
});
};
}
export * from './actions.any';

View File

@@ -7,7 +7,6 @@ import {
SET_HORIZONTAL_VIEW_DIMENSIONS,
SET_TILE_VIEW_DIMENSIONS,
SET_VERTICAL_VIEW_DIMENSIONS,
SET_VISIBLE_REMOTE_PARTICIPANTS,
SET_VOLUME
} from './actionTypes';
import {
@@ -155,24 +154,4 @@ export function setVolume(participantId: string, volume: number) {
};
}
/**
* 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
};
}
export * from './actions.native';
export * from './actions.any';

View File

@@ -1,17 +1,20 @@
// @flow
import React, { Component } from 'react';
import { SafeAreaView, ScrollView } from 'react-native';
import { FlatList, SafeAreaView, ScrollView } from 'react-native';
import { Platform } from '../../../base/react';
import { connect } from '../../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { setVisibleRemoteParticipants } from '../../actions';
import { isFilmstripVisible } from '../../functions';
import LocalThumbnail from './LocalThumbnail';
import Thumbnail from './Thumbnail';
import styles from './styles';
/**
* Filmstrip component's property types.
*/
@@ -30,7 +33,12 @@ type Props = {
/**
* The indicator which determines whether the filmstrip is visible.
*/
_visible: boolean
_visible: boolean,
/**
* Invoked to trigger state changes in Redux.
*/
dispatch: Dispatch<any>,
};
/**
@@ -74,6 +82,65 @@ class Filmstrip extends Component<Props> {
this._separateLocalThumbnail = Platform.OS !== 'android';
}
/**
*
* @param {*} item
* @returns
*/
_keyExtractor(item) {
return item;
}
/**
*
* @param {*} data
* @param {*} index
* @returns
*/
_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
};
}
/**
* 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 = 0 }) {
console.log(`visisble indexes ${JSON.stringify((viewableItems || []).map(i => i.index))}`);
const indexArray = viewableItems.map(i => i.index);
this.props.dispatch(setVisibleRemoteParticipants(Math.min(indexArray), Math.max(indexArray)));
}
/**
* Creates React Elements to display each participant in a thumbnail. Each
* tile will be.
*
* @private
* @returns {ReactElement[]}
*/
_renderThumbnail({ item, index /*, separators */ }) {
console.log(`1rendering thumbnail with index ${index}`);
return (
<Thumbnail
index = { index }
key = { item }
participantID = { item } />)
;
}
/**
* Implements React's {@link Component#render()}.
*
@@ -89,6 +156,8 @@ class Filmstrip extends Component<Props> {
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
const participants = this._sort(_participants, isNarrowAspectRatio);
const initialRowsToRender = Math.ceil(_height / _thumbnailHeight);
return (
<SafeAreaView style = { filmstripStyle }>
@@ -97,6 +166,18 @@ class Filmstrip extends Component<Props> {
&& !isNarrowAspectRatio
&& <LocalThumbnail />
}
<FlatList
data = { participants }
getItemLayout = { this._getItemLayout }
horizontal = { isNarrowAspectRatio }
initialNumToRender = { initialRowsToRender }
key = { isNarrowAspectRatio ? 'narrow' : 'wide' }
keyExtractor = { this._keyExtractor }
onViewableItemsChanged = { this._onViewableItemsChanged }
renderItem = { this._renderThumbnail }
style = { styles.scrollView }
viewabilityConfig = { this.viewabilityConfig }
windowSize = { 2 } />
<ScrollView
horizontal = { isNarrowAspectRatio }
showsHorizontalScrollIndicator = { false }
@@ -128,6 +209,40 @@ class Filmstrip extends Component<Props> {
);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
const participants = this._getSortedParticipants();
const initialRowsToRender = Math.ceil(_height / _thumbnailHeight);
return (
<TouchableWithoutFeedback onPress = { onClick }>
<FlatList
contentContainerStyle = { styles.tileView }
data = { participants }
horizontal = { false }
initialNumToRender = { initialRowsToRender }
key = { _columns }
keyExtractor = { this._keyExtractor }
numColumns = { _columns }
onViewableItemsChanged = { this._onViewableItemsChanged }
renderItem = { this._renderThumbnail }
style = {{
height: _height,
width: _width
}}
viewabilityConfig = { this.viewabilityConfig }
windowSize = { 2 } />
</TouchableWithoutFeedback>
);
}
/**
* Sorts a specific array of {@code Participant}s in display order.
*

View File

@@ -32,6 +32,8 @@ import RaisedHandIndicator from './RaisedHandIndicator';
import ScreenShareIndicator from './ScreenShareIndicator';
import VideoMutedIndicator from './VideoMutedIndicator';
import styles, { AVATAR_SIZE } from './styles';
import { PureComponent } from 'react';
import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
/**
* Thumbnail component's property types.
@@ -112,103 +114,149 @@ type Props = {
* @param {Props} props - Properties passed to this functional component.
* @returns {Component} - A React component.
*/
function Thumbnail(props: Props) {
const {
_audioMuted: audioMuted,
_largeVideo: largeVideo,
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator,
_participant: participant,
_styles,
_videoTrack: videoTrack,
dispatch,
disableTint,
renderDisplayName,
tileView
} = props;
class Thumbnail extends PureComponent<Props> {
/**
*
* @param {*} props
* @returns
*/
constructor(props: Props) {
super(props);
this._onClick = this._onClick.bind(this);
this._onThumbnailLongPress = this._onThumbnailLongPress.bind(this);
}
/**
* Thumbnail click handler.
*
* @returns {void}
*/
_onClick() {
const { _participantId, _pinned, dispatch, tileView } = this.props;
const participantId = participant.id;
const participantInLargeVideo
= participantId === largeVideo.participantId;
const videoMuted = !videoTrack || videoTrack.muted;
const isScreenShare = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
const onClick = useCallback(() => {
if (tileView) {
dispatch(toggleToolboxVisible());
} else {
dispatch(pinParticipant(participant.pinned ? null : participant.id));
dispatch(pinParticipant(_pinned ? null : _participantId));
}
}, [ participant, tileView, dispatch ]);
const onThumbnailLongPress = useCallback(() => {
if (participant.local) {
}
/**
* Thumbnail long press handler.
*
* @returns {void}
*/
_onThumbnailLongPress() {
const { _participantId, _local, dispatch } = this.props;
if (_local) {
dispatch(openDialog(ConnectionStatusComponent, {
participantID: participant.id
participantID: _participantId
}));
} else {
dispatch(openDialog(RemoteVideoMenu, {
participant
participantId: _participantId
}));
}
}, [ participant, dispatch ]);
}
return (
<Container
onClick = { onClick }
onLongPress = { onThumbnailLongPress }
style = { [
styles.thumbnail,
participant.pinned && !tileView
? _styles.thumbnailPinned : null,
props.styleOverrides || null
] }
touchFeedback = { false }>
/**
*
* @returns
*/
render() {
const {
_audioMuted: audioMuted,
_isScreenShare: isScreenShare,
_isFakeParticipant,
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator,
_participantId: participantId,
_participantInLargeVideo: participantInLargeVideo,
_pinned,
_styles,
_videoMuted: videoMuted,
disableTint,
height,
index,
renderDisplayName,
tileView
} = this.props;
<ParticipantView
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
disableVideo = { isScreenShare || participant.isFakeParticipant }
participantId = { participantId }
style = { _styles.participantViewStyle }
tintEnabled = { participantInLargeVideo && !disableTint }
tintStyle = { _styles.activeThumbnailTint }
zOrder = { 1 } />
const styleOverrides = tileView ? {
aspectRatio: SQUARE_TILE_ASPECT_RATIO,
flex: 0,
height,
maxHeight: null,
maxWidth: null,
width: null
} : null;
{ renderDisplayName && <Container style = { styles.displayNameContainer }>
<DisplayNameLabel participantId = { participantId } />
</Container> }
console.log(`rendering thumbnail with index ${index}`);
{ renderModeratorIndicator
&& <View style = { styles.moderatorIndicatorContainer }>
<ModeratorIndicator />
</View>}
{ !participant.isFakeParticipant && <View
return (
<Container
onClick = { this._onClick }
onLongPress = { this._onThumbnailLongPress }
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopLeftIndicatorContainer
] }>
<RaisedHandIndicator participantId = { participant.id } />
{ renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
</View> }
{ !participant.isFakeParticipant && <View
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopRightIndicatorContainer
] }>
<ConnectionIndicator participantId = { participant.id } />
</View> }
{ !participant.isFakeParticipant && <Container style = { styles.thumbnailIndicatorContainer }>
{ audioMuted
&& <AudioMutedIndicator /> }
{ videoMuted
&& <VideoMutedIndicator /> }
{ isScreenShare
&& <ScreenShareIndicator /> }
</Container> }
</Container>
);
styles.thumbnail,
_pinned && !tileView ? _styles.thumbnailPinned : null,
styleOverrides
] }
touchFeedback = { false }>
<ParticipantView
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
disableVideo = { isScreenShare || _isFakeParticipant }
participantId = { participantId }
style = { _styles.participantViewStyle }
tintEnabled = { participantInLargeVideo && !disableTint }
tintStyle = { _styles.activeThumbnailTint }
zOrder = { 1 } />
{
renderDisplayName
&& <Container style = { styles.displayNameContainer }>
<DisplayNameLabel participantId = { participantId } />
</Container>
}
{ renderModeratorIndicator
&& <View style = { styles.moderatorIndicatorContainer }>
<ModeratorIndicator />
</View>
}
{
!_isFakeParticipant
&& <View
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopLeftIndicatorContainer
] }>
<RaisedHandIndicator participantId = { participantId } />
{ renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
</View>
}
{
!_isFakeParticipant
&& <View
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopRightIndicatorContainer
] }>
<ConnectionIndicator participantId = { participantId } />
</View>
}
{
!_isFakeParticipant
&& <Container style = { styles.thumbnailIndicatorContainer }>
{ audioMuted && <AudioMutedIndicator /> }
{ videoMuted && <VideoMutedIndicator /> }
{ isScreenShare && <ScreenShareIndicator /> }
</Container>
}
</Container>
);
}
}
/**
@@ -231,20 +279,27 @@ function _mapStateToProps(state, ownProps) {
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
const videoTrack
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
const videoMuted = !videoTrack || videoTrack.muted;
const isScreenShare = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
const participantCount = getParticipantCount(state);
const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
const _isEveryoneModerator = isEveryoneModerator(state);
const renderModeratorIndicator = !_isEveryoneModerator
&& participant && participant.role === PARTICIPANT_ROLE.MODERATOR;
const participantInLargeVideo = id === largeVideo.participantId;
return {
_audioMuted: audioTrack?.muted ?? true,
_largeVideo: largeVideo,
_participant: participant,
_isScreenShare: isScreenShare,
_isFakeParticipant: participant.isFakeParticipant,
_local: participant.local,
_participantInLargeVideo: participantInLargeVideo,
_participantId: id,
_pinned: participant.pinned,
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator,
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
_videoTrack: videoTrack
_videoMuted: videoMuted
};
}

View File

@@ -2,7 +2,7 @@
import React, { Component } from 'react';
import {
ScrollView,
FlatList,
TouchableWithoutFeedback,
View
} from 'react-native';
@@ -10,12 +10,11 @@ import type { Dispatch } from 'redux';
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { setTileViewDimensions } from '../../actions.native';
import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
import Thumbnail from './Thumbnail';
import styles from './styles';
import { setVisibleRemoteParticipants } from '../../actions.web';
/**
* The type of the React {@link Component} props of {@link TileView}.
@@ -27,6 +26,8 @@ type Props = {
*/
_aspectRatio: Symbol,
_columns: number,
/**
* Application's viewport height.
*/
@@ -47,6 +48,8 @@ type Props = {
*/
_remoteParticipants: Array<string>,
_thumbnailHeight: number,
/**
* Application's viewport height.
*/
@@ -63,23 +66,6 @@ type Props = {
onClick: Function
};
/**
* The margin for each side of the tile view. Taken away from the available
* 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.
@@ -87,22 +73,63 @@ const TILE_ASPECT_RATIO = 1;
* @extends Component
*/
class TileView extends Component<Props> {
/**
* Implements React's {@link Component#componentDidMount}.
*
* @inheritdoc
* @param {*} props
*/
componentDidMount() {
this._updateReceiverQuality();
constructor(props: Props) {
super(props);
this._keyExtractor = this._keyExtractor.bind(this);
this._getItemLayout = this._getItemLayout.bind(this);
this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this);
this._getSortedParticipants = this._getSortedParticipants.bind(this);
this._renderThumbnail = this._renderThumbnail.bind(this);
this.viewabilityConfig = {
itemVisiblePercentThreshold: 30
};
this._flatListStyles = {};
}
/**
* Implements React's {@link Component#componentDidUpdate}.
*
* @inheritdoc
* @param {*} item
* @returns
*/
componentDidUpdate() {
this._updateReceiverQuality();
_keyExtractor(item) {
return item;
}
/**
*
* @param {*} data
* @param {*} index
* @returns
*/
_getItemLayout(data, index) {
const { _columns, _thumbnailHeight } = this.props;
const cellHeight = _thumbnailHeight;
return {
length: cellHeight,
offset: cellHeight * Math.floor(index / _columns),
index
};
}
/**
* 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 = 0 }) {
console.log(`visisble indexes ${JSON.stringify((viewableItems || []).map(i => i.index))}`);
const indexArray = viewableItems.map(i => i.index);
this.props.dispatch(setVisibleRemoteParticipants(Math.min(indexArray), Math.max(indexArray)));
}
/**
@@ -112,54 +139,36 @@ class TileView extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _height, _width, onClick } = this.props;
const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
const participants = this._getSortedParticipants();
const initialRowsToRender = Math.ceil(_height / _thumbnailHeight);
if (this._flatListStyles.height !== _height || this._flatListStyles.width !== _width) {
this._flatListStyles = {
height: _height,
width: _width
};
}
return (
<ScrollView
style = {{
...styles.tileView,
height: _height,
width: _width
}}>
<TouchableWithoutFeedback onPress = { onClick }>
<View
style = {{
...styles.tileViewRows,
minHeight: _height,
minWidth: _width
}}>
{ rowElements }
</View>
</TouchableWithoutFeedback>
</ScrollView>
<TouchableWithoutFeedback onPress = { onClick }>
<FlatList
contentContainerStyle = { styles.tileView }
data = { participants }
horizontal = { false }
initialNumToRender = { initialRowsToRender }
key = { _columns }
keyExtractor = { this._keyExtractor }
numColumns = { _columns }
onViewableItemsChanged = { this._onViewableItemsChanged }
renderItem = { this._renderThumbnail }
style = { this._flatListStyles }
viewabilityConfig = { this.viewabilityConfig }
windowSize = { 2 } />
</TouchableWithoutFeedback>
);
}
/**
* 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.
*
@@ -175,63 +184,6 @@ class TileView extends Component<Props> {
return participants;
}
/**
* Calculate the height and width for the tiles.
*
* @private
* @returns {Object}
*/
_getTileDimensions() {
const { _height, _participantCount, _width } = 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
// 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.
@@ -239,43 +191,29 @@ class TileView extends Component<Props> {
* @private
* @returns {ReactElement[]}
*/
_renderThumbnails() {
_renderThumbnail({ item, index /*, separators */ }) {
const { _thumbnailHeight } = this.props;
const styleOverrides = {
aspectRatio: TILE_ASPECT_RATIO,
aspectRatio: SQUARE_TILE_ASPECT_RATIO,
flex: 0,
height: this._getTileDimensions().height,
height: _thumbnailHeight,
maxHeight: null,
maxWidth: null,
width: null
};
return this._getSortedParticipants()
.map(id => (
<Thumbnail
disableTint = { true }
key = { id }
participantID = { id }
renderDisplayName = { true }
styleOverrides = { styleOverrides }
tileView = { true } />));
}
console.log(`1rendering thumbnail with index ${index}`);
/**
* 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
}
}));
return (
<Thumbnail
disableTint = { true }
height = { _thumbnailHeight }
index = { index }
key = { item }
participantID = { item }
renderDisplayName = { true }
tileView = { true } />)
;
}
}
@@ -288,14 +226,18 @@ class TileView extends Component<Props> {
*/
function _mapStateToProps(state) {
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 {
_aspectRatio: responsiveUi.aspectRatio,
_columns: columns,
_height: responsiveUi.clientHeight,
_localParticipant: getLocalParticipant(state),
_participantCount: getParticipantCountWithFake(state),
_remoteParticipants: remoteParticipants,
_thumbnailHeight: height,
_width: responsiveUi.clientWidth
};
}

View File

@@ -125,16 +125,10 @@ export default {
},
tileView: {
alignSelf: 'center'
},
tileViewRows: {
justifyContent: 'center'
},
tileViewRow: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'center',
justifyContent: 'center'
// flexGrow: 1
}
};

View File

@@ -220,3 +220,14 @@ export const HORIZONTAL_FILMSTRIP_MARGIN = 39;
* @type {number}
*/
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;

View File

@@ -3,6 +3,7 @@
import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags';
import { getParticipantCountWithFake } from '../base/participants';
import { toState } from '../base/redux';
import { ASPECT_RATIO_NARROW } from '../base/responsive-ui/constants';
/**
* Returns true if the filmstrip on mobile is visible, false otherwise.
@@ -26,3 +27,30 @@ export function isFilmstripVisible(stateful: Object | Function) {
return getParticipantCountWithFake(state) > 1;
}
/**
* 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);
}

View File

@@ -0,0 +1,24 @@
import { MiddlewareRegistry } from '../base/redux';
import { CLIENT_RESIZED, SET_ASPECT_RATIO } from '../base/responsive-ui/actionTypes';
import { setTileViewDimensions } from './actions';
import './subscriber';
/**
* Middleware that handles widnow dimension changes and updates the aspect ratio.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(({ dispatch }) => next => action => {
const result = next(action);
switch (action.type) {
case CLIENT_RESIZED:
case SET_ASPECT_RATIO:
dispatch(setTileViewDimensions());
break;
}
return result;
});

View File

@@ -158,7 +158,9 @@ ReducerRegistry.register(
}
}
return state;
return {
...state
};
}
case PARTICIPANT_LEFT: {
const { id, local } = action.participant;
@@ -187,7 +189,9 @@ ReducerRegistry.register(
delete state.participantsVolume[id];
return state;
return {
...state
};
}
}

View File

@@ -0,0 +1,41 @@
// @flow
import { getParticipantCountWithFake } from '../base/participants';
import { StateListenerRegistry } from '../base/redux';
import { getTileViewGridDimensions, shouldDisplayTileView } from '../video-layout';
import { setTileViewDimensions } from './actions';
/**
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
*/
StateListenerRegistry.register(
/* selector */ state => {
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)));
}
});

View File

@@ -439,7 +439,7 @@ export function isDialOutEnabled(state: Object): boolean {
*/
export function isSipInviteEnabled(state: Object): boolean {
const { sipInviteUrl } = state['features/base/config'];
const { features = {} } = getLocalParticipant(state);
const { features = {} } = getLocalParticipant(state) || {};
return state['features/base/jwt'].jwt
&& Boolean(sipInviteUrl)

View File

@@ -36,7 +36,7 @@ type Props = {
/**
* The participant for which this menu opened for.
*/
participant: Object,
participantId: String,
/**
* The color-schemed stylesheet of the BottomSheet.
@@ -94,11 +94,11 @@ class RemoteVideoMenu extends PureComponent<Props> {
* @inheritdoc
*/
render() {
const { _disableKick, _disableRemoteMute, _disableGrantModerator, participant } = this.props;
const { _disableKick, _disableRemoteMute, _disableGrantModerator, participantId } = this.props;
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
participantID: participant.id,
participantID: participantId,
styles: this.props._bottomSheetStyles.buttons
};
@@ -143,7 +143,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
* @returns {React$Element}
*/
_renderMenuHeader() {
const { _bottomSheetStyles, participant } = this.props;
const { _bottomSheetStyles, participantId } = this.props;
return (
<View
@@ -151,7 +151,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
_bottomSheetStyles.sheet,
styles.participantNameContainer ] }>
<Avatar
participantId = { participant.id }
participantId = { participantId }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
{ this.props._participantDisplayName }
@@ -171,7 +171,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
*/
function _mapStateToProps(state, ownProps) {
const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true);
const { participant } = ownProps;
const { participantId } = ownProps;
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
let { disableKick } = remoteVideoMenu;
@@ -182,7 +182,7 @@ function _mapStateToProps(state, ownProps) {
_disableKick: Boolean(disableKick),
_disableRemoteMute: Boolean(disableRemoteMute),
_isOpen: isDialogOpen(state, RemoteVideoMenu_),
_participantDisplayName: getParticipantDisplayName(state, participant.id)
_participantDisplayName: getParticipantDisplayName(state, participantId)
};
}