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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. /* eslint-disable lines-around-comment */
  2. // @ts-ignore
  3. import { getGravatarURL } from '@jitsi/js-utils/avatar';
  4. import { IState, IStore } from '../../app/types';
  5. // @ts-ignore
  6. import { isStageFilmstripAvailable } from '../../filmstrip/functions';
  7. import { IStateful } from '../app/types';
  8. import { GRAVATAR_BASE_URL } from '../avatar/constants';
  9. import { isCORSAvatarURL } from '../avatar/functions';
  10. import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config/functions.any';
  11. import i18next from '../i18n/i18next';
  12. import { JitsiParticipantConnectionStatus, JitsiTrackStreamingStatus } from '../lib-jitsi-meet';
  13. import { shouldRenderVideoTrack } from '../media/functions';
  14. import { toState } from '../redux/functions';
  15. import { getScreenShareTrack, getVideoTrackByParticipant } from '../tracks/functions';
  16. import { createDeferred } from '../util/helpers';
  17. import {
  18. JIGASI_PARTICIPANT_ICON,
  19. MAX_DISPLAY_NAME_LENGTH,
  20. PARTICIPANT_ROLE,
  21. WHITEBOARD_PARTICIPANT_ICON
  22. } from './constants';
  23. // @ts-ignore
  24. import { preloadImage } from './preloadImage';
  25. import { FakeParticipant, Participant } from './types';
  26. /**
  27. * Temp structures for avatar urls to be checked/preloaded.
  28. */
  29. const AVATAR_QUEUE: Object[] = [];
  30. const AVATAR_CHECKED_URLS = new Map();
  31. /* eslint-disable arrow-body-style, no-unused-vars */
  32. const AVATAR_CHECKER_FUNCTIONS = [
  33. (participant: Participant) => {
  34. return isJigasiParticipant(participant) ? JIGASI_PARTICIPANT_ICON : null;
  35. },
  36. (participant: Participant) => {
  37. return isWhiteboardParticipant(participant) ? WHITEBOARD_PARTICIPANT_ICON : null;
  38. },
  39. (participant: Participant) => {
  40. return participant?.avatarURL ? participant.avatarURL : null;
  41. },
  42. (participant: Participant, store: IStore) => {
  43. const config = store.getState()['features/base/config'];
  44. const isGravatarDisabled = config.gravatar?.disabled;
  45. if (participant?.email && !isGravatarDisabled) {
  46. const gravatarBaseURL = config.gravatar?.baseUrl
  47. || config.gravatarBaseURL
  48. || GRAVATAR_BASE_URL;
  49. return getGravatarURL(participant.email, gravatarBaseURL);
  50. }
  51. return null;
  52. }
  53. ];
  54. /* eslint-enable arrow-body-style, no-unused-vars */
  55. /**
  56. * Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
  57. * dominant speaker is visible always on the vertical filmstrip in stage layout.
  58. *
  59. * @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  60. * retrieve the state.
  61. * @returns {Array<string>}
  62. */
  63. export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
  64. const state = toState(stateful);
  65. const {
  66. dominantSpeaker,
  67. fakeParticipants,
  68. sortedRemoteScreenshares,
  69. sortedRemoteVirtualScreenshareParticipants,
  70. speakersList
  71. } = state['features/base/participants'];
  72. const { visibleRemoteParticipants } = state['features/filmstrip'];
  73. let activeSpeakers = new Map(speakersList);
  74. // Do not re-sort the active speakers if dominant speaker is currently visible.
  75. if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
  76. return activeSpeakers;
  77. }
  78. let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
  79. if (activeSpeakers.has(dominantSpeaker ?? '')) {
  80. activeSpeakers.delete(dominantSpeaker ?? '');
  81. }
  82. // Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
  83. // alphabetically sorted.
  84. if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
  85. const updatedSpeakers = Array.from(activeSpeakers);
  86. updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
  87. activeSpeakers = new Map(updatedSpeakers);
  88. }
  89. // Remove screenshares from the count.
  90. if (getMultipleVideoSupportFeatureFlag(state)) {
  91. if (sortedRemoteVirtualScreenshareParticipants) {
  92. availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
  93. for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
  94. const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
  95. activeSpeakers.delete(ownerId);
  96. }
  97. }
  98. } else if (sortedRemoteScreenshares) {
  99. availableSlotsForActiveSpeakers -= sortedRemoteScreenshares.size;
  100. for (const id of Array.from(sortedRemoteScreenshares.keys())) {
  101. activeSpeakers.delete(id);
  102. }
  103. }
  104. // Remove fake participants from the count.
  105. if (fakeParticipants) {
  106. availableSlotsForActiveSpeakers -= fakeParticipants.size;
  107. }
  108. const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
  109. truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
  110. return new Map(truncatedSpeakersList);
  111. }
  112. /**
  113. * Resolves the first loadable avatar URL for a participant.
  114. *
  115. * @param {Object} participant - The participant to resolve avatars for.
  116. * @param {Store} store - Redux store.
  117. * @returns {Promise}
  118. */
  119. export function getFirstLoadableAvatarUrl(participant: Participant, store: IStore) {
  120. const deferred: any = createDeferred();
  121. const fullPromise = deferred.promise
  122. .then(() => _getFirstLoadableAvatarUrl(participant, store))
  123. .then((result: any) => {
  124. if (AVATAR_QUEUE.length) {
  125. const next: any = AVATAR_QUEUE.shift();
  126. next.resolve();
  127. }
  128. return result;
  129. });
  130. if (AVATAR_QUEUE.length) {
  131. AVATAR_QUEUE.push(deferred);
  132. } else {
  133. deferred.resolve();
  134. }
  135. return fullPromise;
  136. }
  137. /**
  138. * Returns local participant from Redux state.
  139. *
  140. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  141. * {@code getState} function to be used to retrieve the state
  142. * features/base/participants.
  143. * @returns {(Participant|undefined)}
  144. */
  145. export function getLocalParticipant(stateful: IStateful) {
  146. const state = toState(stateful)['features/base/participants'];
  147. return state.local;
  148. }
  149. /**
  150. * Returns local screen share participant from Redux state.
  151. *
  152. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  153. * {@code getState} function to be used to retrieve the state features/base/participants.
  154. * @returns {(Participant|undefined)}
  155. */
  156. export function getLocalScreenShareParticipant(stateful: IStateful) {
  157. const state = toState(stateful)['features/base/participants'];
  158. return state.localScreenShare;
  159. }
  160. /**
  161. * Returns screenshare participant.
  162. *
  163. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  164. * retrieve the state features/base/participants.
  165. * @param {string} id - The owner ID of the screenshare participant to retrieve.
  166. * @returns {(Participant|undefined)}
  167. */
  168. export function getVirtualScreenshareParticipantByOwnerId(stateful: IStateful, id: string) {
  169. const state = toState(stateful);
  170. if (getMultipleVideoSupportFeatureFlag(state)) {
  171. const track = getScreenShareTrack(state['features/base/tracks'], id);
  172. return getParticipantById(stateful, track?.jitsiTrack.getSourceName());
  173. }
  174. return;
  175. }
  176. /**
  177. * Normalizes a display name so then no invalid values (padding, length...etc)
  178. * can be set.
  179. *
  180. * @param {string} name - The display name to set.
  181. * @returns {string}
  182. */
  183. export function getNormalizedDisplayName(name: string) {
  184. if (!name || !name.trim()) {
  185. return undefined;
  186. }
  187. return name.trim().substring(0, MAX_DISPLAY_NAME_LENGTH);
  188. }
  189. /**
  190. * Returns participant by ID from Redux state.
  191. *
  192. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  193. * {@code getState} function to be used to retrieve the state
  194. * features/base/participants.
  195. * @param {string} id - The ID of the participant to retrieve.
  196. * @private
  197. * @returns {(Participant|undefined)}
  198. */
  199. export function getParticipantById(stateful: IStateful, id: string): Participant | undefined {
  200. const state = toState(stateful)['features/base/participants'];
  201. const { local, localScreenShare, remote } = state;
  202. return remote.get(id)
  203. || (local?.id === id ? local : undefined)
  204. || (localScreenShare?.id === id ? localScreenShare : undefined);
  205. }
  206. /**
  207. * Returns the participant with the ID matching the passed ID or the local participant if the ID is
  208. * undefined.
  209. *
  210. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  211. * {@code getState} function to be used to retrieve the state
  212. * features/base/participants.
  213. * @param {string|undefined} [participantID] - An optional partipantID argument.
  214. * @returns {Participant|undefined}
  215. */
  216. export function getParticipantByIdOrUndefined(stateful: IStateful, participantID?: string) {
  217. return participantID ? getParticipantById(stateful, participantID) : getLocalParticipant(stateful);
  218. }
  219. /**
  220. * Returns a count of the known participants in the passed in redux state,
  221. * excluding any fake participants.
  222. *
  223. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  224. * {@code getState} function to be used to retrieve the state
  225. * features/base/participants.
  226. * @returns {number}
  227. */
  228. export function getParticipantCount(stateful: IStateful) {
  229. const state = toState(stateful);
  230. const {
  231. local,
  232. remote,
  233. fakeParticipants,
  234. sortedRemoteVirtualScreenshareParticipants
  235. } = state['features/base/participants'];
  236. if (getMultipleVideoSupportFeatureFlag(state)) {
  237. return remote.size - fakeParticipants.size - sortedRemoteVirtualScreenshareParticipants.size + (local ? 1 : 0);
  238. }
  239. return remote.size - fakeParticipants.size + (local ? 1 : 0);
  240. }
  241. /**
  242. * Returns participant ID of the owner of a virtual screenshare participant.
  243. *
  244. * @param {string} id - The ID of the virtual screenshare participant.
  245. * @private
  246. * @returns {(string|undefined)}
  247. */
  248. export function getVirtualScreenshareParticipantOwnerId(id: string) {
  249. return id.split('-')[0];
  250. }
  251. /**
  252. * Returns the Map with fake participants.
  253. *
  254. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  255. * {@code getState} function to be used to retrieve the state
  256. * features/base/participants.
  257. * @returns {Map<string, Participant>} - The Map with fake participants.
  258. */
  259. export function getFakeParticipants(stateful: IStateful) {
  260. return toState(stateful)['features/base/participants'].fakeParticipants;
  261. }
  262. /**
  263. * Returns whether the fake participant is Jigasi.
  264. *
  265. * @param {Participant|undefined} participant - The participant entity.
  266. * @returns {boolean} - True if it's a Jigasi participant.
  267. */
  268. function isJigasiParticipant(participant?: Participant): boolean {
  269. return participant?.fakeParticipant === FakeParticipant.Jigasi;
  270. }
  271. /**
  272. * Returns whether the fake participant is a local screenshare.
  273. *
  274. * @param {Participant|undefined} participant - The participant entity.
  275. * @returns {boolean} - True if it's a local screenshare participant.
  276. */
  277. export function isLocalScreenshareParticipant(participant?: Participant): boolean {
  278. return participant?.fakeParticipant === FakeParticipant.LocalScreenShare;
  279. }
  280. /**
  281. * Returns whether the fake participant is a remote screenshare.
  282. *
  283. * @param {Participant|undefined} participant - The participant entity.
  284. * @returns {boolean} - True if it's a remote screenshare participant.
  285. */
  286. export function isRemoteScreenshareParticipant(participant?: Participant): boolean {
  287. return participant?.fakeParticipant === FakeParticipant.RemoteScreenShare;
  288. }
  289. /**
  290. * Returns whether the fake participant is of local or virtual screenshare type.
  291. *
  292. * @param {IState} state - The (whole) redux state, or redux's.
  293. * @param {string|undefined} participantId - The participant id.
  294. * @returns {boolean} - True if it's one of the two.
  295. */
  296. export function isScreenShareParticipantById(state: IState, participantId?: string): boolean {
  297. const participant = getParticipantByIdOrUndefined(state, participantId);
  298. return isScreenShareParticipant(participant);
  299. }
  300. /**
  301. * Returns whether the fake participant is of local or virtual screenshare type.
  302. *
  303. * @param {Participant|undefined} participant - The participant entity.
  304. * @returns {boolean} - True if it's one of the two.
  305. */
  306. export function isScreenShareParticipant(participant?: Participant): boolean {
  307. return isLocalScreenshareParticipant(participant) || isRemoteScreenshareParticipant(participant);
  308. }
  309. /**
  310. * Returns whether the fake participant is a whiteboard.
  311. *
  312. * @param {Participant|undefined} participant - The participant entity.
  313. * @returns {boolean} - True if it's a whiteboard participant.
  314. */
  315. export function isWhiteboardParticipant(participant?: Participant): boolean {
  316. return participant?.fakeParticipant === FakeParticipant.Whiteboard;
  317. }
  318. /**
  319. * Returns a count of the known remote participants in the passed in redux state.
  320. *
  321. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  322. * {@code getState} function to be used to retrieve the state
  323. * features/base/participants.
  324. * @returns {number}
  325. */
  326. export function getRemoteParticipantCount(stateful: IStateful) {
  327. const state = toState(stateful);
  328. const participantsState = state['features/base/participants'];
  329. if (getMultipleVideoSupportFeatureFlag(state)) {
  330. return participantsState.remote.size - participantsState.sortedRemoteVirtualScreenshareParticipants.size;
  331. }
  332. return participantsState.remote.size;
  333. }
  334. /**
  335. * Returns a count of the known participants in the passed in redux state,
  336. * including fake participants.
  337. *
  338. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  339. * {@code getState} function to be used to retrieve the state
  340. * features/base/participants.
  341. * @returns {number}
  342. */
  343. export function getParticipantCountWithFake(stateful: IStateful) {
  344. const state = toState(stateful);
  345. const { local, localScreenShare, remote } = state['features/base/participants'];
  346. if (getMultipleVideoSupportFeatureFlag(state)) {
  347. return remote.size + (local ? 1 : 0) + (localScreenShare ? 1 : 0);
  348. }
  349. return remote.size + (local ? 1 : 0);
  350. }
  351. /**
  352. * Returns participant's display name.
  353. *
  354. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  355. * retrieve the state.
  356. * @param {string} id - The ID of the participant's display name to retrieve.
  357. * @returns {string}
  358. */
  359. export function getParticipantDisplayName(stateful: IStateful, id: string): string {
  360. const state = toState(stateful);
  361. const participant = getParticipantById(state, id);
  362. const {
  363. defaultLocalDisplayName,
  364. defaultRemoteDisplayName
  365. } = state['features/base/config'];
  366. if (participant) {
  367. if (isScreenShareParticipant(participant)) {
  368. return getScreenshareParticipantDisplayName(state, id);
  369. }
  370. if (participant.name) {
  371. return participant.name;
  372. }
  373. if (participant.local) {
  374. return defaultLocalDisplayName ?? '';
  375. }
  376. }
  377. return defaultRemoteDisplayName ?? '';
  378. }
  379. /**
  380. * Returns screenshare participant's display name.
  381. *
  382. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  383. * retrieve the state.
  384. * @param {string} id - The ID of the screenshare participant's display name to retrieve.
  385. * @returns {string}
  386. */
  387. export function getScreenshareParticipantDisplayName(stateful: IStateful, id: string) {
  388. const ownerDisplayName = getParticipantDisplayName(stateful, getVirtualScreenshareParticipantOwnerId(id));
  389. return i18next.t('screenshareDisplayName', { name: ownerDisplayName });
  390. }
  391. /**
  392. * Returns the presence status of a participant associated with the passed id.
  393. *
  394. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  395. * {@code getState} function to be used to retrieve the state.
  396. * @param {string} id - The id of the participant.
  397. * @returns {string} - The presence status.
  398. */
  399. export function getParticipantPresenceStatus(stateful: IStateful, id: string) {
  400. if (!id) {
  401. return undefined;
  402. }
  403. const participantById = getParticipantById(stateful, id);
  404. if (!participantById) {
  405. return undefined;
  406. }
  407. return participantById.presence;
  408. }
  409. /**
  410. * Selectors for getting all remote participants.
  411. *
  412. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  413. * {@code getState} function to be used to retrieve the state
  414. * features/base/participants.
  415. * @returns {Map<string, Object>}
  416. */
  417. export function getRemoteParticipants(stateful: IStateful): Map<string, Participant> {
  418. return toState(stateful)['features/base/participants'].remote;
  419. }
  420. /**
  421. * Selectors for the getting the remote participants in the order that they are displayed in the filmstrip.
  422. *
  423. @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
  424. * retrieve the state features/filmstrip.
  425. * @returns {Array<string>}
  426. */
  427. export function getRemoteParticipantsSorted(stateful: IStateful) {
  428. return toState(stateful)['features/filmstrip'].remoteParticipants;
  429. }
  430. /**
  431. * Returns the participant which has its pinned state set to truthy.
  432. *
  433. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  434. * {@code getState} function to be used to retrieve the state
  435. * features/base/participants.
  436. * @returns {(Participant|undefined)}
  437. */
  438. export function getPinnedParticipant(stateful: IStateful) {
  439. const state = toState(stateful);
  440. const { pinnedParticipant } = state['features/base/participants'];
  441. const stageFilmstrip = isStageFilmstripAvailable(state);
  442. if (stageFilmstrip) {
  443. const { activeParticipants } = state['features/filmstrip'];
  444. const id = activeParticipants.find(p => p.pinned)?.participantId;
  445. return id ? getParticipantById(stateful, id) : undefined;
  446. }
  447. if (!pinnedParticipant) {
  448. return undefined;
  449. }
  450. return getParticipantById(stateful, pinnedParticipant);
  451. }
  452. /**
  453. * Returns true if the participant is a moderator.
  454. *
  455. * @param {string} participant - Participant object.
  456. * @returns {boolean}
  457. */
  458. export function isParticipantModerator(participant?: Participant) {
  459. return participant?.role === PARTICIPANT_ROLE.MODERATOR;
  460. }
  461. /**
  462. * Returns the dominant speaker participant.
  463. *
  464. * @param {(Function|Object)} stateful - The (whole) redux state or redux's
  465. * {@code getState} function to be used to retrieve the state features/base/participants.
  466. * @returns {Participant} - The participant from the redux store.
  467. */
  468. export function getDominantSpeakerParticipant(stateful: IStateful) {
  469. const state = toState(stateful)['features/base/participants'];
  470. const { dominantSpeaker } = state;
  471. if (!dominantSpeaker) {
  472. return undefined;
  473. }
  474. return getParticipantById(stateful, dominantSpeaker);
  475. }
  476. /**
  477. * Returns true if all of the meeting participants are moderators.
  478. *
  479. * @param {Object|Function} stateful -Object or function that can be resolved
  480. * to the Redux state.
  481. * @returns {boolean}
  482. */
  483. export function isEveryoneModerator(stateful: IStateful) {
  484. const state = toState(stateful)['features/base/participants'];
  485. return state.everyoneIsModerator === true;
  486. }
  487. /**
  488. * Checks a value and returns true if it's a preloaded icon object.
  489. *
  490. * @param {?string | ?Object} icon - The icon to check.
  491. * @returns {boolean}
  492. */
  493. export function isIconUrl(icon?: string | Object) {
  494. return Boolean(icon) && (typeof icon === 'object' || typeof icon === 'function');
  495. }
  496. /**
  497. * Returns true if the current local participant is a moderator in the
  498. * conference.
  499. *
  500. * @param {Object|Function} stateful - Object or function that can be resolved
  501. * to the Redux state.
  502. * @returns {boolean}
  503. */
  504. export function isLocalParticipantModerator(stateful: IStateful) {
  505. const state = toState(stateful)['features/base/participants'];
  506. const { local } = state;
  507. if (!local) {
  508. return false;
  509. }
  510. return isParticipantModerator(local);
  511. }
  512. /**
  513. * Returns true if the video of the participant should be rendered.
  514. * NOTE: This is currently only used on mobile.
  515. *
  516. * @param {Object|Function} stateful - Object or function that can be resolved
  517. * to the Redux state.
  518. * @param {string} id - The ID of the participant.
  519. * @returns {boolean}
  520. */
  521. export function shouldRenderParticipantVideo(stateful: IStateful, id: string) {
  522. const state = toState(stateful);
  523. const participant = getParticipantById(state, id);
  524. if (!participant) {
  525. return false;
  526. }
  527. /* First check if we have an unmuted video track. */
  528. const videoTrack = getVideoTrackByParticipant(state, participant);
  529. if (!shouldRenderVideoTrack(videoTrack, /* waitForVideoStarted */ false)) {
  530. return false;
  531. }
  532. /* Then check if the participant connection or track streaming status is active. */
  533. if (getSourceNameSignalingFeatureFlag(state)) {
  534. // Note that this will work only if a listener is registered for the track's TrackStreamingStatus.
  535. // The associated TrackStreamingStatusImpl instance is not created or disposed when there are zero listeners.
  536. if (videoTrack
  537. && !videoTrack.local
  538. && videoTrack.jitsiTrack?.getTrackStreamingStatus() !== JitsiTrackStreamingStatus.ACTIVE) {
  539. return false;
  540. }
  541. } else {
  542. const connectionStatus = participant.connectionStatus || JitsiParticipantConnectionStatus.ACTIVE;
  543. if (connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE) {
  544. return false;
  545. }
  546. }
  547. /* Then check if audio-only mode is not active. */
  548. const audioOnly = state['features/base/audio-only'].enabled;
  549. if (!audioOnly) {
  550. return true;
  551. }
  552. /* Last, check if the participant is sharing their screen and they are on stage. */
  553. const remoteScreenShares = state['features/video-layout'].remoteScreenShares || [];
  554. const largeVideoParticipantId = state['features/large-video'].participantId;
  555. const participantIsInLargeVideoWithScreen
  556. = participant.id === largeVideoParticipantId && remoteScreenShares.includes(participant.id);
  557. return participantIsInLargeVideoWithScreen;
  558. }
  559. /**
  560. * Resolves the first loadable avatar URL for a participant.
  561. *
  562. * @param {Object} participant - The participant to resolve avatars for.
  563. * @param {Store} store - Redux store.
  564. * @returns {?string}
  565. */
  566. async function _getFirstLoadableAvatarUrl(participant: Participant, store: IStore) {
  567. for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
  568. const url = AVATAR_CHECKER_FUNCTIONS[i](participant, store);
  569. if (url !== null) {
  570. if (AVATAR_CHECKED_URLS.has(url)) {
  571. const { isLoadable, isUsingCORS } = AVATAR_CHECKED_URLS.get(url) || {};
  572. if (isLoadable) {
  573. return {
  574. isUsingCORS,
  575. src: url
  576. };
  577. }
  578. } else {
  579. try {
  580. const { corsAvatarURLs } = store.getState()['features/base/config'];
  581. const useCORS = isIconUrl(url) ? false : isCORSAvatarURL(url, corsAvatarURLs);
  582. const { isUsingCORS, src } = await preloadImage(url, useCORS);
  583. AVATAR_CHECKED_URLS.set(src, {
  584. isLoadable: true,
  585. isUsingCORS
  586. });
  587. return {
  588. isUsingCORS,
  589. src
  590. };
  591. } catch (e) {
  592. AVATAR_CHECKED_URLS.set(url, {
  593. isLoadable: false,
  594. isUsingCORS: false
  595. });
  596. }
  597. }
  598. }
  599. }
  600. return undefined;
  601. }
  602. /**
  603. * Get the participants queue with raised hands.
  604. *
  605. * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
  606. * {@code getState} function to be used to retrieve the state
  607. * features/base/participants.
  608. * @returns {Array<Object>}
  609. */
  610. export function getRaiseHandsQueue(stateful: IStateful): Array<{ id: string; raisedHandTimestamp: number; }> {
  611. const { raisedHandsQueue } = toState(stateful)['features/base/participants'];
  612. return raisedHandsQueue;
  613. }
  614. /**
  615. * Returns whether the given participant has his hand raised or not.
  616. *
  617. * @param {Object} participant - The participant.
  618. * @returns {boolean} - Whether participant has raise hand or not.
  619. */
  620. export function hasRaisedHand(participant?: Participant): boolean {
  621. return Boolean(participant?.raisedHandTimestamp);
  622. }