mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-16 02:17:47 +00:00
Allow service change when only Dropbox and Local recording are enabled Add space between REC indicator and meeting title Hide Recording button if the feature is enabled but not supported Don't play Stop recording sound on self recording
284 lines
9.1 KiB
JavaScript
284 lines
9.1 KiB
JavaScript
// @flow
|
|
|
|
import { isMobileBrowser } from '../base/environment/utils';
|
|
import { JitsiRecordingConstants, browser } from '../base/lib-jitsi-meet';
|
|
import { getLocalParticipant, getRemoteParticipants, isLocalParticipantModerator } from '../base/participants';
|
|
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
|
import { isEnabled as isDropboxEnabled } from '../dropbox';
|
|
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
|
|
|
|
import LocalRecordingManager from './components/Recording/LocalRecordingManager';
|
|
import { RECORDING_STATUS_PRIORITIES, RECORDING_TYPES } from './constants';
|
|
import logger from './logger';
|
|
|
|
/**
|
|
* Searches in the passed in redux state for an active recording session of the
|
|
* passed in mode.
|
|
*
|
|
* @param {Object} state - The redux state to search in.
|
|
* @param {string} mode - Find an active recording session of the given mode.
|
|
* @returns {Object|undefined}
|
|
*/
|
|
export function getActiveSession(state: Object, mode: string) {
|
|
const { sessionDatas } = state['features/recording'];
|
|
const { status: statusConstants } = JitsiRecordingConstants;
|
|
|
|
return sessionDatas.find(sessionData => sessionData.mode === mode
|
|
&& (sessionData.status === statusConstants.ON
|
|
|| sessionData.status === statusConstants.PENDING));
|
|
}
|
|
|
|
/**
|
|
* Returns an estimated recording duration based on the size of the video file
|
|
* in MB. The estimate is calculated under the assumption that 1 min of recorded
|
|
* video needs 10MB of storage on average.
|
|
*
|
|
* @param {number} size - The size in MB of the recorded video.
|
|
* @returns {number} - The estimated duration in minutes.
|
|
*/
|
|
export function getRecordingDurationEstimation(size: ?number) {
|
|
return Math.floor((size || 0) / 10);
|
|
}
|
|
|
|
/**
|
|
* Searches in the passed in redux state for a recording session that matches
|
|
* the passed in recording session ID.
|
|
*
|
|
* @param {Object} state - The redux state to search in.
|
|
* @param {string} id - The ID of the recording session to find.
|
|
* @returns {Object|undefined}
|
|
*/
|
|
export function getSessionById(state: Object, id: string) {
|
|
return state['features/recording'].sessionDatas.find(
|
|
sessionData => sessionData.id === id);
|
|
}
|
|
|
|
/**
|
|
* Fetches the recording link from the server.
|
|
*
|
|
* @param {string} url - The base url.
|
|
* @param {string} recordingSessionId - The ID of the recording session to find.
|
|
* @param {string} region - The meeting region.
|
|
* @param {string} tenant - The meeting tenant.
|
|
* @returns {Promise<any>}
|
|
*/
|
|
export async function getRecordingLink(url: string, recordingSessionId: string, region: string, tenant: string) {
|
|
const fullUrl = `${url}?recordingSessionId=${recordingSessionId}®ion=${region}&tenant=${tenant}`;
|
|
const res = await fetch(fullUrl, {
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
const json = await res.json();
|
|
|
|
return res.ok ? json : Promise.reject(json);
|
|
}
|
|
|
|
/**
|
|
* Selector used for determining if recording is saved on dropbox.
|
|
*
|
|
* @param {Object} state - The redux state to search in.
|
|
* @returns {string}
|
|
*/
|
|
export function isSavingRecordingOnDropbox(state: Object) {
|
|
return isDropboxEnabled(state)
|
|
&& state['features/recording'].selectedRecordingService === RECORDING_TYPES.DROPBOX;
|
|
}
|
|
|
|
/**
|
|
* Selector used for determining disable state for the meeting highlight button.
|
|
*
|
|
* @param {Object} state - The redux state to search in.
|
|
* @returns {string}
|
|
*/
|
|
export function isHighlightMeetingMomentDisabled(state: Object) {
|
|
return state['features/recording'].disableHighlightMeetingMoment;
|
|
}
|
|
|
|
/**
|
|
* Returns the recording session status that is to be shown in a label. E.g. If
|
|
* there is a session with the status OFF and one with PENDING, then the PENDING
|
|
* one will be shown, because that is likely more important for the user to see.
|
|
*
|
|
* @param {Object} state - The redux state to search in.
|
|
* @param {string} mode - The recording mode to get status for.
|
|
* @returns {string|undefined}
|
|
*/
|
|
export function getSessionStatusToShow(state: Object, mode: string): ?string {
|
|
const recordingSessions = state['features/recording'].sessionDatas;
|
|
let status;
|
|
|
|
if (Array.isArray(recordingSessions)) {
|
|
for (const session of recordingSessions) {
|
|
if (session.mode === mode
|
|
&& (!status
|
|
|| (RECORDING_STATUS_PRIORITIES.indexOf(session.status)
|
|
> RECORDING_STATUS_PRIORITIES.indexOf(status)))) {
|
|
status = session.status;
|
|
}
|
|
}
|
|
}
|
|
if ((!Array.isArray(recordingSessions) || recordingSessions.length === 0)
|
|
&& mode === JitsiRecordingConstants.mode.FILE
|
|
&& (LocalRecordingManager.isRecordingLocally() || isRemoteParticipantRecordingLocally(state))) {
|
|
status = JitsiRecordingConstants.status.ON;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Check if local recording is supported.
|
|
*
|
|
* @returns {boolean} - Wether local recording is supported or not.
|
|
*/
|
|
export function supportsLocalRecording() {
|
|
return browser.isChromiumBased() && !browser.isElectron() && !isMobileBrowser()
|
|
&& navigator.product !== 'ReactNative';
|
|
}
|
|
|
|
/**
|
|
* Returns the recording button props.
|
|
*
|
|
* @param {Object} state - The redux state to search in.
|
|
*
|
|
* @returns {{
|
|
* disabled: boolean,
|
|
* tooltip: string,
|
|
* visible: boolean
|
|
* }}
|
|
*/
|
|
export function getRecordButtonProps(state: Object): ?string {
|
|
let visible;
|
|
|
|
// a button can be disabled/enabled if enableFeaturesBasedOnToken
|
|
// is on or if the livestreaming is running.
|
|
let disabled;
|
|
let tooltip = '';
|
|
|
|
// If the containing component provides the visible prop, that is one
|
|
// above all, but if not, the button should be autonomus and decide on
|
|
// its own to be visible or not.
|
|
const isModerator = isLocalParticipantModerator(state);
|
|
const {
|
|
enableFeaturesBasedOnToken,
|
|
recordingService,
|
|
localRecording
|
|
} = state['features/base/config'];
|
|
const { features = {} } = getLocalParticipant(state);
|
|
const localRecordingEnabled = !localRecording?.disable && supportsLocalRecording();
|
|
|
|
const dropboxEnabled = isDropboxEnabled(state);
|
|
|
|
visible = isModerator && (recordingService?.enabled || localRecordingEnabled || dropboxEnabled);
|
|
|
|
if (enableFeaturesBasedOnToken) {
|
|
visible = visible && String(features.recording) === 'true';
|
|
disabled = String(features.recording) === 'disabled';
|
|
if (!visible && !disabled) {
|
|
disabled = true;
|
|
visible = true;
|
|
tooltip = 'dialog.recordingDisabledTooltip';
|
|
}
|
|
}
|
|
|
|
// disable the button if the livestreaming is running.
|
|
if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
|
|
disabled = true;
|
|
tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
|
|
}
|
|
|
|
// disable the button if we are in a breakout room.
|
|
if (isInBreakoutRoom(state)) {
|
|
disabled = true;
|
|
visible = false;
|
|
}
|
|
|
|
return {
|
|
disabled,
|
|
tooltip,
|
|
visible
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the resource id.
|
|
*
|
|
* @param {Object | string} recorder - A participant or it's resource.
|
|
* @returns {string|undefined}
|
|
*/
|
|
export function getResourceId(recorder: string | Object) {
|
|
if (recorder) {
|
|
return typeof recorder === 'string'
|
|
? recorder
|
|
: recorder.getId();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends a meeting highlight to backend.
|
|
*
|
|
* @param {Object} state - Redux state.
|
|
* @returns {boolean} - True if sent, false otherwise.
|
|
*/
|
|
export async function sendMeetingHighlight(state: Object) {
|
|
const { webhookProxyUrl: url } = state['features/base/config'];
|
|
const { conference } = state['features/base/conference'];
|
|
const { jwt } = state['features/base/jwt'];
|
|
const { connection } = state['features/base/connection'];
|
|
const jid = connection.getJid();
|
|
const localParticipant = getLocalParticipant(state);
|
|
|
|
const headers = {
|
|
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
const reqBody = {
|
|
meetingFqn: extractFqnFromPath(state),
|
|
sessionId: conference.getMeetingUniqueId(),
|
|
submitted: Date.now(),
|
|
participantId: localParticipant.jwtId,
|
|
participantName: localParticipant.name,
|
|
participantJid: jid
|
|
};
|
|
|
|
if (url) {
|
|
try {
|
|
const res = await fetch(`${url}/v2/highlights`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify(reqBody)
|
|
});
|
|
|
|
if (res.ok) {
|
|
return true;
|
|
}
|
|
logger.error('Status error:', res.status);
|
|
} catch (err) {
|
|
logger.error('Could not send request', err);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Whether a remote participant is recording locally or not.
|
|
*
|
|
* @param {Object} state - Redux state.
|
|
* @returns {boolean}
|
|
*/
|
|
function isRemoteParticipantRecordingLocally(state) {
|
|
const participants = getRemoteParticipants(state);
|
|
|
|
// eslint-disable-next-line prefer-const
|
|
for (let value of participants.values()) {
|
|
if (value.localRecording) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|