From 7f87d4eadad9f57665398a01d357a26a6c262cda Mon Sep 17 00:00:00 2001 From: Avram Tudor Date: Tue, 19 Dec 2023 11:25:06 +0200 Subject: [PATCH] feat(transcript) add recording settings for recording transcriptions (#14158) --- config.js | 6 +- css/_recording.scss | 4 + lang/main.json | 3 + react/features/base/config/configType.ts | 2 +- react/features/base/config/reducer.ts | 2 +- .../Recording/AbstractStartRecordingDialog.ts | 136 +++++++++++------- .../AbstractStartRecordingDialogContent.tsx | 88 +++++++++++- .../Recording/native/StartRecordingDialog.tsx | 17 ++- .../native/StartRecordingDialogContent.tsx | 94 ++++++++++-- .../components/Recording/styles.web.ts | 2 + .../Recording/web/StartRecordingDialog.tsx | 17 ++- .../web/StartRecordingDialogContent.tsx | 81 +++++++++-- .../AbstractClosedCaptionButton.tsx | 7 +- react/features/subtitles/functions.ts | 15 ++ 14 files changed, 392 insertions(+), 82 deletions(-) create mode 100644 react/features/subtitles/functions.ts diff --git a/config.js b/config.js index bc408ce37d37..5c4acb8d21b8 100644 --- a/config.js +++ b/config.js @@ -378,7 +378,7 @@ var config = { // DEPRECATED. Use transcription.preferredLanguage instead. // preferredTranscribeLanguage: 'en-US', - // DEPRECATED. Use transcription.autoCaptionOnRecord instead. + // DEPRECATED. Use transcription.autoTranscribeOnRecord instead. // autoCaptionOnRecord: false, // Transcription options. @@ -410,8 +410,8 @@ var config = { // // Disable start transcription for all participants. // disableStartForAll: false, - // // Enables automatic turning on captions when recording is started - // autoCaptionOnRecord: false, + // // Enables automatic turning on transcribing when recording is started + // autoTranscribeOnRecord: false, // }, // Misc diff --git a/css/_recording.scss b/css/_recording.scss index c95ae8a74c4e..10d5f2d6cdaf 100644 --- a/css/_recording.scss +++ b/css/_recording.scss @@ -15,6 +15,10 @@ font-size: 14px; margin-left: 16px; max-width: 70%; + + &-no-space { + margin-left: 0; + } } &.space-top { diff --git a/lang/main.json b/lang/main.json index 617761b5a8b7..d47d244a0736 100644 --- a/lang/main.json +++ b/lang/main.json @@ -1016,12 +1016,15 @@ "onlyRecordSelf": "Record only my audio and video streams", "pending": "Preparing to record the meeting...", "rec": "REC", + "recordAudioAndVideo": "Record audio and video", + "recordTranscription": "Record transcription", "saveLocalRecording": "Save recording file locally (Beta)", "serviceDescription": "Your recording will be saved by the recording service", "serviceDescriptionCloud": "Cloud recording", "serviceDescriptionCloudInfo": "Recorded meetings are automatically cleared 24h after their recording time.", "serviceName": "Recording service", "sessionAlreadyActive": "This session is already being recorded or live streamed.", + "showAdvancedOptions": "Advanced options", "signIn": "Sign in", "signOut": "Sign out", "surfaceError": "Please select the current tab.", diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index 7b7986ae8ba0..839aa5bddf40 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -583,7 +583,7 @@ export interface IConfig { transcribeWithAppLanguage?: boolean; transcribingEnabled?: boolean; transcription?: { - autoCaptionOnRecord?: boolean; + autoTranscribeOnRecord?: boolean; disableStartForAll?: boolean; enabled?: boolean; preferredLanguage?: string; diff --git a/react/features/base/config/reducer.ts b/react/features/base/config/reducer.ts index 0d97fe208474..dbc6bd7532cd 100644 --- a/react/features/base/config/reducer.ts +++ b/react/features/base/config/reducer.ts @@ -465,7 +465,7 @@ function _translateLegacyConfig(oldValue: IConfig) { if (oldValue.autoCaptionOnRecord !== undefined) { newValue.transcription = { ...newValue.transcription, - autoCaptionOnRecord: oldValue.autoCaptionOnRecord + autoTranscribeOnRecord: oldValue.autoCaptionOnRecord }; } diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts b/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts index a8668fdcd30c..4ed01c4c24f8 100644 --- a/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts @@ -10,7 +10,7 @@ import { updateDropboxToken } from '../../../dropbox/actions'; import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any'; import { showErrorNotification } from '../../../notifications/actions'; import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants'; -import { toggleRequestingSubtitles } from '../../../subtitles/actions'; +import { setRequestingSubtitles } from '../../../subtitles/actions.any'; import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions'; import { RECORDING_TYPES } from '../../constants'; import { supportsLocalRecording } from '../../functions'; @@ -23,9 +23,9 @@ export interface IProps extends WithTranslation { _appKey: string; /** - * Requests subtitles when recording is turned on. + * Requests transcribing when recording is turned on. */ - _autoCaptionOnRecord: boolean; + _autoTranscribeOnRecord: boolean; /** * The {@code JitsiConference} for the current conference. @@ -114,6 +114,16 @@ interface IState { */ sharingEnabled: boolean; + /** + * True if the user requested the service to record audio and video. + */ + shouldRecordAudioAndVideo: boolean; + + /** + * True if the user requested the service to record transcription. + */ + shouldRecordTranscription: boolean; + /** * Number of MiB of available space in user's Dropbox account. */ @@ -144,6 +154,8 @@ class AbstractStartRecordingDialog extends Component { this._onSharingSettingChanged = this._onSharingSettingChanged.bind(this); this._toggleScreenshotCapture = this._toggleScreenshotCapture.bind(this); this._onLocalRecordingSelfChange = this._onLocalRecordingSelfChange.bind(this); + this._onTranscriptionChange = this._onTranscriptionChange.bind(this); + this._onRecordAudioAndVideoChange = this._onRecordAudioAndVideoChange.bind(this); let selectedRecordingService = ''; @@ -165,6 +177,8 @@ class AbstractStartRecordingDialog extends Component { isValidating: false, userName: undefined, sharingEnabled: true, + shouldRecordAudioAndVideo: true, + shouldRecordTranscription: true, spaceLeft: undefined, selectedRecordingService, localRecordingOnlySelf: false @@ -241,6 +255,30 @@ class AbstractStartRecordingDialog extends Component { }); } + /** + * Handles transcription switch change. + * + * @param {boolean} value - The new value. + * @returns {void} + */ + _onTranscriptionChange(value: boolean) { + this.setState({ + shouldRecordTranscription: value + }); + } + + /** + * Handles audio and video switch change. + * + * @param {boolean} value - The new value. + * @returns {void} + */ + _onRecordAudioAndVideoChange(value: boolean) { + this.setState({ + shouldRecordAudioAndVideo: value + }); + } + /** * Validates the dropbox access token and fetches account information. * @@ -297,7 +335,7 @@ class AbstractStartRecordingDialog extends Component { _onSubmit() { const { _appKey, - _autoCaptionOnRecord, + _autoTranscribeOnRecord, _conference, _isDropboxEnabled, _rToken, @@ -309,57 +347,59 @@ class AbstractStartRecordingDialog extends Component { type?: string; } = {}; - switch (this.state.selectedRecordingService) { - case RECORDING_TYPES.DROPBOX: { - if (_isDropboxEnabled && _token) { + if (this.state.shouldRecordAudioAndVideo) { + switch (this.state.selectedRecordingService) { + case RECORDING_TYPES.DROPBOX: { + if (_isDropboxEnabled && _token) { + appData = JSON.stringify({ + 'file_recording_metadata': { + 'upload_credentials': { + 'service_name': RECORDING_TYPES.DROPBOX, + 'token': _token, + 'r_token': _rToken, + 'app_key': _appKey + } + } + }); + attributes.type = RECORDING_TYPES.DROPBOX; + } else { + dispatch(showErrorNotification({ + titleKey: 'dialog.noDropboxToken' + }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + + return; + } + break; + } + case RECORDING_TYPES.JITSI_REC_SERVICE: { appData = JSON.stringify({ 'file_recording_metadata': { - 'upload_credentials': { - 'service_name': RECORDING_TYPES.DROPBOX, - 'token': _token, - 'r_token': _rToken, - 'app_key': _appKey - } + 'share': this.state.sharingEnabled } }); - attributes.type = RECORDING_TYPES.DROPBOX; - } else { - dispatch(showErrorNotification({ - titleKey: 'dialog.noDropboxToken' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); - - return; + attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE; + break; } - break; - } - case RECORDING_TYPES.JITSI_REC_SERVICE: { - appData = JSON.stringify({ - 'file_recording_metadata': { - 'share': this.state.sharingEnabled - } - }); - attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE; - break; - } - case RECORDING_TYPES.LOCAL: { - dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf)); + case RECORDING_TYPES.LOCAL: { + dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf)); - return true; - } - } + return true; + } + } - sendAnalytics( - createRecordingDialogEvent('start', 'confirm.button', attributes) - ); + sendAnalytics( + createRecordingDialogEvent('start', 'confirm.button', attributes) + ); - this._toggleScreenshotCapture(); - _conference?.startRecording({ - mode: JitsiRecordingConstants.mode.FILE, - appData - }); + this._toggleScreenshotCapture(); + _conference?.startRecording({ + mode: JitsiRecordingConstants.mode.FILE, + appData + }); + } - if (_autoCaptionOnRecord) { - dispatch(toggleRequestingSubtitles()); + if (_autoTranscribeOnRecord || this.state.shouldRecordTranscription) { + dispatch(setRequestingSubtitles(true, false)); } return true; @@ -392,7 +432,7 @@ class AbstractStartRecordingDialog extends Component { * @private * @returns {{ * _appKey: string, - * _autoCaptionOnRecord: boolean, + * _autoTranscribeOnRecord: boolean, * _conference: JitsiConference, * _fileRecordingsServiceEnabled: boolean, * _fileRecordingsServiceSharingEnabled: boolean, @@ -412,7 +452,7 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) { return { _appKey: dropbox.appKey ?? '', - _autoCaptionOnRecord: transcription?.autoCaptionOnRecord ?? false, + _autoTranscribeOnRecord: transcription?.autoTranscribeOnRecord ?? false, _conference: state['features/base/conference'].conference, _fileRecordingsServiceEnabled: recordingService?.enabled ?? false, _fileRecordingsServiceSharingEnabled: recordingService?.sharingEnabled ?? false, diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx index c072f1fa9069..32479a6dcf64 100644 --- a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx @@ -9,6 +9,7 @@ import { _abstractMapStateToProps } from '../../../base/dialog/functions'; import { isLocalParticipantModerator } from '../../../base/participants/functions'; import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions'; import { isVpaasMeeting } from '../../../jaas/functions'; +import { canStartTranscribing } from '../../../subtitles/functions'; import { RECORDING_TYPES } from '../../constants'; import { supportsLocalRecording } from '../../functions'; @@ -18,6 +19,11 @@ import { supportsLocalRecording } from '../../functions'; */ export interface IProps extends WithTranslation { + /** + * Whether the local participant can start transcribing. + */ + _canStartTranscribing: boolean; + /** * Style of the dialogs feature. */ @@ -111,11 +117,21 @@ export interface IProps extends WithTranslation { */ onLocalRecordingSelfChange?: () => void; + /** + * Callback to change the audio and video recording setting. + */ + onRecordAudioAndVideoChange: Function; + /** * Callback to be invoked on sharing setting change. */ onSharingSettingChanged: () => void; + /** + * Callback to change the transcription recording setting. + */ + onTranscriptionChange: Function; + /** * The currently selected recording service of type: RECORDING_TYPES. */ @@ -126,6 +142,16 @@ export interface IProps extends WithTranslation { */ sharingSetting: boolean; + /** + * Whether to show the audio and video related content. + */ + shouldRecordAudioAndVideo: boolean; + + /** + * Whether to show the transcription related content. + */ + shouldRecordTranscription: boolean; + /** * Number of MiB of available space in user's Dropbox account. */ @@ -137,18 +163,26 @@ export interface IProps extends WithTranslation { userName?: string; } +export interface IState { + + /** + * Whether to show the advanced options or not. + */ + showAdvancedOptions: boolean; +} + /** - * React Component for getting confirmation to start a file recording session. + * React Component for getting confirmation to start a recording session. * * @augments Component */ -class AbstractStartRecordingDialogContent

extends Component

{ +class AbstractStartRecordingDialogContent extends Component { /** * Initializes a new {@code AbstractStartRecordingDialogContent} instance. * * @inheritdoc */ - constructor(props: P) { + constructor(props: IProps) { super(props); // Bind event handler; it bounds once for every instance. @@ -157,6 +191,13 @@ class AbstractStartRecordingDialogContent

extends Component

this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this); this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this); this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this); + this._onTranscriptionSwitchChange = this._onTranscriptionSwitchChange.bind(this); + this._onRecordAudioAndVideoSwitchChange = this._onRecordAudioAndVideoSwitchChange.bind(this); + this._onToggleShowOptions = this._onToggleShowOptions.bind(this); + + this.state = { + showAdvancedOptions: false + }; } /** @@ -177,7 +218,7 @@ class AbstractStartRecordingDialogContent

extends Component

* * @inheritdoc */ - componentDidUpdate(prevProps: P) { + componentDidUpdate(prevProps: IProps) { // Auto sign-out when the use chooses another recording service. if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX && this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) { @@ -185,6 +226,15 @@ class AbstractStartRecordingDialogContent

extends Component

} } + /** + * Returns whether the advanced options should be rendered. + * + * @returns {boolean} + */ + _onToggleShowOptions() { + this.setState({ showAdvancedOptions: !this.state.showAdvancedOptions }); + } + /** * Whether the file sharing content should be rendered or not. * @@ -208,6 +258,15 @@ class AbstractStartRecordingDialogContent

extends Component

return true; } + /** + * Whether the save transcription content should be rendered or not. + * + * @returns {boolean} + */ + _canStartTranscribing() { + return this.props._canStartTranscribing; + } + /** * Whether the no integrations content should be rendered or not. * @@ -236,6 +295,26 @@ class AbstractStartRecordingDialogContent

extends Component

return true; } + /** + * Handler for transcription switch change. + * + * @param {boolean} value - The new value. + * @returns {void} + */ + _onTranscriptionSwitchChange(value: boolean | undefined) { + this.props.onTranscriptionChange(value); + } + + /** + * Handler for audio and video switch change. + * + * @param {boolean} value - The new value. + * @returns {void} + */ + _onRecordAudioAndVideoSwitchChange(value: boolean | undefined) { + this.props.onRecordAudioAndVideoChange(value); + } + /** * Handler for onValueChange events from the Switch component. * @@ -339,6 +418,7 @@ export function mapStateToProps(state: IReduxState) { return { ..._abstractMapStateToProps(state), isVpaas: isVpaasMeeting(state), + _canStartTranscribing: canStartTranscribing(state), _hideStorageWarning: Boolean(recordingService?.hideStorageWarning), _isModerator: isLocalParticipantModerator(state), _localRecordingAvailable, diff --git a/react/features/recording/components/Recording/native/StartRecordingDialog.tsx b/react/features/recording/components/Recording/native/StartRecordingDialog.tsx index cbfb1d5979f9..ecdeede03ddb 100644 --- a/react/features/recording/components/Recording/native/StartRecordingDialog.tsx +++ b/react/features/recording/components/Recording/native/StartRecordingDialog.tsx @@ -98,7 +98,16 @@ class StartRecordingDialog extends AbstractStartRecordingDialog { * @returns {boolean} */ isStartRecordingDisabled() { - const { isTokenValid, selectedRecordingService } = this.state; + const { + isTokenValid, + selectedRecordingService, + shouldRecordAudioAndVideo, + shouldRecordTranscription + } = this.state; + + if (!shouldRecordAudioAndVideo && !shouldRecordTranscription) { + return true; + } // Start button is disabled if recording service is only shown; // When validating dropbox token, if that is not enabled, we either always @@ -125,6 +134,8 @@ class StartRecordingDialog extends AbstractStartRecordingDialog { isValidating, selectedRecordingService, sharingEnabled, + shouldRecordAudioAndVideo, + shouldRecordTranscription, spaceLeft, userName } = this.state; @@ -142,9 +153,13 @@ class StartRecordingDialog extends AbstractStartRecordingDialog { isTokenValid = { isTokenValid } isValidating = { isValidating } onChange = { this._onSelectedRecordingServiceChanged } + onRecordAudioAndVideoChange = { this._onRecordAudioAndVideoChange } onSharingSettingChanged = { this._onSharingSettingChanged } + onTranscriptionChange = { this._onTranscriptionChange } selectedRecordingService = { selectedRecordingService } sharingSetting = { sharingEnabled } + shouldRecordAudioAndVideo = { shouldRecordAudioAndVideo } + shouldRecordTranscription = { shouldRecordTranscription } spaceLeft = { spaceLeft } userName = { userName } /> diff --git a/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx b/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx index 770949440c46..2c5a45999348 100644 --- a/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx @@ -4,16 +4,15 @@ import { Text } from 'react-native-paper'; import { connect } from 'react-redux'; import { translate } from '../../../../base/i18n/functions'; +import Icon from '../../../../base/icons/components/Icon'; +import { IconArrowDown, IconArrowRight } from '../../../../base/icons/svg'; import LoadingIndicator from '../../../../base/react/components/native/LoadingIndicator'; import Button from '../../../../base/ui/components/native/Button'; import Switch from '../../../../base/ui/components/native/Switch'; import { BUTTON_TYPES } from '../../../../base/ui/constants.native'; import { RECORDING_TYPES } from '../../../constants'; import { getRecordingDurationEstimation } from '../../../functions'; -import AbstractStartRecordingDialogContent, { - IProps, - mapStateToProps -} from '../AbstractStartRecordingDialogContent'; +import AbstractStartRecordingDialogContent, { mapStateToProps } from '../AbstractStartRecordingDialogContent'; import { DROPBOX_LOGO, ICON_CLOUD, @@ -25,7 +24,7 @@ import { /** * The start recording dialog content for the mobile application. */ -class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { +class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { /** * Renders the component. * @@ -41,10 +40,86 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent ); } + /** + * Renders the save transcription switch. + * + * @returns {React$Component} + */ + _renderAdvancedOptions() { + if (!this._canStartTranscribing()) { + return null; + } + + const { showAdvancedOptions } = this.state; + const { + _dialogStyles, + _styles: styles, + shouldRecordAudioAndVideo, + shouldRecordTranscription, + t + } = this.props; + + return ( + <> + + + { t('recording.showAdvancedOptions') } + + + + {showAdvancedOptions && ( + <> + + + { t('recording.recordTranscription') } + + + + + + { t('recording.recordAudioAndVideo') } + + + + + )} + + ); + } + /** * Renders the content in case no integrations were enabled. * @@ -57,6 +132,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent ) : null; @@ -109,6 +185,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent @@ -237,6 +314,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent ); diff --git a/react/features/recording/components/Recording/styles.web.ts b/react/features/recording/components/Recording/styles.web.ts index 5f71f3b12d76..50fa309c29ef 100644 --- a/react/features/recording/components/Recording/styles.web.ts +++ b/react/features/recording/components/Recording/styles.web.ts @@ -12,3 +12,5 @@ export const ICON_CLOUD = 'images/icon-cloud.png'; export const ICON_INFO = 'images/icon-info.png'; export const ICON_USERS = 'images/icon-users.png'; + +export const ICON_OPTIONS = 'images/icon-info.png'; diff --git a/react/features/recording/components/Recording/web/StartRecordingDialog.tsx b/react/features/recording/components/Recording/web/StartRecordingDialog.tsx index 0a08e5a07d4f..b4bb8a81ac5b 100644 --- a/react/features/recording/components/Recording/web/StartRecordingDialog.tsx +++ b/react/features/recording/components/Recording/web/StartRecordingDialog.tsx @@ -28,7 +28,16 @@ class StartRecordingDialog extends AbstractStartRecordingDialog { * @returns {boolean} */ isStartRecordingDisabled() { - const { isTokenValid, selectedRecordingService } = this.state; + const { + isTokenValid, + selectedRecordingService, + shouldRecordAudioAndVideo, + shouldRecordTranscription + } = this.state; + + if (!shouldRecordAudioAndVideo && !shouldRecordTranscription) { + return true; + } // Start button is disabled if recording service is only shown; // When validating dropbox token, if that is not enabled, we either always @@ -57,6 +66,8 @@ class StartRecordingDialog extends AbstractStartRecordingDialog { localRecordingOnlySelf, selectedRecordingService, sharingEnabled, + shouldRecordAudioAndVideo, + shouldRecordTranscription, spaceLeft, userName } = this.state; @@ -82,9 +93,13 @@ class StartRecordingDialog extends AbstractStartRecordingDialog { localRecordingOnlySelf = { localRecordingOnlySelf } onChange = { this._onSelectedRecordingServiceChanged } onLocalRecordingSelfChange = { this._onLocalRecordingSelfChange } + onRecordAudioAndVideoChange = { this._onRecordAudioAndVideoChange } onSharingSettingChanged = { this._onSharingSettingChanged } + onTranscriptionChange = { this._onTranscriptionChange } selectedRecordingService = { selectedRecordingService } sharingSetting = { sharingEnabled } + shouldRecordAudioAndVideo = { shouldRecordAudioAndVideo } + shouldRecordTranscription = { shouldRecordTranscription } spaceLeft = { spaceLeft } userName = { userName } /> diff --git a/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx b/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx index 00c48d8d76c1..a118fe70f548 100644 --- a/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx +++ b/react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { translate } from '../../../../base/i18n/functions'; +import Icon from '../../../../base/icons/components/Icon'; +import { IconArrowDown, IconArrowRight } from '../../../../base/icons/svg'; import Container from '../../../../base/react/components/web/Container'; import Image from '../../../../base/react/components/web/Image'; import LoadingIndicator from '../../../../base/react/components/web/LoadingIndicator'; @@ -11,10 +13,7 @@ import Switch from '../../../../base/ui/components/web/Switch'; import { BUTTON_TYPES } from '../../../../base/ui/constants.web'; import { RECORDING_TYPES } from '../../../constants'; import { getRecordingDurationEstimation } from '../../../functions'; -import AbstractStartRecordingDialogContent, { - IProps, - mapStateToProps -} from '../AbstractStartRecordingDialogContent'; +import AbstractStartRecordingDialogContent, { mapStateToProps } from '../AbstractStartRecordingDialogContent'; import { DROPBOX_LOGO, ICON_CLOUD, @@ -30,7 +29,7 @@ const EMPTY_FUNCTION = () => { /** * The start recording dialog content for the mobile application. */ -class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { +class StartRecordingDialogContent extends AbstractStartRecordingDialogContent { /** * Renders the component. * @@ -49,10 +48,72 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent )} { this._renderLocalRecordingContent() } + { this._renderAdvancedOptions() } ); } + /** + * Renders the switch for saving the transcription. + * + * @returns {React$Component} + */ + _renderAdvancedOptions() { + if (!this._canStartTranscribing()) { + return null; + } + + const { showAdvancedOptions } = this.state; + const { shouldRecordAudioAndVideo, shouldRecordTranscription, t } = this.props; + + return ( + <> +

+
+ + +
+ {showAdvancedOptions && ( + <> +
+ + +
+
+ + +
+ + )} + + ); + } + /** * Renders the content in case no integrations were enabled. * @@ -78,7 +139,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent ) : null; @@ -148,7 +209,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent @@ -294,7 +355,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent ); @@ -372,7 +433,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent @@ -396,7 +457,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent diff --git a/react/features/subtitles/components/AbstractClosedCaptionButton.tsx b/react/features/subtitles/components/AbstractClosedCaptionButton.tsx index 3414714590a5..c91a6dd0314e 100644 --- a/react/features/subtitles/components/AbstractClosedCaptionButton.tsx +++ b/react/features/subtitles/components/AbstractClosedCaptionButton.tsx @@ -2,9 +2,9 @@ import { createToolbarEvent } from '../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../analytics/functions'; import { IReduxState } from '../../app/types'; import { MEET_FEATURES } from '../../base/jwt/constants'; -import { isLocalParticipantModerator } from '../../base/participants/functions'; import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton'; import { maybeShowPremiumFeatureDialog } from '../../jaas/actions'; +import { canStartTranscribing } from '../functions'; export interface IAbstractProps extends AbstractButtonProps { @@ -103,13 +103,10 @@ export class AbstractClosedCaptionButton */ export function _abstractMapStateToProps(state: IReduxState, ownProps: IAbstractProps) { const { _requestingSubtitles, _language } = state['features/subtitles']; - const { transcription } = state['features/base/config']; - const { isTranscribing } = state['features/transcribing']; // if the participant is moderator, it can enable transcriptions and if // transcriptions are already started for the meeting, guests can just show them - const { visible = Boolean(transcription?.enabled - && (isLocalParticipantModerator(state) || isTranscribing)) } = ownProps; + const { visible = canStartTranscribing(state) } = ownProps; return { _requestingSubtitles, diff --git a/react/features/subtitles/functions.ts b/react/features/subtitles/functions.ts new file mode 100644 index 000000000000..44fc962fda57 --- /dev/null +++ b/react/features/subtitles/functions.ts @@ -0,0 +1,15 @@ +import { IReduxState } from '../app/types'; +import { isLocalParticipantModerator } from '../base/participants/functions'; + +/** + * Checks whether the participant can start the transcription. + * + * @param {IReduxState} state - The redux state. + * @returns {boolean} - True if the participant can start the transcription. + */ +export function canStartTranscribing(state: IReduxState) { + const { transcription } = state['features/base/config']; + const { isTranscribing } = state['features/transcribing']; + + return Boolean(transcription?.enabled && (isLocalParticipantModerator(state) || isTranscribing)); +}