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.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // @flow
  2. import { FILMSTRIP_ENABLED, getFeatureFlag } 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 displayed,
  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. * Not implemented on mobile.
  63. *
  64. * @returns {Array<string>}
  65. */
  66. export function getActiveParticipantsIds() {
  67. return [];
  68. }
  69. /**
  70. * Returns the number of participants displayed in tile view.
  71. *
  72. * @param {Object | Function} stateful - The Object or Function that can be
  73. * resolved to a Redux state object with the toState function.
  74. * @returns {number} - The number of participants displayed in tile view.
  75. */
  76. export function getTileViewParticipantCount(stateful: Object | Function) {
  77. const state = toState(stateful);
  78. const disableSelfView = shouldHideSelfView(state);
  79. const localParticipant = getLocalParticipant(state);
  80. const participantCount = getParticipantCountWithFake(state) - (disableSelfView && localParticipant ? 1 : 0);
  81. return participantCount;
  82. }
  83. /**
  84. * Returns how many columns should be displayed for tile view.
  85. *
  86. * @param {Object | Function} stateful - The Object or Function that can be
  87. * resolved to a Redux state object with the toState function.
  88. * @returns {number} - The number of columns to be rendered in tile view.
  89. * @private
  90. */
  91. export function getColumnCount(stateful: Object | Function) {
  92. const state = toState(stateful);
  93. const participantCount = getTileViewParticipantCount(state);
  94. const { aspectRatio } = state['features/base/responsive-ui'];
  95. // For narrow view, tiles should stack on top of each other for a lonely
  96. // call and a 1:1 call. Otherwise tiles should be grouped into rows of
  97. // two.
  98. if (aspectRatio === ASPECT_RATIO_NARROW) {
  99. return participantCount >= 3 ? 2 : 1;
  100. }
  101. if (participantCount === 4) {
  102. // In wide view, a four person call should display as a 2x2 grid.
  103. return 2;
  104. }
  105. return Math.min(participantCount <= 6 ? 3 : 4, participantCount);
  106. }
  107. /**
  108. * Returns true if the filmstrip has a scroll and false otherwise.
  109. *
  110. * @param {Object} state - The redux state.
  111. * @returns {boolean} - True if the scroll is displayed and false otherwise.
  112. */
  113. export function isFilmstripScrollVisible(state) {
  114. if (shouldDisplayTileView(state)) {
  115. return state['features/filmstrip']?.tileViewDimensions?.hasScroll;
  116. }
  117. const { aspectRatio, clientWidth, clientHeight, safeAreaInsets = {} } = state['features/base/responsive-ui'];
  118. const isNarrowAspectRatio = aspectRatio === ASPECT_RATIO_NARROW;
  119. const disableSelfView = shouldHideSelfView(state);
  120. const localParticipant = Boolean(getLocalParticipant(state));
  121. const localParticipantVisible = localParticipant && !disableSelfView;
  122. const participantCount
  123. = getParticipantCountWithFake(state)
  124. - (localParticipant && (shouldDisplayLocalThumbnailSeparately() || disableSelfView) ? 1 : 0);
  125. const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
  126. const { height, width } = getFilmstripDimensions({
  127. aspectRatio,
  128. clientWidth,
  129. clientHeight,
  130. insets: safeAreaInsets,
  131. localParticipantVisible
  132. });
  133. if (isNarrowAspectRatio) {
  134. return width < (thumbnailWidth + (2 * margin)) * participantCount;
  135. }
  136. return height < (thumbnailHeight + (2 * margin)) * participantCount;
  137. }
  138. /**
  139. * Whether the stage filmstrip is available or not.
  140. *
  141. * @param {Object} state - Redux state.
  142. * @returns {boolean}
  143. */
  144. export function isStageFilmstripAvailable() {
  145. return false;
  146. }
  147. /**
  148. * Whether the stage filmstrip is enabled.
  149. *
  150. * @returns {boolean}
  151. */
  152. export function isStageFilmstripEnabled() {
  153. return false;
  154. }
  155. /**
  156. * Whether or not the top panel is enabled.
  157. *
  158. * @returns {boolean}
  159. */
  160. export function isTopPanelEnabled() {
  161. return false;
  162. }
  163. /**
  164. * Calculates the width and height of the filmstrip based on the screen size and aspect ratio.
  165. *
  166. * @param {Object} options - The screen aspect ratio, width, height and safe are insets.
  167. * @returns {Object} - The width and the height.
  168. */
  169. export function getFilmstripDimensions({
  170. aspectRatio,
  171. clientWidth,
  172. clientHeight,
  173. insets = {},
  174. localParticipantVisible = true
  175. }) {
  176. const { height, width, margin } = styles.thumbnail;
  177. const conferenceBorder = conferenceStyles.conference.borderWidth || 0;
  178. const { left = 0, right = 0, top = 0, bottom = 0 } = insets;
  179. if (aspectRatio === ASPECT_RATIO_NARROW) {
  180. return {
  181. height,
  182. width:
  183. (shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
  184. ? clientWidth - width - (margin * 2) : clientWidth)
  185. - left - right - (styles.filmstripNarrow.margin * 2) - (conferenceBorder * 2)
  186. };
  187. }
  188. return {
  189. height:
  190. (shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
  191. ? clientHeight - height - (margin * 2) : clientHeight)
  192. - top - bottom - (conferenceBorder * 2),
  193. width
  194. };
  195. }
  196. /**
  197. * Returns true if the local thumbnail should be displayed separately and false otherwise.
  198. *
  199. * @returns {boolean} - True if the local thumbnail should be displayed separately and flase otherwise.
  200. */
  201. export function shouldDisplayLocalThumbnailSeparately() {
  202. // XXX Our current design is to have the local participant separate from
  203. // the remote participants. Unfortunately, Android's Video
  204. // implementation cannot accommodate that because remote participants'
  205. // videos appear on top of the local participant's video at times.
  206. // That's because Android's Video utilizes EGL and EGL gives us only two
  207. // practical layers in which we can place our participants' videos:
  208. // layer #0 sits behind the window, creates a hole in the window, and
  209. // there we render the LargeVideo; layer #1 is known as media overlay in
  210. // EGL terms, renders on top of layer #0, and, consequently, is for the
  211. // Filmstrip. With the separate LocalThumbnail, we should have left the
  212. // remote participants' Thumbnails in layer #1 and utilized layer #2 for
  213. // LocalThumbnail. Unfortunately, layer #2 is not practical (that's why
  214. // I said we had two practical layers only) because it renders on top of
  215. // everything which in our case means on top of participant-related
  216. // indicators such as moderator, audio and video muted, etc. For now we
  217. // do not have much of a choice but to continue rendering LocalThumbnail
  218. // as any other remote Thumbnail on Android.
  219. return Platform.OS !== 'android';
  220. }
  221. /**
  222. * Not implemented on mobile.
  223. *
  224. * @returns {undefined}
  225. */
  226. export function getScreenshareFilmstripParticipantId() {
  227. return undefined;
  228. }