You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { ScrollView } from 'react-native';
  4. import { Container, Platform } from '../../../base/react';
  5. import { connect } from '../../../base/redux';
  6. import {
  7. isNarrowAspectRatio,
  8. makeAspectRatioAware
  9. } from '../../../base/responsive-ui';
  10. import { isFilmstripVisible } from '../../functions';
  11. import LocalThumbnail from './LocalThumbnail';
  12. import styles from './styles';
  13. import Thumbnail from './Thumbnail';
  14. /**
  15. * Filmstrip component's property types.
  16. */
  17. type Props = {
  18. /**
  19. * The indicator which determines whether the filmstrip is enabled.
  20. *
  21. * @private
  22. */
  23. _enabled: boolean,
  24. /**
  25. * The participants in the conference.
  26. *
  27. * @private
  28. */
  29. _participants: Array<any>,
  30. /**
  31. * The indicator which determines whether the filmstrip is visible.
  32. *
  33. * @private
  34. */
  35. _visible: boolean
  36. };
  37. /**
  38. * Implements a React {@link Component} which represents the filmstrip on
  39. * mobile/React Native.
  40. *
  41. * @extends Component
  42. */
  43. class Filmstrip extends Component<Props> {
  44. /**
  45. * Whether the local participant should be rendered separately from the
  46. * remote participants i.e. outside of their {@link ScrollView}.
  47. */
  48. _separateLocalThumbnail: boolean;
  49. /**
  50. * Constructor of the component.
  51. *
  52. * @inheritdoc
  53. */
  54. constructor(props) {
  55. super(props);
  56. // XXX Our current design is to have the local participant separate from
  57. // the remote participants. Unfortunately, Android's Video
  58. // implementation cannot accommodate that because remote participants'
  59. // videos appear on top of the local participant's video at times.
  60. // That's because Android's Video utilizes EGL and EGL gives us only two
  61. // practical layers in which we can place our participants' videos:
  62. // layer #0 sits behind the window, creates a hole in the window, and
  63. // there we render the LargeVideo; layer #1 is known as media overlay in
  64. // EGL terms, renders on top of layer #0, and, consequently, is for the
  65. // Filmstrip. With the separate LocalThumnail, we should have left the
  66. // remote participants' Thumbnails in layer #1 and utilized layer #2 for
  67. // LocalThumbnail. Unfortunately, layer #2 is not practical (that's why
  68. // I said we had two practical layers only) because it renders on top of
  69. // everything which in our case means on top of participant-related
  70. // indicators such as moderator, audio and video muted, etc. For now we
  71. // do not have much of a choice but to continue rendering LocalThumbnail
  72. // as any other remote Thumbnail on Android.
  73. this._separateLocalThumbnail = Platform.OS !== 'android';
  74. }
  75. /**
  76. * Implements React's {@link Component#render()}.
  77. *
  78. * @inheritdoc
  79. * @returns {ReactElement}
  80. */
  81. render() {
  82. if (!this.props._enabled) {
  83. return null;
  84. }
  85. const isNarrowAspectRatio_ = isNarrowAspectRatio(this);
  86. const filmstripStyle
  87. = isNarrowAspectRatio_
  88. ? styles.filmstripNarrow
  89. : styles.filmstripWide;
  90. return (
  91. <Container
  92. style = { filmstripStyle }
  93. visible = { this.props._visible }>
  94. {
  95. this._separateLocalThumbnail
  96. && !isNarrowAspectRatio_
  97. && <LocalThumbnail />
  98. }
  99. <ScrollView
  100. horizontal = { isNarrowAspectRatio_ }
  101. showsHorizontalScrollIndicator = { false }
  102. showsVerticalScrollIndicator = { false }
  103. style = { styles.scrollView } >
  104. {
  105. !this._separateLocalThumbnail
  106. && !isNarrowAspectRatio_
  107. && <LocalThumbnail />
  108. }
  109. {
  110. this._sort(
  111. this.props._participants,
  112. isNarrowAspectRatio_)
  113. .map(p => (
  114. <Thumbnail
  115. key = { p.id }
  116. participant = { p } />))
  117. }
  118. {
  119. !this._separateLocalThumbnail
  120. && isNarrowAspectRatio_
  121. && <LocalThumbnail />
  122. }
  123. </ScrollView>
  124. {
  125. this._separateLocalThumbnail
  126. && isNarrowAspectRatio_
  127. && <LocalThumbnail />
  128. }
  129. </Container>
  130. );
  131. }
  132. /**
  133. * Sorts a specific array of {@code Participant}s in display order.
  134. *
  135. * @param {Participant[]} participants - The array of {@code Participant}s
  136. * to sort in display order.
  137. * @param {boolean} isNarrowAspectRatio_ - Indicates if the aspect ratio is
  138. * wide or narrow.
  139. * @private
  140. * @returns {Participant[]} A new array containing the elements of the
  141. * specified {@code participants} array sorted in display order.
  142. */
  143. _sort(participants, isNarrowAspectRatio_) {
  144. // XXX Array.prototype.sort() is not appropriate because (1) it operates
  145. // in place and (2) it is not necessarily stable.
  146. const sortedParticipants = [
  147. ...participants
  148. ];
  149. if (isNarrowAspectRatio_) {
  150. // When the narrow aspect ratio is used, we want to have the remote
  151. // participants from right to left with the newest added/joined to
  152. // the leftmost side. The local participant is the leftmost item.
  153. sortedParticipants.reverse();
  154. }
  155. return sortedParticipants;
  156. }
  157. }
  158. /**
  159. * Maps (parts of) the redux state to the associated {@code Filmstrip}'s props.
  160. *
  161. * @param {Object} state - The redux state.
  162. * @private
  163. * @returns {{
  164. * _participants: Participant[],
  165. * _visible: boolean
  166. * }}
  167. */
  168. function _mapStateToProps(state) {
  169. const participants = state['features/base/participants'];
  170. const { enabled } = state['features/filmstrip'];
  171. return {
  172. /**
  173. * The indicator which determines whether the filmstrip is enabled.
  174. *
  175. * @private
  176. * @type {boolean}
  177. */
  178. _enabled: enabled,
  179. /**
  180. * The remote participants in the conference.
  181. *
  182. * @private
  183. * @type {Participant[]}
  184. */
  185. _participants: participants.filter(p => !p.local),
  186. /**
  187. * The indicator which determines whether the filmstrip is visible. The
  188. * mobile/react-native Filmstrip is visible when there are at least 2
  189. * participants in the conference (including the local one).
  190. *
  191. * @private
  192. * @type {boolean}
  193. */
  194. _visible: isFilmstripVisible(state)
  195. };
  196. }
  197. export default connect(_mapStateToProps)(makeAspectRatioAware(Filmstrip));