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.

actions.any.ts 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { IReduxState, IStore } from '../app/types';
  2. import { IStateful } from '../base/app/types';
  3. import { MEDIA_TYPE } from '../base/media/constants';
  4. import {
  5. getDominantSpeakerParticipant,
  6. getLocalParticipant,
  7. getLocalScreenShareParticipant,
  8. getParticipantById,
  9. getPinnedParticipant,
  10. getRemoteParticipants,
  11. getVirtualScreenshareParticipantByOwnerId
  12. } from '../base/participants/functions';
  13. import { toState } from '../base/redux/functions';
  14. import { isStageFilmstripAvailable } from '../filmstrip/functions';
  15. import { getAutoPinSetting } from '../video-layout/functions';
  16. import {
  17. SELECT_LARGE_VIDEO_PARTICIPANT,
  18. SET_LARGE_VIDEO_DIMENSIONS,
  19. UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
  20. } from './actionTypes';
  21. /**
  22. * Action to select the participant to be displayed in LargeVideo based on the
  23. * participant id provided. If a participant id is not provided, the LargeVideo
  24. * participant will be selected based on a variety of factors: If there is a
  25. * dominant or pinned speaker, or if there are remote tracks, etc.
  26. *
  27. * @param {string} participant - The participant id of the user that needs to be
  28. * displayed on the large video.
  29. * @returns {Function}
  30. */
  31. export function selectParticipantInLargeVideo(participant?: string) {
  32. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  33. const state = getState();
  34. if (isStageFilmstripAvailable(state, 2)) {
  35. return;
  36. }
  37. // Keep Etherpad open.
  38. if (state['features/etherpad'].editing) {
  39. return;
  40. }
  41. const participantId = participant ?? _electParticipantInLargeVideo(state);
  42. const largeVideo = state['features/large-video'];
  43. const remoteScreenShares = state['features/video-layout'].remoteScreenShares;
  44. let latestScreenshareParticipantId;
  45. if (remoteScreenShares?.length) {
  46. latestScreenshareParticipantId = remoteScreenShares[remoteScreenShares.length - 1];
  47. }
  48. // When trying to auto pin screenshare, always select the endpoint even though it happens to be
  49. // the large video participant in redux (for the reasons listed above in the large video selection
  50. // logic above). The auto pin screenshare logic kicks in after the track is added
  51. // (which updates the large video participant and selects all endpoints because of the auto tile
  52. // view mode). If the screenshare endpoint is not among the forwarded endpoints from the bridge,
  53. // it needs to be selected again at this point.
  54. if (participantId !== largeVideo.participantId || participantId === latestScreenshareParticipantId) {
  55. dispatch({
  56. type: SELECT_LARGE_VIDEO_PARTICIPANT,
  57. participantId
  58. });
  59. }
  60. };
  61. }
  62. /**
  63. * Updates the currently seen resolution of the video displayed on large video.
  64. *
  65. * @param {number} resolution - The current resolution (height) of the video.
  66. * @returns {{
  67. * type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
  68. * resolution: number
  69. * }}
  70. */
  71. export function updateKnownLargeVideoResolution(resolution: number) {
  72. return {
  73. type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
  74. resolution
  75. };
  76. }
  77. /**
  78. * Sets the dimensions of the large video in redux.
  79. *
  80. * @param {number} height - The height of the large video.
  81. * @param {number} width - The width of the large video.
  82. * @returns {{
  83. * type: SET_LARGE_VIDEO_DIMENSIONS,
  84. * height: number,
  85. * width: number
  86. * }}
  87. */
  88. export function setLargeVideoDimensions(height: number, width: number) {
  89. return {
  90. type: SET_LARGE_VIDEO_DIMENSIONS,
  91. height,
  92. width
  93. };
  94. }
  95. /**
  96. * Returns the most recent existing remote video track.
  97. *
  98. * @param {Function|Object} stateful - The redux store or {@code getState} function.
  99. * @private
  100. * @returns {(Track|undefined)}
  101. */
  102. function _electLastVisibleRemoteParticipant(stateful: IStateful) {
  103. const state = toState(stateful);
  104. const tracks = state['features/base/tracks'];
  105. // First we try to get most recent remote video track.
  106. for (let i = tracks.length - 1; i >= 0; --i) {
  107. const track = tracks[i];
  108. if (!track.local && track.mediaType === MEDIA_TYPE.VIDEO && track.participantId) {
  109. const participant = getParticipantById(state, track.participantId);
  110. if (participant) {
  111. return participant;
  112. }
  113. }
  114. }
  115. }
  116. /**
  117. * Returns the identifier of the participant who is to be on the stage and
  118. * should be displayed in {@code LargeVideo}.
  119. *
  120. * @param {Object} state - The Redux state from which the participant to be
  121. * displayed in {@code LargeVideo} is to be elected.
  122. * @private
  123. * @returns {(string|undefined)}
  124. */
  125. function _electParticipantInLargeVideo(state: IReduxState) {
  126. // If a participant is pinned, they will be shown in the LargeVideo (regardless of whether they are local or
  127. // remote) when the filmstrip on stage is disabled.
  128. let participant = getPinnedParticipant(state);
  129. if (participant) {
  130. return participant.id;
  131. }
  132. const autoPinSetting = getAutoPinSetting();
  133. if (autoPinSetting) {
  134. // when the setting auto_pin_latest_screen_share is true as spot does, prioritize local screenshare
  135. if (autoPinSetting === true) {
  136. const localScreenShareParticipant = getLocalScreenShareParticipant(state);
  137. if (localScreenShareParticipant) {
  138. return localScreenShareParticipant.id;
  139. }
  140. }
  141. // Pick the most recent remote screenshare that was added to the conference.
  142. const remoteScreenShares = state['features/video-layout'].remoteScreenShares;
  143. if (remoteScreenShares?.length) {
  144. return remoteScreenShares[remoteScreenShares.length - 1];
  145. }
  146. }
  147. // Next, pick the dominant speaker (other than self).
  148. participant = getDominantSpeakerParticipant(state);
  149. if (participant && !participant.local) {
  150. // Return the screensharing participant id associated with this endpoint if multi-stream is enabled and
  151. // auto_pin_latest_screen_share setting is disabled.
  152. const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, participant.id);
  153. return screenshareParticipant?.id ?? participant.id;
  154. }
  155. // In case this is the local participant.
  156. participant = undefined;
  157. // Next, pick the most recent participant with video.
  158. const lastVisibleRemoteParticipant = _electLastVisibleRemoteParticipant(state);
  159. if (lastVisibleRemoteParticipant) {
  160. return lastVisibleRemoteParticipant.id;
  161. }
  162. // Last, select the participant that joined last (other than poltergist or other bot type participants).
  163. const participants = [ ...getRemoteParticipants(state).values() ];
  164. for (let i = participants.length; i > 0 && !participant; i--) {
  165. const p = participants[i - 1];
  166. !p.botType && (participant = p);
  167. }
  168. if (participant) {
  169. return participant.id;
  170. }
  171. return getLocalParticipant(state)?.id;
  172. }