2017-11-03 20:14:38 +00:00
|
|
|
// @flow
|
2017-05-24 17:01:46 +00:00
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
import React, { Component } from 'react';
|
2016-10-31 17:33:32 +00:00
|
|
|
import { ScrollView } from 'react-native';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2018-05-25 11:14:29 +00:00
|
|
|
import { Container, Platform } from '../../../base/react';
|
2019-03-21 16:38:29 +00:00
|
|
|
import { connect } from '../../../base/redux';
|
2020-06-02 09:03:17 +00:00
|
|
|
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
2018-06-14 09:14:32 +00:00
|
|
|
import { isFilmstripVisible } from '../../functions';
|
|
|
|
|
2018-04-26 12:44:23 +00:00
|
|
|
import LocalThumbnail from './LocalThumbnail';
|
2018-02-06 18:14:05 +00:00
|
|
|
import Thumbnail from './Thumbnail';
|
2020-05-20 10:57:03 +00:00
|
|
|
import styles from './styles';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
/**
|
2018-02-06 09:40:16 +00:00
|
|
|
* Filmstrip component's property types.
|
2016-10-05 14:36:59 +00:00
|
|
|
*/
|
2018-02-06 09:40:16 +00:00
|
|
|
type Props = {
|
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
/**
|
|
|
|
* Application's aspect ratio.
|
|
|
|
*/
|
|
|
|
_aspectRatio: Symbol,
|
|
|
|
|
2016-12-01 01:52:39 +00:00
|
|
|
/**
|
2018-02-06 09:40:16 +00:00
|
|
|
* The indicator which determines whether the filmstrip is enabled.
|
2016-12-01 01:52:39 +00:00
|
|
|
*/
|
2018-02-06 09:40:16 +00:00
|
|
|
_enabled: boolean,
|
2017-03-31 17:54:58 +00:00
|
|
|
|
2018-02-06 09:40:16 +00:00
|
|
|
/**
|
|
|
|
* The participants in the conference.
|
|
|
|
*/
|
|
|
|
_participants: Array<any>,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The indicator which determines whether the filmstrip is visible.
|
|
|
|
*/
|
|
|
|
_visible: boolean
|
|
|
|
};
|
2016-12-01 01:52:39 +00:00
|
|
|
|
2018-02-06 09:40:16 +00:00
|
|
|
/**
|
|
|
|
* Implements a React {@link Component} which represents the filmstrip on
|
|
|
|
* mobile/React Native.
|
|
|
|
*
|
|
|
|
* @extends Component
|
|
|
|
*/
|
|
|
|
class Filmstrip extends Component<Props> {
|
2018-05-25 11:14:29 +00:00
|
|
|
/**
|
|
|
|
* Whether the local participant should be rendered separately from the
|
|
|
|
* remote participants i.e. outside of their {@link ScrollView}.
|
|
|
|
*/
|
|
|
|
_separateLocalThumbnail: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor of the component.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
// XXX Our current design is to have the local participant separate from
|
|
|
|
// the remote participants. Unfortunately, Android's Video
|
|
|
|
// implementation cannot accommodate that because remote participants'
|
|
|
|
// videos appear on top of the local participant's video at times.
|
|
|
|
// That's because Android's Video utilizes EGL and EGL gives us only two
|
|
|
|
// practical layers in which we can place our participants' videos:
|
|
|
|
// layer #0 sits behind the window, creates a hole in the window, and
|
|
|
|
// there we render the LargeVideo; layer #1 is known as media overlay in
|
|
|
|
// EGL terms, renders on top of layer #0, and, consequently, is for the
|
|
|
|
// Filmstrip. With the separate LocalThumnail, we should have left the
|
|
|
|
// remote participants' Thumbnails in layer #1 and utilized layer #2 for
|
|
|
|
// LocalThumbnail. Unfortunately, layer #2 is not practical (that's why
|
|
|
|
// I said we had two practical layers only) because it renders on top of
|
|
|
|
// everything which in our case means on top of participant-related
|
|
|
|
// indicators such as moderator, audio and video muted, etc. For now we
|
|
|
|
// do not have much of a choice but to continue rendering LocalThumbnail
|
|
|
|
// as any other remote Thumbnail on Android.
|
|
|
|
this._separateLocalThumbnail = Platform.OS !== 'android';
|
|
|
|
}
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#render()}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
2017-05-24 17:01:46 +00:00
|
|
|
* @returns {ReactElement}
|
2016-10-05 14:36:59 +00:00
|
|
|
*/
|
|
|
|
render() {
|
2020-06-02 09:03:17 +00:00
|
|
|
const { _aspectRatio, _enabled, _participants, _visible } = this.props;
|
|
|
|
|
|
|
|
if (!_enabled) {
|
2018-02-06 09:40:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
|
|
|
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
|
2017-10-13 16:13:46 +00:00
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
return (
|
|
|
|
<Container
|
2017-10-13 16:13:46 +00:00
|
|
|
style = { filmstripStyle }
|
2020-06-02 09:03:17 +00:00
|
|
|
visible = { _visible }>
|
2018-04-26 12:44:23 +00:00
|
|
|
{
|
2018-05-25 11:14:29 +00:00
|
|
|
this._separateLocalThumbnail
|
2020-06-02 09:03:17 +00:00
|
|
|
&& !isNarrowAspectRatio
|
2018-05-25 11:14:29 +00:00
|
|
|
&& <LocalThumbnail />
|
2018-04-26 12:44:23 +00:00
|
|
|
}
|
2016-10-31 17:33:32 +00:00
|
|
|
<ScrollView
|
2020-06-02 09:03:17 +00:00
|
|
|
horizontal = { isNarrowAspectRatio }
|
2016-10-31 17:33:32 +00:00
|
|
|
showsHorizontalScrollIndicator = { false }
|
2018-04-26 12:44:23 +00:00
|
|
|
showsVerticalScrollIndicator = { false }
|
|
|
|
style = { styles.scrollView } >
|
2018-05-25 11:14:29 +00:00
|
|
|
{
|
2020-06-02 09:03:17 +00:00
|
|
|
!this._separateLocalThumbnail && !isNarrowAspectRatio
|
2018-05-25 11:14:29 +00:00
|
|
|
&& <LocalThumbnail />
|
|
|
|
}
|
2016-10-31 17:33:32 +00:00
|
|
|
{
|
2017-10-02 23:08:07 +00:00
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
this._sort(_participants, isNarrowAspectRatio)
|
2018-08-06 16:09:32 +00:00
|
|
|
.map(p => (
|
2016-10-31 17:33:32 +00:00
|
|
|
<Thumbnail
|
|
|
|
key = { p.id }
|
2018-08-06 16:09:32 +00:00
|
|
|
participant = { p } />))
|
2017-10-02 23:08:07 +00:00
|
|
|
|
2016-10-31 17:33:32 +00:00
|
|
|
}
|
2018-05-25 11:14:29 +00:00
|
|
|
{
|
2020-06-02 09:03:17 +00:00
|
|
|
!this._separateLocalThumbnail && isNarrowAspectRatio
|
2018-05-25 11:14:29 +00:00
|
|
|
&& <LocalThumbnail />
|
|
|
|
}
|
2016-10-31 17:33:32 +00:00
|
|
|
</ScrollView>
|
2018-04-26 12:44:23 +00:00
|
|
|
{
|
2020-06-02 09:03:17 +00:00
|
|
|
this._separateLocalThumbnail && isNarrowAspectRatio
|
2018-05-25 11:14:29 +00:00
|
|
|
&& <LocalThumbnail />
|
2018-04-26 12:44:23 +00:00
|
|
|
}
|
2016-10-05 14:36:59 +00:00
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
}
|
2016-11-04 18:13:26 +00:00
|
|
|
|
|
|
|
/**
|
2017-10-01 06:35:19 +00:00
|
|
|
* Sorts a specific array of {@code Participant}s in display order.
|
2016-11-04 18:13:26 +00:00
|
|
|
*
|
2017-10-01 06:35:19 +00:00
|
|
|
* @param {Participant[]} participants - The array of {@code Participant}s
|
2016-11-04 18:13:26 +00:00
|
|
|
* to sort in display order.
|
2020-06-02 09:03:17 +00:00
|
|
|
* @param {boolean} isNarrowAspectRatio - Indicates if the aspect ratio is
|
2017-12-14 10:38:27 +00:00
|
|
|
* wide or narrow.
|
2016-11-04 18:13:26 +00:00
|
|
|
* @private
|
|
|
|
* @returns {Participant[]} A new array containing the elements of the
|
2017-10-01 06:35:19 +00:00
|
|
|
* specified {@code participants} array sorted in display order.
|
2016-11-04 18:13:26 +00:00
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
_sort(participants, isNarrowAspectRatio) {
|
2016-11-04 18:13:26 +00:00
|
|
|
// XXX Array.prototype.sort() is not appropriate because (1) it operates
|
|
|
|
// in place and (2) it is not necessarily stable.
|
|
|
|
|
2017-12-14 10:38:27 +00:00
|
|
|
const sortedParticipants = [
|
2018-04-26 12:44:23 +00:00
|
|
|
...participants
|
2017-12-14 10:38:27 +00:00
|
|
|
];
|
2016-11-04 18:13:26 +00:00
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
if (isNarrowAspectRatio) {
|
2017-12-14 10:38:27 +00:00
|
|
|
// 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();
|
2016-11-04 18:13:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return sortedParticipants;
|
|
|
|
}
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-12 17:42:38 +00:00
|
|
|
* Maps (parts of) the redux state to the associated {@code Filmstrip}'s props.
|
2016-10-05 14:36:59 +00:00
|
|
|
*
|
2018-02-12 17:42:38 +00:00
|
|
|
* @param {Object} state - The redux state.
|
2017-01-28 23:34:57 +00:00
|
|
|
* @private
|
2020-06-02 09:03:17 +00:00
|
|
|
* @returns {Props}
|
2016-10-05 14:36:59 +00:00
|
|
|
*/
|
2017-01-28 23:34:57 +00:00
|
|
|
function _mapStateToProps(state) {
|
2017-10-13 16:13:46 +00:00
|
|
|
const participants = state['features/base/participants'];
|
2018-06-14 09:14:32 +00:00
|
|
|
const { enabled } = state['features/filmstrip'];
|
2017-10-13 16:13:46 +00:00
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
return {
|
2020-06-02 09:03:17 +00:00
|
|
|
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
2018-02-06 09:40:16 +00:00
|
|
|
_enabled: enabled,
|
2018-04-26 12:44:23 +00:00
|
|
|
_participants: participants.filter(p => !p.local),
|
2018-06-14 09:14:32 +00:00
|
|
|
_visible: isFilmstripVisible(state)
|
2016-10-05 14:36:59 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
export default connect(_mapStateToProps)(Filmstrip);
|