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.

functions.native.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // @flow
  2. import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags';
  3. import {
  4. getLocalParticipant,
  5. getParticipantCountWithFake,
  6. getPinnedParticipant
  7. } from '../base/participants';
  8. import { Platform } from '../base/react';
  9. import { toState } from '../base/redux';
  10. import { ASPECT_RATIO_NARROW } from '../base/responsive-ui/constants';
  11. import { shouldHideSelfView } from '../base/settings/functions.any';
  12. import conferenceStyles from '../conference/components/native/styles';
  13. import { shouldDisplayTileView } from '../video-layout';
  14. import { styles } from './components';
  15. export * from './functions.any';
  16. /**
  17. * Returns true if the filmstrip on mobile is visible, false otherwise.
  18. *
  19. * NOTE: Filmstrip on mobile behaves differently to web, and is only visible
  20. * when there are at least 2 participants.
  21. *
  22. * @param {Object | Function} stateful - The Object or Function that can be
  23. * resolved to a Redux state object with the toState function.
  24. * @returns {boolean}
  25. */
  26. export function isFilmstripVisible(stateful: Object | Function) {
  27. const state = toState(stateful);
  28. const enabled = getFeatureFlag(state, FILMSTRIP_ENABLED, true);
  29. if (!enabled) {
  30. return false;
  31. }
  32. return getParticipantCountWithFake(state) > 1;
  33. }
  34. /**
  35. * Determines whether the remote video thumbnails should be displayed/visible in
  36. * the filmstrip.
  37. *
  38. * @param {Object} state - The full redux state.
  39. * @returns {boolean} - If remote video thumbnails should be displayed/visible
  40. * in the filmstrip, then {@code true}; otherwise, {@code false}.
  41. */
  42. export function shouldRemoteVideosBeVisible(state: Object) {
  43. if (state['features/invite'].calleeInfoVisible) {
  44. return false;
  45. }
  46. // Include fake participants to derive how many thumbnails are dispalyed,
  47. // as it is assumed all participants, including fake, will be displayed
  48. // in the filmstrip.
  49. const participantCount = getParticipantCountWithFake(state);
  50. const pinnedParticipant = getPinnedParticipant(state);
  51. const { disable1On1Mode } = state['features/base/config'];
  52. return Boolean(
  53. participantCount > 2
  54. // Always show the filmstrip when there is another participant to
  55. // show and the local video is pinned. Note we are not taking the
  56. // toolbar visibility into account here (unlike web) because
  57. // showing / hiding views in quick succession on mobile is taxing.
  58. || (participantCount > 1 && pinnedParticipant?.local)
  59. || disable1On1Mode);
  60. }
  61. /**
  62. * Returns the number of participants displayed in tile view.
  63. *
  64. * @param {Object | Function} stateful - The Object or Function that can be
  65. * resolved to a Redux state object with the toState function.
  66. * @returns {number} - The number of participants displayed in tile view.
  67. */
  68. export function getTileViewParticipantCount(stateful: Object | Function) {
  69. const state = toState(stateful);
  70. const disableSelfView = shouldHideSelfView(state);
  71. const localParticipant = getLocalParticipant(state);
  72. const participantCount = getParticipantCountWithFake(state) - (disableSelfView && localParticipant ? 1 : 0);
  73. return participantCount;
  74. }
  75. /**
  76. * Returns how many columns should be displayed for tile view.
  77. *
  78. * @param {Object | Function} stateful - The Object or Function that can be
  79. * resolved to a Redux state object with the toState function.
  80. * @returns {number} - The number of columns to be rendered in tile view.
  81. * @private
  82. */
  83. export function getColumnCount(stateful: Object | Function) {
  84. const state = toState(stateful);
  85. const participantCount = getTileViewParticipantCount(state);
  86. const { aspectRatio } = state['features/base/responsive-ui'];
  87. // For narrow view, tiles should stack on top of each other for a lonely
  88. // call and a 1:1 call. Otherwise tiles should be grouped into rows of
  89. // two.
  90. if (aspectRatio === ASPECT_RATIO_NARROW) {
  91. return participantCount >= 3 ? 2 : 1;
  92. }
  93. if (participantCount === 4) {
  94. // In wide view, a four person call should display as a 2x2 grid.
  95. return 2;
  96. }
  97. return Math.min(3, participantCount);
  98. }
  99. /**
  100. * Returns true if the filmstrip has a scroll and false otherwise.
  101. *
  102. * @param {Object} state - The redux state.
  103. * @returns {boolean} - True if the scroll is displayed and false otherwise.
  104. */
  105. export function isFilmstripScrollVisible(state) {
  106. if (shouldDisplayTileView(state)) {
  107. return state['features/filmstrip']?.tileViewDimensions?.hasScroll;
  108. }
  109. const { aspectRatio, clientWidth, clientHeight, safeAreaInsets = {} } = state['features/base/responsive-ui'];
  110. const isNarrowAspectRatio = aspectRatio === ASPECT_RATIO_NARROW;
  111. const disableSelfView = shouldHideSelfView(state);
  112. const localParticipant = Boolean(getLocalParticipant(state));
  113. const localParticipantVisible = localParticipant && !disableSelfView;
  114. const participantCount
  115. = getParticipantCountWithFake(state)
  116. - (localParticipant && (shouldDisplayLocalThumbnailSeparately() || disableSelfView) ? 1 : 0);
  117. const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
  118. const { height, width } = getFilmstripDimensions({
  119. aspectRatio,
  120. clientWidth,
  121. clientHeight,
  122. insets: safeAreaInsets,
  123. localParticipantVisible
  124. });
  125. if (isNarrowAspectRatio) {
  126. return width < (thumbnailWidth + (2 * margin)) * participantCount;
  127. }
  128. return height < (thumbnailHeight + (2 * margin)) * participantCount;
  129. }
  130. /**
  131. * Whether the stage filmstrip is available or not.
  132. *
  133. * @param {Object} state - Redux state.
  134. * @returns {boolean}
  135. */
  136. export function isStageFilmstripAvailable() {
  137. return false;
  138. }
  139. /**
  140. * Whether the stage filmstrip is enabled.
  141. *
  142. * @returns {boolean}
  143. */
  144. export function isStageFilmstripEnabled() {
  145. return false;
  146. }
  147. /**
  148. * Calculates the width and height of the filmstrip based on the screen size and aspect ratio.
  149. *
  150. * @param {Object} options - The screen aspect ratio, width, height and safe are insets.
  151. * @returns {Object} - The width and the height.
  152. */
  153. export function getFilmstripDimensions({
  154. aspectRatio,
  155. clientWidth,
  156. clientHeight,
  157. insets = {},
  158. localParticipantVisible = true
  159. }) {
  160. const { height, width, margin } = styles.thumbnail;
  161. const conferenceBorder = conferenceStyles.conference.borderWidth || 0;
  162. const { left = 0, right = 0, top = 0, bottom = 0 } = insets;
  163. if (aspectRatio === ASPECT_RATIO_NARROW) {
  164. return {
  165. height,
  166. width:
  167. (shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
  168. ? clientWidth - width - (margin * 2) : clientWidth)
  169. - left - right - (styles.filmstripNarrow.margin * 2) - (conferenceBorder * 2)
  170. };
  171. }
  172. return {
  173. height:
  174. (shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
  175. ? clientHeight - height - (margin * 2) : clientHeight)
  176. - top - bottom - (conferenceBorder * 2),
  177. width
  178. };
  179. }
  180. /**
  181. * Returns true if the local thumbnail should be displayed separately and false otherwise.
  182. *
  183. * @returns {boolean} - True if the local thumbnail should be displayed separately and flase otherwise.
  184. */
  185. export function shouldDisplayLocalThumbnailSeparately() {
  186. // XXX Our current design is to have the local participant separate from
  187. // the remote participants. Unfortunately, Android's Video
  188. // implementation cannot accommodate that because remote participants'
  189. // videos appear on top of the local participant's video at times.
  190. // That's because Android's Video utilizes EGL and EGL gives us only two
  191. // practical layers in which we can place our participants' videos:
  192. // layer #0 sits behind the window, creates a hole in the window, and
  193. // there we render the LargeVideo; layer #1 is known as media overlay in
  194. // EGL terms, renders on top of layer #0, and, consequently, is for the
  195. // Filmstrip. With the separate LocalThumbnail, we should have left the
  196. // remote participants' Thumbnails in layer #1 and utilized layer #2 for
  197. // LocalThumbnail. Unfortunately, layer #2 is not practical (that's why
  198. // I said we had two practical layers only) because it renders on top of
  199. // everything which in our case means on top of participant-related
  200. // indicators such as moderator, audio and video muted, etc. For now we
  201. // do not have much of a choice but to continue rendering LocalThumbnail
  202. // as any other remote Thumbnail on Android.
  203. return Platform.OS !== 'android';
  204. }