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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // @flow
  2. import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
  3. import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
  4. import { isInBreakoutRoom } from '../breakout-rooms/functions';
  5. import { isEnabled as isDropboxEnabled } from '../dropbox';
  6. import { extractFqnFromPath } from '../dynamic-branding';
  7. import { RECORDING_STATUS_PRIORITIES, RECORDING_TYPES } from './constants';
  8. import logger from './logger';
  9. /**
  10. * Searches in the passed in redux state for an active recording session of the
  11. * passed in mode.
  12. *
  13. * @param {Object} state - The redux state to search in.
  14. * @param {string} mode - Find an active recording session of the given mode.
  15. * @returns {Object|undefined}
  16. */
  17. export function getActiveSession(state: Object, mode: string) {
  18. const { sessionDatas } = state['features/recording'];
  19. const { status: statusConstants } = JitsiRecordingConstants;
  20. return sessionDatas.find(sessionData => sessionData.mode === mode
  21. && (sessionData.status === statusConstants.ON
  22. || sessionData.status === statusConstants.PENDING));
  23. }
  24. /**
  25. * Returns an estimated recording duration based on the size of the video file
  26. * in MB. The estimate is calculated under the assumption that 1 min of recorded
  27. * video needs 10MB of storage on average.
  28. *
  29. * @param {number} size - The size in MB of the recorded video.
  30. * @returns {number} - The estimated duration in minutes.
  31. */
  32. export function getRecordingDurationEstimation(size: ?number) {
  33. return Math.floor((size || 0) / 10);
  34. }
  35. /**
  36. * Searches in the passed in redux state for a recording session that matches
  37. * the passed in recording session ID.
  38. *
  39. * @param {Object} state - The redux state to search in.
  40. * @param {string} id - The ID of the recording session to find.
  41. * @returns {Object|undefined}
  42. */
  43. export function getSessionById(state: Object, id: string) {
  44. return state['features/recording'].sessionDatas.find(
  45. sessionData => sessionData.id === id);
  46. }
  47. /**
  48. * Fetches the recording link from the server.
  49. *
  50. * @param {string} url - The base url.
  51. * @param {string} recordingSessionId - The ID of the recording session to find.
  52. * @param {string} region - The meeting region.
  53. * @param {string} tenant - The meeting tenant.
  54. * @returns {Promise<any>}
  55. */
  56. export async function getRecordingLink(url: string, recordingSessionId: string, region: string, tenant: string) {
  57. const fullUrl = `${url}?recordingSessionId=${recordingSessionId}&region=${region}&tenant=${tenant}`;
  58. const res = await fetch(fullUrl, {
  59. headers: {
  60. 'Content-Type': 'application/json'
  61. }
  62. });
  63. const json = await res.json();
  64. return res.ok ? json : Promise.reject(json);
  65. }
  66. /**
  67. * Selector used for determining if recording is saved on dropbox.
  68. *
  69. * @param {Object} state - The redux state to search in.
  70. * @returns {string}
  71. */
  72. export function isSavingRecordingOnDropbox(state: Object) {
  73. return isDropboxEnabled(state)
  74. && state['features/recording'].selectedRecordingService === RECORDING_TYPES.DROPBOX;
  75. }
  76. /**
  77. * Selector used for determining disable state for the meeting highlight button.
  78. *
  79. * @param {Object} state - The redux state to search in.
  80. * @returns {string}
  81. */
  82. export function isHighlightMeetingMomentDisabled(state: Object) {
  83. return state['features/recording'].disableHighlightMeetingMoment;
  84. }
  85. /**
  86. * Returns the recording session status that is to be shown in a label. E.g. If
  87. * there is a session with the status OFF and one with PENDING, then the PENDING
  88. * one will be shown, because that is likely more important for the user to see.
  89. *
  90. * @param {Object} state - The redux state to search in.
  91. * @param {string} mode - The recording mode to get status for.
  92. * @returns {string|undefined}
  93. */
  94. export function getSessionStatusToShow(state: Object, mode: string): ?string {
  95. const recordingSessions = state['features/recording'].sessionDatas;
  96. let status;
  97. if (Array.isArray(recordingSessions)) {
  98. for (const session of recordingSessions) {
  99. if (session.mode === mode
  100. && (!status
  101. || (RECORDING_STATUS_PRIORITIES.indexOf(session.status)
  102. > RECORDING_STATUS_PRIORITIES.indexOf(status)))) {
  103. status = session.status;
  104. }
  105. }
  106. }
  107. return status;
  108. }
  109. /**
  110. * Returns the recording button props.
  111. *
  112. * @param {Object} state - The redux state to search in.
  113. *
  114. * @returns {{
  115. * disabled: boolean,
  116. * tooltip: string,
  117. * visible: boolean
  118. * }}
  119. */
  120. export function getRecordButtonProps(state: Object): ?string {
  121. let visible;
  122. // a button can be disabled/enabled if enableFeaturesBasedOnToken
  123. // is on or if the livestreaming is running.
  124. let disabled;
  125. let tooltip = '';
  126. // If the containing component provides the visible prop, that is one
  127. // above all, but if not, the button should be autonomus and decide on
  128. // its own to be visible or not.
  129. const isModerator = isLocalParticipantModerator(state);
  130. const {
  131. enableFeaturesBasedOnToken,
  132. fileRecordingsEnabled
  133. } = state['features/base/config'];
  134. const { features = {} } = getLocalParticipant(state);
  135. visible = isModerator && fileRecordingsEnabled;
  136. if (enableFeaturesBasedOnToken) {
  137. visible = visible && String(features.recording) === 'true';
  138. disabled = String(features.recording) === 'disabled';
  139. if (!visible && !disabled) {
  140. disabled = true;
  141. visible = true;
  142. tooltip = 'dialog.recordingDisabledTooltip';
  143. }
  144. }
  145. // disable the button if the livestreaming is running.
  146. if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
  147. disabled = true;
  148. tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
  149. }
  150. // disable the button if we are in a breakout room.
  151. if (isInBreakoutRoom(state)) {
  152. disabled = true;
  153. visible = false;
  154. }
  155. return {
  156. disabled,
  157. tooltip,
  158. visible
  159. };
  160. }
  161. /**
  162. * Returns the resource id.
  163. *
  164. * @param {Object | string} recorder - A participant or it's resource.
  165. * @returns {string|undefined}
  166. */
  167. export function getResourceId(recorder: string | Object) {
  168. if (recorder) {
  169. return typeof recorder === 'string'
  170. ? recorder
  171. : recorder.getId();
  172. }
  173. }
  174. /**
  175. * Sends a meeting highlight to backend.
  176. *
  177. * @param {Object} state - Redux state.
  178. * @returns {boolean} - True if sent, false otherwise.
  179. */
  180. export async function sendMeetingHighlight(state: Object) {
  181. const { webhookProxyUrl: url } = state['features/base/config'];
  182. const { conference } = state['features/base/conference'];
  183. const { jwt } = state['features/base/jwt'];
  184. const { connection } = state['features/base/connection'];
  185. const jid = connection.getJid();
  186. const localParticipant = getLocalParticipant(state);
  187. const headers = {
  188. ...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
  189. 'Content-Type': 'application/json'
  190. };
  191. const reqBody = {
  192. meetingFqn: extractFqnFromPath(state),
  193. sessionId: conference.getMeetingUniqueId(),
  194. submitted: Date.now(),
  195. participantId: localParticipant.jwtId,
  196. participantName: localParticipant.name,
  197. participantJid: jid
  198. };
  199. if (url) {
  200. try {
  201. const res = await fetch(`${url}/v2/highlights`, {
  202. method: 'POST',
  203. headers,
  204. body: JSON.stringify(reqBody)
  205. });
  206. if (res.ok) {
  207. return true;
  208. }
  209. logger.error('Status error:', res.status);
  210. } catch (err) {
  211. logger.error('Could not send request', err);
  212. }
  213. }
  214. return false;
  215. }