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.ts 9.6KB

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