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.8KB

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