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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. // @flow
  2. import type { Dispatch } from 'redux';
  3. import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
  4. import {
  5. createSelectParticipantFailedEvent,
  6. sendAnalytics
  7. } from '../analytics';
  8. import { _handleParticipantError } from '../base/conference';
  9. import { MEDIA_TYPE } from '../base/media';
  10. import { getParticipants } from '../base/participants';
  11. import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
  12. import { reportError } from '../base/util';
  13. import { shouldDisplayTileView } from '../video-layout';
  14. import {
  15. SELECT_LARGE_VIDEO_PARTICIPANT,
  16. UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
  17. } from './actionTypes';
  18. declare var APP: Object;
  19. /**
  20. * Captures a screenshot of the video displayed on the large video.
  21. *
  22. * @returns {Function}
  23. */
  24. export function captureLargeVideoScreenshot() {
  25. return (dispatch: Dispatch<any>, getState: Function): Promise<Object> => {
  26. const state = getState();
  27. const largeVideo = state['features/large-video'];
  28. if (!largeVideo) {
  29. return Promise.resolve();
  30. }
  31. const tracks = state['features/base/tracks'];
  32. const { jitsiTrack } = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
  33. const videoStream = jitsiTrack.getOriginalStream();
  34. // Create a HTML canvas and draw video from the track on to the canvas.
  35. const [ track ] = videoStream.getVideoTracks();
  36. const { height, width } = track.getSettings() ?? track.getConstraints();
  37. const canvasElement = document.createElement('canvas');
  38. const ctx = canvasElement.getContext('2d');
  39. const videoElement = document.createElement('video');
  40. videoElement.height = parseInt(height, 10);
  41. videoElement.width = parseInt(width, 10);
  42. videoElement.autoplay = true;
  43. videoElement.srcObject = videoStream;
  44. canvasElement.height = videoElement.height;
  45. canvasElement.width = videoElement.width;
  46. // Wait for the video to load before drawing on to the canvas.
  47. const promise = new Promise(resolve => {
  48. videoElement.onloadeddata = () => resolve();
  49. });
  50. return promise.then(() => {
  51. ctx.drawImage(videoElement, 0, 0, videoElement.width, videoElement.height);
  52. const dataURL = canvasElement.toDataURL('image/png', 1.0);
  53. // Cleanup.
  54. ctx.clearRect(0, 0, videoElement.width, videoElement.height);
  55. videoElement.srcObject = null;
  56. canvasElement.remove();
  57. videoElement.remove();
  58. return Promise.resolve(dataURL);
  59. });
  60. };
  61. }
  62. /**
  63. * Resizes the large video container based on the dimensions provided.
  64. *
  65. * @param {number} width - Width that needs to be applied on the large video container.
  66. * @param {number} height - Height that needs to be applied on the large video container.
  67. * @returns {Function}
  68. */
  69. export function resizeLargeVideo(width: number, height: number) {
  70. return (dispatch: Dispatch<any>, getState: Function) => {
  71. const state = getState();
  72. const largeVideo = state['features/large-video'];
  73. if (largeVideo) {
  74. const largeVideoContainer = VideoLayout.getLargeVideo();
  75. largeVideoContainer.updateContainerSize(width, height);
  76. largeVideoContainer.resize();
  77. }
  78. };
  79. }
  80. /**
  81. * Signals conference to select a participant.
  82. *
  83. * @returns {Function}
  84. */
  85. export function selectParticipant() {
  86. return (dispatch: Dispatch<any>, getState: Function) => {
  87. const state = getState();
  88. const { conference } = state['features/base/conference'];
  89. if (conference) {
  90. const ids = shouldDisplayTileView(state)
  91. ? getParticipants(state).map(participant => participant.id)
  92. : [ state['features/large-video'].participantId ];
  93. try {
  94. conference.selectParticipants(ids);
  95. } catch (err) {
  96. _handleParticipantError(err);
  97. sendAnalytics(createSelectParticipantFailedEvent(err));
  98. reportError(
  99. err, `Failed to select participants ${ids.toString()}`);
  100. }
  101. }
  102. };
  103. }
  104. /**
  105. * Action to select the participant to be displayed in LargeVideo based on the
  106. * participant id provided. If a participant id is not provided, the LargeVideo
  107. * participant will be selected based on a variety of factors: If there is a
  108. * dominant or pinned speaker, or if there are remote tracks, etc.
  109. *
  110. * @param {string} participant - The participant id of the user that needs to be
  111. * displayed on the large video.
  112. * @returns {Function}
  113. */
  114. export function selectParticipantInLargeVideo(participant: ?string) {
  115. return (dispatch: Dispatch<any>, getState: Function) => {
  116. const state = getState();
  117. const participantId = participant ?? _electParticipantInLargeVideo(state);
  118. const largeVideo = state['features/large-video'];
  119. if (participantId !== largeVideo.participantId) {
  120. dispatch({
  121. type: SELECT_LARGE_VIDEO_PARTICIPANT,
  122. participantId
  123. });
  124. dispatch(selectParticipant());
  125. }
  126. };
  127. }
  128. /**
  129. * Updates the currently seen resolution of the video displayed on large video.
  130. *
  131. * @param {number} resolution - The current resolution (height) of the video.
  132. * @returns {{
  133. * type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
  134. * resolution: number
  135. * }}
  136. */
  137. export function updateKnownLargeVideoResolution(resolution: number) {
  138. return {
  139. type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
  140. resolution
  141. };
  142. }
  143. /**
  144. * Returns the most recent existing remote video track.
  145. *
  146. * @param {Track[]} tracks - All current tracks.
  147. * @private
  148. * @returns {(Track|undefined)}
  149. */
  150. function _electLastVisibleRemoteVideo(tracks) {
  151. // First we try to get most recent remote video track.
  152. for (let i = tracks.length - 1; i >= 0; --i) {
  153. const track = tracks[i];
  154. if (!track.local && track.mediaType === MEDIA_TYPE.VIDEO) {
  155. return track;
  156. }
  157. }
  158. }
  159. /**
  160. * Returns the identifier of the participant who is to be on the stage and
  161. * should be displayed in {@code LargeVideo}.
  162. *
  163. * @param {Object} state - The Redux state from which the participant to be
  164. * displayed in {@code LargeVideo} is to be elected.
  165. * @private
  166. * @returns {(string|undefined)}
  167. */
  168. function _electParticipantInLargeVideo(state) {
  169. // 1. If a participant is pinned, they will be shown in the LargeVideo (
  170. // regardless of whether they are local or remote).
  171. const participants = state['features/base/participants'];
  172. let participant = participants.find(p => p.pinned);
  173. let id = participant && participant.id;
  174. if (!id) {
  175. // 2. No participant is pinned so get the dominant speaker. But the
  176. // local participant won't be displayed in LargeVideo even if she is
  177. // the dominant speaker.
  178. participant = participants.find(p => p.dominantSpeaker && !p.local);
  179. id = participant && participant.id;
  180. if (!id) {
  181. // 3. There is no dominant speaker so select the remote participant
  182. // who last had visible video.
  183. const tracks = state['features/base/tracks'];
  184. const videoTrack = _electLastVisibleRemoteVideo(tracks);
  185. id = videoTrack && videoTrack.participantId;
  186. if (!id) {
  187. // 4. It's possible there is no participant with visible video.
  188. // This can happen for a number of reasons:
  189. // - there is only one participant (i.e. the local user),
  190. // - other participants joined with video muted.
  191. // As a last resort, pick the last participant who joined the
  192. // conference (regardless of whether they are local or
  193. // remote).
  194. //
  195. // HOWEVER: We don't want to show poltergeist or other bot type participants on stage
  196. // automatically, because it's misleading (users may think they are already
  197. // joined and maybe speaking).
  198. for (let i = participants.length; i > 0 && !participant; i--) {
  199. const p = participants[i - 1];
  200. !p.botType && (participant = p);
  201. }
  202. id = participant && participant.id;
  203. }
  204. }
  205. }
  206. return id;
  207. }