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 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import _ from 'lodash';
  2. import { IReduxState } from '../app/types';
  3. import { getConferenceTimestamp } from '../base/conference/functions';
  4. import { PARTICIPANT_ROLE } from '../base/participants/constants';
  5. import { getParticipantById } from '../base/participants/functions';
  6. import { FaceLandmarks } from '../face-landmarks/types';
  7. import { THRESHOLD_FIXED_AXIS } from './constants';
  8. import { ISpeaker, ISpeakerStats } from './reducer';
  9. /**
  10. * Checks if the speaker stats search is disabled.
  11. *
  12. * @param {IReduxState} state - The redux state.
  13. * @returns {boolean} - True if the speaker stats search is disabled and false otherwise.
  14. */
  15. export function isSpeakerStatsSearchDisabled(state: IReduxState) {
  16. return state['features/base/config']?.speakerStats?.disableSearch;
  17. }
  18. /**
  19. * Checks if the speaker stats is disabled.
  20. *
  21. * @param {IReduxState} state - The redux state.
  22. * @returns {boolean} - True if the speaker stats search is disabled and false otherwise.
  23. */
  24. export function isSpeakerStatsDisabled(state: IReduxState) {
  25. return state['features/base/config']?.speakerStats?.disabled;
  26. }
  27. /**
  28. * Gets whether participants in speaker stats should be ordered or not, and with what priority.
  29. *
  30. * @param {IReduxState} state - The redux state.
  31. * @returns {Array<string>} - The speaker stats order array or an empty array.
  32. */
  33. export function getSpeakerStatsOrder(state: IReduxState) {
  34. return state['features/base/config']?.speakerStats?.order ?? [
  35. 'role',
  36. 'name',
  37. 'hasLeft'
  38. ];
  39. }
  40. /**
  41. * Gets speaker stats.
  42. *
  43. * @param {IReduxState} state - The redux state.
  44. * @returns {Object} - The speaker stats.
  45. */
  46. export function getSpeakerStats(state: IReduxState) {
  47. return state['features/speaker-stats']?.stats ?? {};
  48. }
  49. /**
  50. * Gets speaker stats search criteria.
  51. *
  52. * @param {IReduxState} state - The redux state.
  53. * @returns {string | null} - The search criteria.
  54. */
  55. export function getSearchCriteria(state: IReduxState) {
  56. return state['features/speaker-stats']?.criteria;
  57. }
  58. /**
  59. * Gets if speaker stats reorder is pending.
  60. *
  61. * @param {IReduxState} state - The redux state.
  62. * @returns {boolean} - The pending reorder flag.
  63. */
  64. export function getPendingReorder(state: IReduxState) {
  65. return state['features/speaker-stats']?.pendingReorder ?? false;
  66. }
  67. /**
  68. * Get sorted speaker stats ids based on a configuration setting.
  69. *
  70. * @param {IState} state - The redux state.
  71. * @param {IState} stats - The current speaker stats.
  72. * @returns {string[] | undefined} - Ordered speaker stats ids.
  73. * @public
  74. */
  75. export function getSortedSpeakerStatsIds(state: IReduxState, stats: ISpeakerStats) {
  76. const orderConfig = getSpeakerStatsOrder(state);
  77. if (orderConfig) {
  78. const enhancedStats = getEnhancedStatsForOrdering(state, stats, orderConfig);
  79. return Object.entries(enhancedStats)
  80. .sort(([ , a ], [ , b ]) => compareFn(a, b))
  81. .map(el => el[0]);
  82. }
  83. /**
  84. *
  85. * Compares the order of two participants in the speaker stats list.
  86. *
  87. * @param {ISpeaker} currentParticipant - The first participant for comparison.
  88. * @param {ISpeaker} nextParticipant - The second participant for comparison.
  89. * @returns {number} - The sort order of the two participants.
  90. */
  91. function compareFn(currentParticipant: ISpeaker, nextParticipant: ISpeaker) {
  92. if (orderConfig.includes('hasLeft')) {
  93. if (nextParticipant.hasLeft() && !currentParticipant.hasLeft()) {
  94. return -1;
  95. } else if (currentParticipant.hasLeft() && !nextParticipant.hasLeft()) {
  96. return 1;
  97. }
  98. }
  99. let result = 0;
  100. for (const sortCriteria of orderConfig) {
  101. switch (sortCriteria) {
  102. case 'role':
  103. if (!nextParticipant.isModerator && currentParticipant.isModerator) {
  104. result = -1;
  105. } else if (!currentParticipant.isModerator && nextParticipant.isModerator) {
  106. result = 1;
  107. } else {
  108. result = 0;
  109. }
  110. break;
  111. case 'name':
  112. result = (currentParticipant.displayName || '').localeCompare(
  113. nextParticipant.displayName || ''
  114. );
  115. break;
  116. }
  117. if (result !== 0) {
  118. break;
  119. }
  120. }
  121. return result;
  122. }
  123. }
  124. /**
  125. * Enhance speaker stats to include data needed for ordering.
  126. *
  127. * @param {IState} state - The redux state.
  128. * @param {ISpeakerStats} stats - Speaker stats.
  129. * @param {Array<string>} orderConfig - Ordering configuration.
  130. * @returns {ISpeakerStats} - Enhanced speaker stats.
  131. * @public
  132. */
  133. function getEnhancedStatsForOrdering(state: IReduxState, stats: ISpeakerStats, orderConfig: Array<string>) {
  134. if (!orderConfig) {
  135. return stats;
  136. }
  137. for (const id in stats) {
  138. if (stats[id].hasOwnProperty('_hasLeft') && !stats[id].hasLeft()) {
  139. if (orderConfig.includes('role')) {
  140. const participant = getParticipantById(state, stats[id].getUserId());
  141. stats[id].isModerator = participant && participant.role === PARTICIPANT_ROLE.MODERATOR;
  142. }
  143. }
  144. }
  145. return stats;
  146. }
  147. /**
  148. * Filter stats by search criteria.
  149. *
  150. * @param {IState} state - The redux state.
  151. * @param {ISpeakerStats | undefined} stats - The unfiltered stats.
  152. *
  153. * @returns {ISpeakerStats} - Filtered speaker stats.
  154. * @public
  155. */
  156. export function filterBySearchCriteria(state: IReduxState, stats?: ISpeakerStats) {
  157. const filteredStats = _.cloneDeep(stats ?? getSpeakerStats(state));
  158. const criteria = getSearchCriteria(state);
  159. if (criteria !== null) {
  160. const searchRegex = new RegExp(criteria, 'gi');
  161. for (const id in filteredStats) {
  162. if (filteredStats[id].hasOwnProperty('_isLocalStats')) {
  163. const name = filteredStats[id].getDisplayName();
  164. filteredStats[id].hidden = !name || !name.match(searchRegex);
  165. }
  166. }
  167. }
  168. return filteredStats;
  169. }
  170. /**
  171. * Reset the hidden speaker stats.
  172. *
  173. * @param {IState} state - The redux state.
  174. * @param {ISpeakerStats | undefined} stats - The unfiltered stats.
  175. *
  176. * @returns {Object} - Speaker stats.
  177. * @public
  178. */
  179. export function resetHiddenStats(state: IReduxState, stats?: ISpeakerStats) {
  180. const resetStats = _.cloneDeep(stats ?? getSpeakerStats(state));
  181. for (const id in resetStats) {
  182. if (resetStats[id].hidden) {
  183. resetStats[id].hidden = false;
  184. }
  185. }
  186. return resetStats;
  187. }
  188. /**
  189. * Gets the current duration of the conference.
  190. *
  191. * @param {IState} state - The redux state.
  192. * @returns {number | null} - The duration in milliseconds or null.
  193. */
  194. export function getCurrentDuration(state: IReduxState) {
  195. const startTimestamp = getConferenceTimestamp(state);
  196. return startTimestamp ? Date.now() - startTimestamp : null;
  197. }
  198. /**
  199. * Gets the boundaries of the emotion timeline.
  200. *
  201. * @param {IState} state - The redux state.
  202. * @returns {Object} - The left and right boundaries.
  203. */
  204. export function getTimelineBoundaries(state: IReduxState) {
  205. const { timelineBoundary, offsetLeft, offsetRight } = state['features/speaker-stats'];
  206. const currentDuration = getCurrentDuration(state) ?? 0;
  207. const rightBoundary = timelineBoundary ? timelineBoundary : currentDuration;
  208. let leftOffset = 0;
  209. if (rightBoundary > THRESHOLD_FIXED_AXIS) {
  210. leftOffset = rightBoundary - THRESHOLD_FIXED_AXIS;
  211. }
  212. const left = offsetLeft + leftOffset;
  213. const right = rightBoundary + offsetRight;
  214. return {
  215. left,
  216. right
  217. };
  218. }
  219. /**
  220. * Returns the conference start time of the face landmarks.
  221. *
  222. * @param {FaceLandmarks} faceLandmarks - The face landmarks.
  223. * @param {number} startTimestamp - The start timestamp of the conference.
  224. * @returns {number}
  225. */
  226. export function getFaceLandmarksStart(faceLandmarks: FaceLandmarks, startTimestamp: number) {
  227. return faceLandmarks.timestamp - startTimestamp;
  228. }
  229. /**
  230. * Returns the conference end time of the face landmarks.
  231. *
  232. * @param {FaceLandmarks} faceLandmarks - The face landmarks.
  233. * @param {number} startTimestamp - The start timestamp of the conference.
  234. * @returns {number}
  235. */
  236. export function getFaceLandmarksEnd(faceLandmarks: FaceLandmarks, startTimestamp: number) {
  237. return getFaceLandmarksStart(faceLandmarks, startTimestamp) + faceLandmarks.duration;
  238. }