jiti-meet/react/features/recording/functions.ts

277 lines
8.9 KiB
TypeScript
Raw Normal View History

import { IReduxState } from '../app/types';
import { isMobileBrowser } from '../base/environment/utils';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { JitsiRecordingConstants, browser } from '../base/lib-jitsi-meet';
import {
getLocalParticipant,
getRemoteParticipants,
isLocalParticipantModerator
} from '../base/participants/functions';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { isEnabled as isDropboxEnabled } from '../dropbox/functions';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
feat(recording): frontend logic can support live streaming and recording (#2952) * feat(recording): frontend logic can support live streaming and recording Instead of either live streaming or recording, now both can live together. The changes to facilitate such include the following: - Killing the state storing in Recording.js. Instead state is stored in the lib and updated in redux for labels to display the necessary state updates. - Creating a new container, Labels, for recording labels. Previously labels were manually created and positioned. The container can create a reasonable number of labels and only the container itself needs to be positioned with CSS. The VideoQualityLabel has been shoved into the container as well because it moves along with the recording labels. - The action for updating recording state has been modified to enable updating an array of recording sessions to support having multiple sessions. - Confirmation dialogs for stopping and starting a file recording session have been created, as they previously were jquery modals opened by Recording.js. - Toolbox.web displays live streaming and recording buttons based on configuration instead of recording availability. - VideoQualityLabel and RecordingLabel have been simplified to remove any positioning logic, as the Labels container handles such. - Previous recording state update logic has been moved into the RecordingLabel component. Each RecordingLabel is in charge of displaying state for a recording session. The display UX has been left alone. - Sipgw availability is no longer broadcast so remove logic depending on its state. Some moving around of code was necessary to get around linting errors about the existing code being too deeply nested (even though I didn't touch it). * work around lib-jitsi-meet circular dependency issues * refactor labels to use html base * pass in translation keys to video quality label * add video quality classnames for torture tests * break up, rearrange recorder session update listener * add comment about disabling startup resize animation * rename session to sessionData * chore(deps): update to latest lib for recording changes
2018-05-16 14:00:16 +00:00
import LocalRecordingManager from './components/Recording/LocalRecordingManager';
import { RECORDING_STATUS_PRIORITIES, RECORDING_TYPES } from './constants';
import logger from './logger';
2018-09-11 10:16:01 +00:00
feat(recording): frontend logic can support live streaming and recording (#2952) * feat(recording): frontend logic can support live streaming and recording Instead of either live streaming or recording, now both can live together. The changes to facilitate such include the following: - Killing the state storing in Recording.js. Instead state is stored in the lib and updated in redux for labels to display the necessary state updates. - Creating a new container, Labels, for recording labels. Previously labels were manually created and positioned. The container can create a reasonable number of labels and only the container itself needs to be positioned with CSS. The VideoQualityLabel has been shoved into the container as well because it moves along with the recording labels. - The action for updating recording state has been modified to enable updating an array of recording sessions to support having multiple sessions. - Confirmation dialogs for stopping and starting a file recording session have been created, as they previously were jquery modals opened by Recording.js. - Toolbox.web displays live streaming and recording buttons based on configuration instead of recording availability. - VideoQualityLabel and RecordingLabel have been simplified to remove any positioning logic, as the Labels container handles such. - Previous recording state update logic has been moved into the RecordingLabel component. Each RecordingLabel is in charge of displaying state for a recording session. The display UX has been left alone. - Sipgw availability is no longer broadcast so remove logic depending on its state. Some moving around of code was necessary to get around linting errors about the existing code being too deeply nested (even though I didn't touch it). * work around lib-jitsi-meet circular dependency issues * refactor labels to use html base * pass in translation keys to video quality label * add video quality classnames for torture tests * break up, rearrange recorder session update listener * add comment about disabling startup resize animation * rename session to sessionData * chore(deps): update to latest lib for recording changes
2018-05-16 14:00:16 +00:00
/**
* 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: IReduxState, mode: string) {
feat(recording): frontend logic can support live streaming and recording (#2952) * feat(recording): frontend logic can support live streaming and recording Instead of either live streaming or recording, now both can live together. The changes to facilitate such include the following: - Killing the state storing in Recording.js. Instead state is stored in the lib and updated in redux for labels to display the necessary state updates. - Creating a new container, Labels, for recording labels. Previously labels were manually created and positioned. The container can create a reasonable number of labels and only the container itself needs to be positioned with CSS. The VideoQualityLabel has been shoved into the container as well because it moves along with the recording labels. - The action for updating recording state has been modified to enable updating an array of recording sessions to support having multiple sessions. - Confirmation dialogs for stopping and starting a file recording session have been created, as they previously were jquery modals opened by Recording.js. - Toolbox.web displays live streaming and recording buttons based on configuration instead of recording availability. - VideoQualityLabel and RecordingLabel have been simplified to remove any positioning logic, as the Labels container handles such. - Previous recording state update logic has been moved into the RecordingLabel component. Each RecordingLabel is in charge of displaying state for a recording session. The display UX has been left alone. - Sipgw availability is no longer broadcast so remove logic depending on its state. Some moving around of code was necessary to get around linting errors about the existing code being too deeply nested (even though I didn't touch it). * work around lib-jitsi-meet circular dependency issues * refactor labels to use html base * pass in translation keys to video quality label * add video quality classnames for torture tests * break up, rearrange recorder session update listener * add comment about disabling startup resize animation * rename session to sessionData * chore(deps): update to latest lib for recording changes
2018-05-16 14:00:16 +00:00
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 | null) {
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: IReduxState, id: string) {
return state['features/recording'].sessionDatas.find(
sessionData => sessionData.id === id);
}
2018-09-11 10:16:01 +00:00
/**
* 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}&region=${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: IReduxState) {
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: IReduxState) {
return state['features/recording'].disableHighlightMeetingMoment;
}
2018-09-11 10:16:01 +00:00
/**
* Returns the recording session status that is to be shown in a label. E.g. If
2018-09-11 10:16:01 +00:00
* 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: IReduxState, mode: string): string | undefined {
2018-09-11 10:16:01 +00:00
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;
}
2018-09-11 10:16:01 +00:00
return status;
}
/**
* Check if local recording is supported.
*
* @returns {boolean} - Whether 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: IReduxState) {
let visible;
// a button can be disabled/enabled if enableFeaturesBasedOnToken
// is on or if the livestreaming is running.
let disabled = false;
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 {
recordingService,
localRecording
} = state['features/base/config'];
const localRecordingEnabled = !localRecording?.disable && supportsLocalRecording();
const dropboxEnabled = isDropboxEnabled(state);
visible = isModerator && (recordingService?.enabled || localRecordingEnabled || dropboxEnabled);
visible = isJwtFeatureEnabled(state, 'recording', visible);
// disable the button if the livestreaming is running.
if (visible && 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 | { getId: Function; }) {
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: IReduxState) {
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: IReduxState) {
const participants = getRemoteParticipants(state);
// eslint-disable-next-line prefer-const
for (let value of participants.values()) {
if (value.localRecording) {
return true;
}
}
return false;
}