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

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