您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

functions.ts 8.9KB

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