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.web.js 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. // @flow
  2. import { getSourceNameSignalingFeatureFlag } from '../base/config';
  3. import { isMobileBrowser } from '../base/environment/utils';
  4. import { MEDIA_TYPE } from '../base/media';
  5. import {
  6. getLocalParticipant,
  7. getParticipantById,
  8. getParticipantCountWithFake,
  9. getPinnedParticipant
  10. } from '../base/participants';
  11. import { toState } from '../base/redux';
  12. import { shouldHideSelfView } from '../base/settings/functions.any';
  13. import {
  14. getLocalVideoTrack,
  15. getTrackByMediaTypeAndParticipant,
  16. isLocalTrackMuted,
  17. isRemoteTrackMuted
  18. } from '../base/tracks/functions';
  19. import { isTrackStreamingStatusActive, isParticipantConnectionStatusActive } from '../connection-indicator/functions';
  20. import { isSharingStatus } from '../shared-video/functions';
  21. import {
  22. getCurrentLayout,
  23. getNotResponsiveTileViewGridDimensions,
  24. LAYOUTS
  25. } from '../video-layout';
  26. import {
  27. ASPECT_RATIO_BREAKPOINT,
  28. DEFAULT_FILMSTRIP_WIDTH,
  29. DEFAULT_LOCAL_TILE_ASPECT_RATIO,
  30. DISPLAY_AVATAR,
  31. DISPLAY_VIDEO,
  32. FILMSTRIP_GRID_BREAKPOINT,
  33. INDICATORS_TOOLTIP_POSITION,
  34. SCROLL_SIZE,
  35. SQUARE_TILE_ASPECT_RATIO,
  36. THUMBNAIL_TYPE,
  37. TILE_ASPECT_RATIO,
  38. TILE_HORIZONTAL_MARGIN,
  39. TILE_MIN_HEIGHT_LARGE,
  40. TILE_MIN_HEIGHT_SMALL,
  41. TILE_PORTRAIT_ASPECT_RATIO,
  42. TILE_VERTICAL_MARGIN,
  43. TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
  44. TILE_VIEW_GRID_HORIZONTAL_MARGIN,
  45. TILE_VIEW_GRID_VERTICAL_MARGIN,
  46. VERTICAL_VIEW_HORIZONTAL_MARGIN
  47. } from './constants';
  48. export * from './functions.any';
  49. declare var interfaceConfig: Object;
  50. /**
  51. * Returns true if the filmstrip on mobile is visible, false otherwise.
  52. *
  53. * NOTE: Filmstrip on web behaves differently to mobile, much simpler, but so
  54. * function lies here only for the sake of consistency and to avoid flow errors
  55. * on import.
  56. *
  57. * @param {Object | Function} stateful - The Object or Function that can be
  58. * resolved to a Redux state object with the toState function.
  59. * @returns {boolean}
  60. */
  61. export function isFilmstripVisible(stateful: Object | Function) {
  62. return toState(stateful)['features/filmstrip'].visible;
  63. }
  64. /**
  65. * Determines whether the remote video thumbnails should be displayed/visible in
  66. * the filmstrip.
  67. *
  68. * @param {Object} state - The full redux state.
  69. * @returns {boolean} - If remote video thumbnails should be displayed/visible
  70. * in the filmstrip, then {@code true}; otherwise, {@code false}.
  71. */
  72. export function shouldRemoteVideosBeVisible(state: Object) {
  73. if (state['features/invite'].calleeInfoVisible) {
  74. return false;
  75. }
  76. // Include fake participants to derive how many thumbnails are dispalyed,
  77. // as it is assumed all participants, including fake, will be displayed
  78. // in the filmstrip.
  79. const participantCount = getParticipantCountWithFake(state);
  80. let pinnedParticipant;
  81. const { disable1On1Mode } = state['features/base/config'];
  82. const { contextMenuOpened } = state['features/base/responsive-ui'];
  83. return Boolean(
  84. contextMenuOpened
  85. || participantCount > 2
  86. // Always show the filmstrip when there is another participant to
  87. // show and the local video is pinned, or the toolbar is displayed.
  88. || (participantCount > 1
  89. && disable1On1Mode !== null
  90. && (state['features/toolbox'].visible
  91. || ((pinnedParticipant = getPinnedParticipant(state))
  92. && pinnedParticipant.local)))
  93. || disable1On1Mode);
  94. }
  95. /**
  96. * Checks whether there is a playable video stream available for the user associated with the passed ID.
  97. *
  98. * @param {Object | Function} stateful - The Object or Function that can be
  99. * resolved to a Redux state object with the toState function.
  100. * @param {string} id - The id of the participant.
  101. * @returns {boolean} <tt>true</tt> if there is a playable video stream available
  102. * or <tt>false</tt> otherwise.
  103. */
  104. export function isVideoPlayable(stateful: Object | Function, id: String) {
  105. const state = toState(stateful);
  106. const tracks = state['features/base/tracks'];
  107. const participant = id ? getParticipantById(state, id) : getLocalParticipant(state);
  108. const isLocal = participant?.local ?? true;
  109. const videoTrack
  110. = isLocal ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
  111. const isAudioOnly = Boolean(state['features/base/audio-only'].enabled);
  112. let isPlayable = false;
  113. if (isLocal) {
  114. const isVideoMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO);
  115. isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly;
  116. } else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
  117. const isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, id);
  118. if (getSourceNameSignalingFeatureFlag(state)) {
  119. isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly
  120. && isTrackStreamingStatusActive(videoTrack);
  121. } else {
  122. isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly
  123. && isParticipantConnectionStatusActive(participant);
  124. }
  125. }
  126. return isPlayable;
  127. }
  128. /**
  129. * Calculates the size for thumbnails when in horizontal view layout.
  130. *
  131. * @param {number} clientHeight - The height of the app window.
  132. * @returns {{local: {height, width}, remote: {height, width}}}
  133. */
  134. export function calculateThumbnailSizeForHorizontalView(clientHeight: number = 0) {
  135. const topBottomMargin = 15;
  136. const availableHeight = Math.min(clientHeight,
  137. (interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH) + topBottomMargin);
  138. const height = availableHeight - topBottomMargin;
  139. return {
  140. local: {
  141. height,
  142. width: Math.floor(interfaceConfig.LOCAL_THUMBNAIL_RATIO * height)
  143. },
  144. remote: {
  145. height,
  146. width: Math.floor(interfaceConfig.REMOTE_THUMBNAIL_RATIO * height)
  147. }
  148. };
  149. }
  150. /**
  151. * Calculates the size for thumbnails when in vertical view layout.
  152. *
  153. * @param {number} clientWidth - The height of the app window.
  154. * @param {number} filmstripWidth - The width of the filmstrip.
  155. * @param {boolean} isResizable - Whether the filmstrip is resizable or not.
  156. * @returns {{local: {height, width}, remote: {height, width}}}
  157. */
  158. export function calculateThumbnailSizeForVerticalView(clientWidth: number = 0,
  159. filmstripWidth: number = 0, isResizable = false) {
  160. const availableWidth = Math.min(
  161. Math.max(clientWidth - VERTICAL_VIEW_HORIZONTAL_MARGIN, 0),
  162. (isResizable ? filmstripWidth : interfaceConfig.FILM_STRIP_MAX_HEIGHT) || DEFAULT_FILMSTRIP_WIDTH);
  163. return {
  164. local: {
  165. height: Math.floor(availableWidth
  166. / (interfaceConfig.LOCAL_THUMBNAIL_RATIO || DEFAULT_LOCAL_TILE_ASPECT_RATIO)),
  167. width: availableWidth
  168. },
  169. remote: {
  170. height: isResizable
  171. ? DEFAULT_FILMSTRIP_WIDTH
  172. : Math.floor(availableWidth / interfaceConfig.REMOTE_THUMBNAIL_RATIO),
  173. width: availableWidth
  174. }
  175. };
  176. }
  177. /**
  178. * Returns the minimum height of a thumbnail.
  179. *
  180. * @param {number} clientWidth - The width of the window.
  181. * @returns {number} The minimum height of a thumbnail.
  182. */
  183. export function getThumbnailMinHeight(clientWidth) {
  184. return clientWidth < ASPECT_RATIO_BREAKPOINT ? TILE_MIN_HEIGHT_SMALL : TILE_MIN_HEIGHT_LARGE;
  185. }
  186. /**
  187. * Returns the default aspect ratio for a tile.
  188. *
  189. * @param {boolean} disableResponsiveTiles - Indicates whether the responsive tiles functionality is disabled.
  190. * @param {boolean} disableTileEnlargement - Indicates whether the tiles enlargement functionality is disabled.
  191. * @param {number} clientWidth - The width of the window.
  192. * @returns {number} The default aspect ratio for a tile.
  193. */
  194. export function getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth) {
  195. if (!disableResponsiveTiles && disableTileEnlargement && clientWidth < ASPECT_RATIO_BREAKPOINT) {
  196. return SQUARE_TILE_ASPECT_RATIO;
  197. }
  198. return TILE_ASPECT_RATIO;
  199. }
  200. /**
  201. * Returns the number of participants that will be displayed in tile view.
  202. *
  203. * @param {Object} state - The redux store state.
  204. * @returns {number} The number of participants that will be displayed in tile view.
  205. */
  206. export function getNumberOfPartipantsForTileView(state) {
  207. const { iAmRecorder } = state['features/base/config'];
  208. const disableSelfView = shouldHideSelfView(state);
  209. const { localScreenShare } = state['features/base/participants'];
  210. const localParticipantsCount = getSourceNameSignalingFeatureFlag(state) && localScreenShare ? 2 : 1;
  211. const numberOfParticipants = getParticipantCountWithFake(state)
  212. - (iAmRecorder ? 1 : 0)
  213. - (disableSelfView ? localParticipantsCount : 0);
  214. return numberOfParticipants;
  215. }
  216. /**
  217. * Calculates the dimensions (thumbnail width/height and columns/row) for tile view when the responsive tiles are
  218. * disabled.
  219. *
  220. * @param {Object} state - The redux store state.
  221. * @returns {Object} - The dimensions.
  222. */
  223. export function calculateNonResponsiveTileViewDimensions(state) {
  224. const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
  225. const { disableTileEnlargement } = state['features/base/config'];
  226. const { columns: c, minVisibleRows, rows: r } = getNotResponsiveTileViewGridDimensions(state);
  227. const size = calculateThumbnailSizeForTileView({
  228. columns: c,
  229. minVisibleRows,
  230. clientWidth,
  231. clientHeight,
  232. disableTileEnlargement,
  233. disableResponsiveTiles: true
  234. });
  235. if (typeof size === 'undefined') { // The columns don't fit into the screen. We will have horizontal scroll.
  236. const aspectRatio = disableTileEnlargement
  237. ? getTileDefaultAspectRatio(true, disableTileEnlargement, clientWidth)
  238. : TILE_PORTRAIT_ASPECT_RATIO;
  239. const height = getThumbnailMinHeight(clientWidth);
  240. return {
  241. height,
  242. width: aspectRatio * height,
  243. columns: c,
  244. rows: r
  245. };
  246. }
  247. return {
  248. height: size.height,
  249. width: size.width,
  250. columns: c,
  251. rows: r
  252. };
  253. }
  254. /**
  255. * Calculates the dimensions (thumbnail width/height and columns/row) for tile view when the responsive tiles are
  256. * enabled.
  257. *
  258. * @param {Object} state - The redux store state.
  259. * @returns {Object} - The dimensions.
  260. */
  261. export function calculateResponsiveTileViewDimensions({
  262. clientWidth,
  263. clientHeight,
  264. disableTileEnlargement = false,
  265. noHorizontalContainerMargin = false,
  266. maxColumns,
  267. numberOfParticipants,
  268. desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES
  269. }) {
  270. let height, width;
  271. let columns, rows;
  272. let dimensions = {
  273. maxArea: 0
  274. };
  275. let minHeightEnforcedDimensions = {
  276. maxArea: 0
  277. };
  278. let zeroVisibleRowsDimensions = {
  279. maxArea: 0
  280. };
  281. for (let c = 1; c <= Math.min(maxColumns, numberOfParticipants, desiredNumberOfVisibleTiles); c++) {
  282. const r = Math.ceil(numberOfParticipants / c);
  283. // we want to display as much as possible tumbnails up to desiredNumberOfVisibleTiles
  284. const visibleRows
  285. = numberOfParticipants <= desiredNumberOfVisibleTiles ? r : Math.floor(desiredNumberOfVisibleTiles / c);
  286. const size = calculateThumbnailSizeForTileView({
  287. columns: c,
  288. minVisibleRows: visibleRows,
  289. clientWidth,
  290. clientHeight,
  291. disableTileEnlargement,
  292. disableResponsiveTiles: false,
  293. noHorizontalContainerMargin
  294. });
  295. if (size) {
  296. const { height: currentHeight, width: currentWidth, minHeightEnforced, maxVisibleRows } = size;
  297. const numberOfVisibleParticipants = Math.min(c * maxVisibleRows, numberOfParticipants);
  298. let area = Math.round(
  299. (currentHeight + TILE_VERTICAL_MARGIN)
  300. * (currentWidth + TILE_HORIZONTAL_MARGIN)
  301. * numberOfVisibleParticipants);
  302. const currentDimensions = {
  303. maxArea: area,
  304. height: currentHeight,
  305. width: currentWidth,
  306. columns: c,
  307. rows: r,
  308. numberOfVisibleParticipants
  309. };
  310. const { numberOfVisibleParticipants: oldNumberOfVisibleParticipants = 0 } = dimensions;
  311. if (!minHeightEnforced) {
  312. if (area > dimensions.maxArea) {
  313. dimensions = currentDimensions;
  314. } else if ((area === dimensions.maxArea)
  315. && ((oldNumberOfVisibleParticipants > desiredNumberOfVisibleTiles
  316. && oldNumberOfVisibleParticipants >= numberOfParticipants)
  317. || (oldNumberOfVisibleParticipants < numberOfParticipants
  318. && numberOfVisibleParticipants <= desiredNumberOfVisibleTiles))
  319. ) { // If the area of the new candidates and the old ones are equal we preffer the one that will have
  320. // closer number of visible participants to desiredNumberOfVisibleTiles config.
  321. dimensions = currentDimensions;
  322. }
  323. } else if (minHeightEnforced && area >= minHeightEnforcedDimensions.maxArea) {
  324. // If we choose configuration with minHeightEnforced there will be less than desiredNumberOfVisibleTiles
  325. // visible tiles, that's why we prefer more columns when the area is the same.
  326. minHeightEnforcedDimensions = currentDimensions;
  327. } else if (minHeightEnforced && maxVisibleRows === 0) {
  328. area = currentHeight * currentWidth * Math.min(c, numberOfParticipants);
  329. if (area > zeroVisibleRowsDimensions.maxArea) {
  330. zeroVisibleRowsDimensions = {
  331. ...currentDimensions,
  332. maxArea: area
  333. };
  334. }
  335. }
  336. }
  337. }
  338. if (dimensions.maxArea > 0) {
  339. ({ height, width, columns, rows } = dimensions);
  340. } else if (minHeightEnforcedDimensions.maxArea > 0) {
  341. ({ height, width, columns, rows } = minHeightEnforcedDimensions);
  342. } else if (zeroVisibleRowsDimensions.maxArea > 0) {
  343. ({ height, width, columns, rows } = zeroVisibleRowsDimensions);
  344. } else { // This would mean that we can't fit even one thumbnail with minimal size.
  345. const aspectRatio = disableTileEnlargement
  346. ? getTileDefaultAspectRatio(false, disableTileEnlargement, clientWidth)
  347. : TILE_PORTRAIT_ASPECT_RATIO;
  348. height = getThumbnailMinHeight(clientWidth);
  349. width = aspectRatio * height;
  350. columns = 1;
  351. rows = numberOfParticipants;
  352. }
  353. return {
  354. height,
  355. width,
  356. columns,
  357. rows
  358. };
  359. }
  360. /**
  361. * Calculates the size for thumbnails when in tile view layout.
  362. *
  363. * @param {Object} dimensions - The desired dimensions of the tile view grid.
  364. * @returns {{hasScroll, height, width}}
  365. */
  366. export function calculateThumbnailSizeForTileView({
  367. columns,
  368. minVisibleRows,
  369. clientWidth,
  370. clientHeight,
  371. disableResponsiveTiles = false,
  372. disableTileEnlargement = false,
  373. noHorizontalContainerMargin = false
  374. }: Object) {
  375. const aspectRatio = getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth);
  376. const minHeight = getThumbnailMinHeight(clientWidth);
  377. const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
  378. - (noHorizontalContainerMargin ? SCROLL_SIZE : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
  379. const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
  380. const viewHeight = availableHeight - (minVisibleRows * TILE_VERTICAL_MARGIN);
  381. const initialWidth = viewWidth / columns;
  382. let initialHeight = viewHeight / minVisibleRows;
  383. let minHeightEnforced = false;
  384. if (initialHeight < minHeight) {
  385. minHeightEnforced = true;
  386. initialHeight = minHeight;
  387. }
  388. if (disableTileEnlargement) {
  389. const aspectRatioHeight = initialWidth / aspectRatio;
  390. if (aspectRatioHeight < minHeight) { // we can't fit the required number of columns.
  391. return;
  392. }
  393. const height = Math.min(aspectRatioHeight, initialHeight);
  394. return {
  395. height,
  396. width: aspectRatio * height,
  397. minHeightEnforced,
  398. maxVisibleRows: Math.floor(availableHeight / (height + TILE_VERTICAL_MARGIN))
  399. };
  400. }
  401. const initialRatio = initialWidth / initialHeight;
  402. let height = initialHeight;
  403. let width;
  404. // The biggest area of the grid will be when the grid's height is equal to clientHeight or when the grid's width is
  405. // equal to clientWidth.
  406. if (initialRatio > aspectRatio) {
  407. width = initialHeight * aspectRatio;
  408. } else if (initialRatio >= TILE_PORTRAIT_ASPECT_RATIO) {
  409. width = initialWidth;
  410. // eslint-disable-next-line no-negated-condition
  411. } else if (!minHeightEnforced) {
  412. height = initialWidth / TILE_PORTRAIT_ASPECT_RATIO;
  413. if (height >= minHeight) {
  414. width = initialWidth;
  415. } else { // The width is so small that we can't reach the minimum height with portrait aspect ratio.
  416. return;
  417. }
  418. } else {
  419. // We can't fit that number of columns with the desired min height and aspect ratio.
  420. return;
  421. }
  422. return {
  423. height,
  424. width,
  425. minHeightEnforced,
  426. maxVisibleRows: Math.floor(availableHeight / (height + TILE_VERTICAL_MARGIN))
  427. };
  428. }
  429. /**
  430. * Returns the width of the visible area (doesn't include the left margin/padding) of the the vertical filmstrip.
  431. *
  432. * @returns {number} - The width of the vertical filmstrip.
  433. */
  434. export function getVerticalFilmstripVisibleAreaWidth() {
  435. // Adding 11px for the 2px right margin, 2px borders on the left and right and 5px right padding.
  436. // Also adding 7px for the scrollbar. Note that we are not counting the left margins and paddings because this
  437. // function is used for calculating the available space and they are invisible.
  438. // TODO: Check if we can remove the left margins and paddings from the CSS.
  439. // FIXME: This function is used to calculate the size of the large video, etherpad or shared video. Once everything
  440. // is reactified this calculation will need to move to the corresponding components.
  441. const filmstripMaxWidth = (interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH) + 18;
  442. return Math.min(filmstripMaxWidth, window.innerWidth);
  443. }
  444. /**
  445. * Computes information that determine the display mode.
  446. *
  447. * @param {Object} input - Object containing all necessary information for determining the display mode for
  448. * the thumbnail.
  449. * @returns {number} - One of <tt>DISPLAY_VIDEO</tt> or <tt>DISPLAY_AVATAR</tt>.
  450. */
  451. export function computeDisplayModeFromInput(input: Object) {
  452. const {
  453. isActiveParticipant,
  454. isAudioOnly,
  455. isCurrentlyOnLargeVideo,
  456. isVirtualScreenshareParticipant,
  457. isScreenSharing,
  458. canPlayEventReceived,
  459. isRemoteParticipant,
  460. multipleVideoSupport,
  461. stageParticipantsVisible,
  462. stageFilmstrip,
  463. tileViewActive
  464. } = input;
  465. const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
  466. if (multipleVideoSupport) {
  467. // Display video for virtual screen share participants in all layouts.
  468. if (isVirtualScreenshareParticipant) {
  469. return DISPLAY_VIDEO;
  470. }
  471. // Multi-stream is not supported on plan-b endpoints even if its is enabled via config.js. A virtual
  472. // screenshare tile is still created when a remote endpoint starts screenshare to keep the behavior consistent
  473. // and an avatar is displayed on the original participant thumbnail as long as screenshare is in progress.
  474. if (isScreenSharing) {
  475. return DISPLAY_AVATAR;
  476. }
  477. }
  478. if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
  479. || (stageParticipantsVisible && isActiveParticipant && !stageFilmstrip))) {
  480. return DISPLAY_AVATAR;
  481. } else if (isCurrentlyOnLargeVideo && !tileViewActive) {
  482. // Display name is always and only displayed when user is on the stage
  483. return adjustedIsVideoPlayable && !isAudioOnly ? DISPLAY_VIDEO : DISPLAY_AVATAR;
  484. } else if (adjustedIsVideoPlayable && !isAudioOnly) {
  485. // check hovering and change state to video with name
  486. return DISPLAY_VIDEO;
  487. }
  488. // check hovering and change state to avatar with name
  489. return DISPLAY_AVATAR;
  490. }
  491. /**
  492. * Extracts information for props and state needed to compute the display mode.
  493. *
  494. * @param {Object} props - The Thumbnail component's props.
  495. * @param {Object} state - The Thumbnail component's state.
  496. * @returns {Object}
  497. */
  498. export function getDisplayModeInput(props: Object, state: Object) {
  499. const {
  500. _currentLayout,
  501. _isActiveParticipant,
  502. _isAudioOnly,
  503. _isCurrentlyOnLargeVideo,
  504. _isVirtualScreenshareParticipant,
  505. _isScreenSharing,
  506. _isVideoPlayable,
  507. _multipleVideoSupport,
  508. _participant,
  509. _stageParticipantsVisible,
  510. _videoTrack,
  511. stageFilmstrip
  512. } = props;
  513. const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
  514. const { canPlayEventReceived } = state;
  515. return {
  516. isActiveParticipant: _isActiveParticipant,
  517. isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo,
  518. isAudioOnly: _isAudioOnly,
  519. tileViewActive,
  520. isVideoPlayable: _isVideoPlayable,
  521. connectionStatus: _participant?.connectionStatus,
  522. canPlayEventReceived,
  523. videoStream: Boolean(_videoTrack),
  524. isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
  525. isScreenSharing: _isScreenSharing,
  526. isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant,
  527. multipleVideoSupport: _multipleVideoSupport,
  528. stageParticipantsVisible: _stageParticipantsVisible,
  529. stageFilmstrip,
  530. videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
  531. };
  532. }
  533. /**
  534. * Gets the tooltip position for the thumbnail indicators.
  535. *
  536. * @param {string} thumbnailType - The current thumbnail type.
  537. * @returns {string}
  538. */
  539. export function getIndicatorsTooltipPosition(thumbnailType: string) {
  540. return INDICATORS_TOOLTIP_POSITION[thumbnailType] || 'top';
  541. }
  542. /**
  543. * Returns whether or not the filmstrip is resizable.
  544. *
  545. * @param {Object} state - Redux state.
  546. * @returns {boolean}
  547. */
  548. export function isFilmstripResizable(state: Object) {
  549. const { filmstrip } = state['features/base/config'];
  550. const _currentLayout = getCurrentLayout(state);
  551. return !filmstrip?.disableResizable && !isMobileBrowser()
  552. && (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
  553. }
  554. /**
  555. * Whether or not grid should be displayed in the vertical filmstrip.
  556. *
  557. * @param {Object} state - Redux state.
  558. * @returns {boolean}
  559. */
  560. export function showGridInVerticalView(state) {
  561. const resizableFilmstrip = isFilmstripResizable(state);
  562. const { width } = state['features/filmstrip'];
  563. return resizableFilmstrip && ((width.current ?? 0) > FILMSTRIP_GRID_BREAKPOINT);
  564. }
  565. /**
  566. * Gets the vertical filmstrip max width.
  567. *
  568. * @param {Object} state - Redux state.
  569. * @returns {number}
  570. */
  571. export function getVerticalViewMaxWidth(state) {
  572. const { width } = state['features/filmstrip'];
  573. const _resizableFilmstrip = isFilmstripResizable(state);
  574. const _verticalViewGrid = showGridInVerticalView(state);
  575. let maxWidth = _resizableFilmstrip
  576. ? width.current || DEFAULT_FILMSTRIP_WIDTH
  577. : interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH;
  578. // Adding 4px for the border-right and margin-right.
  579. // On non-resizable filmstrip add 4px for the left margin and border.
  580. // Also adding 7px for the scrollbar. Also adding 9px for the drag handle.
  581. maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? 9 : 4);
  582. return maxWidth;
  583. }
  584. /**
  585. * Returns true if the scroll is displayed and false otherwise.
  586. *
  587. * @param {Object} state - The redux state.
  588. * @returns {boolean} - True if the scroll is displayed and false otherwise.
  589. */
  590. export function isFilmstripScrollVisible(state) {
  591. const _currentLayout = getCurrentLayout(state);
  592. let hasScroll = false;
  593. switch (_currentLayout) {
  594. case LAYOUTS.TILE_VIEW:
  595. ({ hasScroll = false } = state['features/filmstrip'].tileViewDimensions);
  596. break;
  597. case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
  598. case LAYOUTS.STAGE_FILMSTRIP_VIEW: {
  599. ({ hasScroll = false } = state['features/filmstrip'].verticalViewDimensions);
  600. break;
  601. }
  602. case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
  603. ({ hasScroll = false } = state['features/filmstrip'].horizontalViewDimensions);
  604. break;
  605. }
  606. }
  607. return hasScroll;
  608. }
  609. /**
  610. * Gets the ids of the active participants.
  611. *
  612. * @param {Object} state - Redux state.
  613. * @returns {Array<string>}
  614. */
  615. export function getActiveParticipantsIds(state) {
  616. const { activeParticipants } = state['features/filmstrip'];
  617. return activeParticipants.map(p => p.participantId);
  618. }
  619. /**
  620. * Gets the ids of the active participants.
  621. *
  622. * @param {Object} state - Redux state.
  623. * @returns {Array<Object>}
  624. */
  625. export function getPinnedActiveParticipants(state) {
  626. const { activeParticipants } = state['features/filmstrip'];
  627. return activeParticipants.filter(p => p.pinned);
  628. }
  629. /**
  630. * Get whether or not the stage filmstrip is available (enabled & can be used).
  631. *
  632. * @param {Object} state - Redux state.
  633. * @param {number} minParticipantCount - The min number of participants for the stage filmstrip
  634. * to be displayed.
  635. * @returns {boolean}
  636. */
  637. export function isStageFilmstripAvailable(state, minParticipantCount = 0) {
  638. const { activeParticipants } = state['features/filmstrip'];
  639. const { remoteScreenShares } = state['features/video-layout'];
  640. const sharedVideo = isSharingStatus(state['features/shared-video']?.status);
  641. return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo
  642. && activeParticipants.length >= minParticipantCount;
  643. }
  644. /**
  645. * Whether the stage filmstrip is disabled or not.
  646. *
  647. * @param {Object} state - Redux state.
  648. * @returns {boolean}
  649. */
  650. export function isStageFilmstripEnabled(state) {
  651. const { filmstrip } = state['features/base/config'];
  652. return !(filmstrip?.disableStageFilmstrip ?? true) && interfaceConfig.VERTICAL_FILMSTRIP;
  653. }
  654. /**
  655. * Gets the thumbnail type by filmstrip type.
  656. *
  657. * @param {string} currentLayout - Current app layout.
  658. * @param {boolean} isStageFilmstrip - Whether the filmstrip is stage filmstrip or not.
  659. * @returns {string}
  660. */
  661. export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = false) {
  662. switch (currentLayout) {
  663. case LAYOUTS.TILE_VIEW:
  664. return THUMBNAIL_TYPE.TILE;
  665. case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
  666. return THUMBNAIL_TYPE.VERTICAL;
  667. case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
  668. return THUMBNAIL_TYPE.HORIZONTAL;
  669. case LAYOUTS.STAGE_FILMSTRIP_VIEW:
  670. if (isStageFilmstrip) {
  671. return THUMBNAIL_TYPE.TILE;
  672. }
  673. return THUMBNAIL_TYPE.VERTICAL;
  674. }
  675. }