瀏覽代碼

feat(transcript) add recording settings for recording transcriptions (#14158)

factor2
Avram Tudor 1 年之前
父節點
當前提交
7f87d4eada
No account linked to committer's email address

+ 3
- 3
config.js 查看文件

@@ -378,7 +378,7 @@ var config = {
378 378
     // DEPRECATED. Use transcription.preferredLanguage instead.
379 379
     // preferredTranscribeLanguage: 'en-US',
380 380
 
381
-    // DEPRECATED. Use transcription.autoCaptionOnRecord instead.
381
+    // DEPRECATED. Use transcription.autoTranscribeOnRecord instead.
382 382
     // autoCaptionOnRecord: false,
383 383
 
384 384
     // Transcription options.
@@ -410,8 +410,8 @@ var config = {
410 410
     //     // Disable start transcription for all participants.
411 411
     //     disableStartForAll: false,
412 412
 
413
-    //     // Enables automatic turning on captions when recording is started
414
-    //     autoCaptionOnRecord: false,
413
+    //     // Enables automatic turning on transcribing when recording is started
414
+    //     autoTranscribeOnRecord: false,
415 415
     // },
416 416
 
417 417
     // Misc

+ 4
- 0
css/_recording.scss 查看文件

@@ -15,6 +15,10 @@
15 15
             font-size: 14px;
16 16
             margin-left: 16px;
17 17
             max-width: 70%;
18
+
19
+            &-no-space {
20
+                margin-left: 0;
21
+            }
18 22
         }
19 23
 
20 24
         &.space-top {

+ 3
- 0
lang/main.json 查看文件

@@ -1016,12 +1016,15 @@
1016 1016
         "onlyRecordSelf": "Record only my audio and video streams",
1017 1017
         "pending": "Preparing to record the meeting...",
1018 1018
         "rec": "REC",
1019
+        "recordAudioAndVideo": "Record audio and video",
1020
+        "recordTranscription": "Record transcription",
1019 1021
         "saveLocalRecording": "Save recording file locally (Beta)",
1020 1022
         "serviceDescription": "Your recording will be saved by the recording service",
1021 1023
         "serviceDescriptionCloud": "Cloud recording",
1022 1024
         "serviceDescriptionCloudInfo": "Recorded meetings are automatically cleared 24h after their recording time.",
1023 1025
         "serviceName": "Recording service",
1024 1026
         "sessionAlreadyActive": "This session is already being recorded or live streamed.",
1027
+        "showAdvancedOptions": "Advanced options",
1025 1028
         "signIn": "Sign in",
1026 1029
         "signOut": "Sign out",
1027 1030
         "surfaceError": "Please select the current tab.",

+ 1
- 1
react/features/base/config/configType.ts 查看文件

@@ -583,7 +583,7 @@ export interface IConfig {
583 583
     transcribeWithAppLanguage?: boolean;
584 584
     transcribingEnabled?: boolean;
585 585
     transcription?: {
586
-        autoCaptionOnRecord?: boolean;
586
+        autoTranscribeOnRecord?: boolean;
587 587
         disableStartForAll?: boolean;
588 588
         enabled?: boolean;
589 589
         preferredLanguage?: string;

+ 1
- 1
react/features/base/config/reducer.ts 查看文件

@@ -465,7 +465,7 @@ function _translateLegacyConfig(oldValue: IConfig) {
465 465
     if (oldValue.autoCaptionOnRecord !== undefined) {
466 466
         newValue.transcription = {
467 467
             ...newValue.transcription,
468
-            autoCaptionOnRecord: oldValue.autoCaptionOnRecord
468
+            autoTranscribeOnRecord: oldValue.autoCaptionOnRecord
469 469
         };
470 470
     }
471 471
 

+ 88
- 48
react/features/recording/components/Recording/AbstractStartRecordingDialog.ts 查看文件

@@ -10,7 +10,7 @@ import { updateDropboxToken } from '../../../dropbox/actions';
10 10
 import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any';
11 11
 import { showErrorNotification } from '../../../notifications/actions';
12 12
 import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
13
-import { toggleRequestingSubtitles } from '../../../subtitles/actions';
13
+import { setRequestingSubtitles } from '../../../subtitles/actions.any';
14 14
 import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions';
15 15
 import { RECORDING_TYPES } from '../../constants';
16 16
 import { supportsLocalRecording } from '../../functions';
@@ -23,9 +23,9 @@ export interface IProps extends WithTranslation {
23 23
     _appKey: string;
24 24
 
25 25
     /**
26
-     * Requests subtitles when recording is turned on.
26
+     * Requests transcribing when recording is turned on.
27 27
      */
28
-    _autoCaptionOnRecord: boolean;
28
+    _autoTranscribeOnRecord: boolean;
29 29
 
30 30
     /**
31 31
      * The {@code JitsiConference} for the current conference.
@@ -114,6 +114,16 @@ interface IState {
114 114
      */
115 115
     sharingEnabled: boolean;
116 116
 
117
+    /**
118
+     * True if the user requested the service to record audio and video.
119
+     */
120
+    shouldRecordAudioAndVideo: boolean;
121
+
122
+    /**
123
+     * True if the user requested the service to record transcription.
124
+     */
125
+    shouldRecordTranscription: boolean;
126
+
117 127
     /**
118 128
      * Number of MiB of available space in user's Dropbox account.
119 129
      */
@@ -144,6 +154,8 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
144 154
         this._onSharingSettingChanged = this._onSharingSettingChanged.bind(this);
145 155
         this._toggleScreenshotCapture = this._toggleScreenshotCapture.bind(this);
146 156
         this._onLocalRecordingSelfChange = this._onLocalRecordingSelfChange.bind(this);
157
+        this._onTranscriptionChange = this._onTranscriptionChange.bind(this);
158
+        this._onRecordAudioAndVideoChange = this._onRecordAudioAndVideoChange.bind(this);
147 159
 
148 160
         let selectedRecordingService = '';
149 161
 
@@ -165,6 +177,8 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
165 177
             isValidating: false,
166 178
             userName: undefined,
167 179
             sharingEnabled: true,
180
+            shouldRecordAudioAndVideo: true,
181
+            shouldRecordTranscription: true,
168 182
             spaceLeft: undefined,
169 183
             selectedRecordingService,
170 184
             localRecordingOnlySelf: false
@@ -241,6 +255,30 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
241 255
         });
242 256
     }
243 257
 
258
+    /**
259
+     * Handles transcription switch change.
260
+     *
261
+     * @param {boolean} value - The new value.
262
+     * @returns {void}
263
+     */
264
+    _onTranscriptionChange(value: boolean) {
265
+        this.setState({
266
+            shouldRecordTranscription: value
267
+        });
268
+    }
269
+
270
+    /**
271
+     * Handles audio and video switch change.
272
+     *
273
+     * @param {boolean} value - The new value.
274
+     * @returns {void}
275
+     */
276
+    _onRecordAudioAndVideoChange(value: boolean) {
277
+        this.setState({
278
+            shouldRecordAudioAndVideo: value
279
+        });
280
+    }
281
+
244 282
     /**
245 283
      * Validates the dropbox access token and fetches account information.
246 284
      *
@@ -297,7 +335,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
297 335
     _onSubmit() {
298 336
         const {
299 337
             _appKey,
300
-            _autoCaptionOnRecord,
338
+            _autoTranscribeOnRecord,
301 339
             _conference,
302 340
             _isDropboxEnabled,
303 341
             _rToken,
@@ -309,57 +347,59 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
309 347
             type?: string;
310 348
         } = {};
311 349
 
312
-        switch (this.state.selectedRecordingService) {
313
-        case RECORDING_TYPES.DROPBOX: {
314
-            if (_isDropboxEnabled && _token) {
350
+        if (this.state.shouldRecordAudioAndVideo) {
351
+            switch (this.state.selectedRecordingService) {
352
+            case RECORDING_TYPES.DROPBOX: {
353
+                if (_isDropboxEnabled && _token) {
354
+                    appData = JSON.stringify({
355
+                        'file_recording_metadata': {
356
+                            'upload_credentials': {
357
+                                'service_name': RECORDING_TYPES.DROPBOX,
358
+                                'token': _token,
359
+                                'r_token': _rToken,
360
+                                'app_key': _appKey
361
+                            }
362
+                        }
363
+                    });
364
+                    attributes.type = RECORDING_TYPES.DROPBOX;
365
+                } else {
366
+                    dispatch(showErrorNotification({
367
+                        titleKey: 'dialog.noDropboxToken'
368
+                    }, NOTIFICATION_TIMEOUT_TYPE.LONG));
369
+
370
+                    return;
371
+                }
372
+                break;
373
+            }
374
+            case RECORDING_TYPES.JITSI_REC_SERVICE: {
315 375
                 appData = JSON.stringify({
316 376
                     'file_recording_metadata': {
317
-                        'upload_credentials': {
318
-                            'service_name': RECORDING_TYPES.DROPBOX,
319
-                            'token': _token,
320
-                            'r_token': _rToken,
321
-                            'app_key': _appKey
322
-                        }
377
+                        'share': this.state.sharingEnabled
323 378
                     }
324 379
                 });
325
-                attributes.type = RECORDING_TYPES.DROPBOX;
326
-            } else {
327
-                dispatch(showErrorNotification({
328
-                    titleKey: 'dialog.noDropboxToken'
329
-                }, NOTIFICATION_TIMEOUT_TYPE.LONG));
330
-
331
-                return;
380
+                attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE;
381
+                break;
332 382
             }
333
-            break;
334
-        }
335
-        case RECORDING_TYPES.JITSI_REC_SERVICE: {
336
-            appData = JSON.stringify({
337
-                'file_recording_metadata': {
338
-                    'share': this.state.sharingEnabled
339
-                }
340
-            });
341
-            attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE;
342
-            break;
343
-        }
344
-        case RECORDING_TYPES.LOCAL: {
345
-            dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
383
+            case RECORDING_TYPES.LOCAL: {
384
+                dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
346 385
 
347
-            return true;
348
-        }
349
-        }
386
+                return true;
387
+            }
388
+            }
350 389
 
351
-        sendAnalytics(
352
-            createRecordingDialogEvent('start', 'confirm.button', attributes)
353
-        );
390
+            sendAnalytics(
391
+                createRecordingDialogEvent('start', 'confirm.button', attributes)
392
+            );
354 393
 
355
-        this._toggleScreenshotCapture();
356
-        _conference?.startRecording({
357
-            mode: JitsiRecordingConstants.mode.FILE,
358
-            appData
359
-        });
394
+            this._toggleScreenshotCapture();
395
+            _conference?.startRecording({
396
+                mode: JitsiRecordingConstants.mode.FILE,
397
+                appData
398
+            });
399
+        }
360 400
 
361
-        if (_autoCaptionOnRecord) {
362
-            dispatch(toggleRequestingSubtitles());
401
+        if (_autoTranscribeOnRecord || this.state.shouldRecordTranscription) {
402
+            dispatch(setRequestingSubtitles(true, false));
363 403
         }
364 404
 
365 405
         return true;
@@ -392,7 +432,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
392 432
  * @private
393 433
  * @returns {{
394 434
  *     _appKey: string,
395
- *     _autoCaptionOnRecord: boolean,
435
+ *     _autoTranscribeOnRecord: boolean,
396 436
  *     _conference: JitsiConference,
397 437
  *     _fileRecordingsServiceEnabled: boolean,
398 438
  *     _fileRecordingsServiceSharingEnabled: boolean,
@@ -412,7 +452,7 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
412 452
 
413 453
     return {
414 454
         _appKey: dropbox.appKey ?? '',
415
-        _autoCaptionOnRecord: transcription?.autoCaptionOnRecord ?? false,
455
+        _autoTranscribeOnRecord: transcription?.autoTranscribeOnRecord ?? false,
416 456
         _conference: state['features/base/conference'].conference,
417 457
         _fileRecordingsServiceEnabled: recordingService?.enabled ?? false,
418 458
         _fileRecordingsServiceSharingEnabled: recordingService?.sharingEnabled ?? false,

+ 84
- 4
react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx 查看文件

@@ -9,6 +9,7 @@ import { _abstractMapStateToProps } from '../../../base/dialog/functions';
9 9
 import { isLocalParticipantModerator } from '../../../base/participants/functions';
10 10
 import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions';
11 11
 import { isVpaasMeeting } from '../../../jaas/functions';
12
+import { canStartTranscribing } from '../../../subtitles/functions';
12 13
 import { RECORDING_TYPES } from '../../constants';
13 14
 import { supportsLocalRecording } from '../../functions';
14 15
 
@@ -18,6 +19,11 @@ import { supportsLocalRecording } from '../../functions';
18 19
  */
19 20
 export interface IProps extends WithTranslation {
20 21
 
22
+    /**
23
+     * Whether the local participant can start transcribing.
24
+     */
25
+    _canStartTranscribing: boolean;
26
+
21 27
     /**
22 28
      * Style of the dialogs feature.
23 29
      */
@@ -111,11 +117,21 @@ export interface IProps extends WithTranslation {
111 117
      */
112 118
     onLocalRecordingSelfChange?: () => void;
113 119
 
120
+    /**
121
+     * Callback to change the audio and video recording setting.
122
+     */
123
+    onRecordAudioAndVideoChange: Function;
124
+
114 125
     /**
115 126
      * Callback to be invoked on sharing setting change.
116 127
      */
117 128
     onSharingSettingChanged: () => void;
118 129
 
130
+    /**
131
+     * Callback to change the transcription recording setting.
132
+     */
133
+    onTranscriptionChange: Function;
134
+
119 135
     /**
120 136
      * The currently selected recording service of type: RECORDING_TYPES.
121 137
      */
@@ -126,6 +142,16 @@ export interface IProps extends WithTranslation {
126 142
      */
127 143
     sharingSetting: boolean;
128 144
 
145
+    /**
146
+     * Whether to show the audio and video related content.
147
+     */
148
+    shouldRecordAudioAndVideo: boolean;
149
+
150
+    /**
151
+     * Whether to show the transcription related content.
152
+     */
153
+    shouldRecordTranscription: boolean;
154
+
129 155
     /**
130 156
      * Number of MiB of available space in user's Dropbox account.
131 157
      */
@@ -137,18 +163,26 @@ export interface IProps extends WithTranslation {
137 163
     userName?: string;
138 164
 }
139 165
 
166
+export interface IState {
167
+
168
+    /**
169
+     * Whether to show the advanced options or not.
170
+     */
171
+    showAdvancedOptions: boolean;
172
+}
173
+
140 174
 /**
141
- * React Component for getting confirmation to start a file recording session.
175
+ * React Component for getting confirmation to start a recording session.
142 176
  *
143 177
  * @augments Component
144 178
  */
145
-class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P> {
179
+class AbstractStartRecordingDialogContent extends Component<IProps, IState> {
146 180
     /**
147 181
      * Initializes a new {@code AbstractStartRecordingDialogContent} instance.
148 182
      *
149 183
      * @inheritdoc
150 184
      */
151
-    constructor(props: P) {
185
+    constructor(props: IProps) {
152 186
         super(props);
153 187
 
154 188
         // Bind event handler; it bounds once for every instance.
@@ -157,6 +191,13 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
157 191
         this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this);
158 192
         this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this);
159 193
         this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this);
194
+        this._onTranscriptionSwitchChange = this._onTranscriptionSwitchChange.bind(this);
195
+        this._onRecordAudioAndVideoSwitchChange = this._onRecordAudioAndVideoSwitchChange.bind(this);
196
+        this._onToggleShowOptions = this._onToggleShowOptions.bind(this);
197
+
198
+        this.state = {
199
+            showAdvancedOptions: false
200
+        };
160 201
     }
161 202
 
162 203
     /**
@@ -177,7 +218,7 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
177 218
      *
178 219
      * @inheritdoc
179 220
      */
180
-    componentDidUpdate(prevProps: P) {
221
+    componentDidUpdate(prevProps: IProps) {
181 222
         // Auto sign-out when the use chooses another recording service.
182 223
         if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX
183 224
                 && this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) {
@@ -185,6 +226,15 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
185 226
         }
186 227
     }
187 228
 
229
+    /**
230
+     * Returns whether the advanced options should be rendered.
231
+     *
232
+     * @returns {boolean}
233
+     */
234
+    _onToggleShowOptions() {
235
+        this.setState({ showAdvancedOptions: !this.state.showAdvancedOptions });
236
+    }
237
+
188 238
     /**
189 239
      * Whether the file sharing content should be rendered or not.
190 240
      *
@@ -208,6 +258,15 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
208 258
         return true;
209 259
     }
210 260
 
261
+    /**
262
+     * Whether the save transcription content should be rendered or not.
263
+     *
264
+     * @returns {boolean}
265
+     */
266
+    _canStartTranscribing() {
267
+        return this.props._canStartTranscribing;
268
+    }
269
+
211 270
     /**
212 271
      * Whether the no integrations content should be rendered or not.
213 272
      *
@@ -236,6 +295,26 @@ class AbstractStartRecordingDialogContent<P extends IProps> extends Component<P>
236 295
         return true;
237 296
     }
238 297
 
298
+    /**
299
+     * Handler for transcription switch change.
300
+     *
301
+     * @param {boolean} value - The new value.
302
+     * @returns {void}
303
+     */
304
+    _onTranscriptionSwitchChange(value: boolean | undefined) {
305
+        this.props.onTranscriptionChange(value);
306
+    }
307
+
308
+    /**
309
+     * Handler for audio and video switch change.
310
+     *
311
+     * @param {boolean} value - The new value.
312
+     * @returns {void}
313
+     */
314
+    _onRecordAudioAndVideoSwitchChange(value: boolean | undefined) {
315
+        this.props.onRecordAudioAndVideoChange(value);
316
+    }
317
+
239 318
     /**
240 319
      * Handler for onValueChange events from the Switch component.
241 320
      *
@@ -339,6 +418,7 @@ export function mapStateToProps(state: IReduxState) {
339 418
     return {
340 419
         ..._abstractMapStateToProps(state),
341 420
         isVpaas: isVpaasMeeting(state),
421
+        _canStartTranscribing: canStartTranscribing(state),
342 422
         _hideStorageWarning: Boolean(recordingService?.hideStorageWarning),
343 423
         _isModerator: isLocalParticipantModerator(state),
344 424
         _localRecordingAvailable,

+ 16
- 1
react/features/recording/components/Recording/native/StartRecordingDialog.tsx 查看文件

@@ -98,7 +98,16 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
98 98
      * @returns {boolean}
99 99
      */
100 100
     isStartRecordingDisabled() {
101
-        const { isTokenValid, selectedRecordingService } = this.state;
101
+        const {
102
+            isTokenValid,
103
+            selectedRecordingService,
104
+            shouldRecordAudioAndVideo,
105
+            shouldRecordTranscription
106
+        } = this.state;
107
+
108
+        if (!shouldRecordAudioAndVideo && !shouldRecordTranscription) {
109
+            return true;
110
+        }
102 111
 
103 112
         // Start button is disabled if recording service is only shown;
104 113
         // When validating dropbox token, if that is not enabled, we either always
@@ -125,6 +134,8 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
125 134
             isValidating,
126 135
             selectedRecordingService,
127 136
             sharingEnabled,
137
+            shouldRecordAudioAndVideo,
138
+            shouldRecordTranscription,
128 139
             spaceLeft,
129 140
             userName
130 141
         } = this.state;
@@ -142,9 +153,13 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
142 153
                     isTokenValid = { isTokenValid }
143 154
                     isValidating = { isValidating }
144 155
                     onChange = { this._onSelectedRecordingServiceChanged }
156
+                    onRecordAudioAndVideoChange = { this._onRecordAudioAndVideoChange }
145 157
                     onSharingSettingChanged = { this._onSharingSettingChanged }
158
+                    onTranscriptionChange = { this._onTranscriptionChange }
146 159
                     selectedRecordingService = { selectedRecordingService }
147 160
                     sharingSetting = { sharingEnabled }
161
+                    shouldRecordAudioAndVideo = { shouldRecordAudioAndVideo }
162
+                    shouldRecordTranscription = { shouldRecordTranscription }
148 163
                     spaceLeft = { spaceLeft }
149 164
                     userName = { userName } />
150 165
             </JitsiScreen>

+ 86
- 8
react/features/recording/components/Recording/native/StartRecordingDialogContent.tsx 查看文件

@@ -4,16 +4,15 @@ import { Text } from 'react-native-paper';
4 4
 import { connect } from 'react-redux';
5 5
 
6 6
 import { translate } from '../../../../base/i18n/functions';
7
+import Icon from '../../../../base/icons/components/Icon';
8
+import { IconArrowDown, IconArrowRight } from '../../../../base/icons/svg';
7 9
 import LoadingIndicator from '../../../../base/react/components/native/LoadingIndicator';
8 10
 import Button from '../../../../base/ui/components/native/Button';
9 11
 import Switch from '../../../../base/ui/components/native/Switch';
10 12
 import { BUTTON_TYPES } from '../../../../base/ui/constants.native';
11 13
 import { RECORDING_TYPES } from '../../../constants';
12 14
 import { getRecordingDurationEstimation } from '../../../functions';
13
-import AbstractStartRecordingDialogContent, {
14
-    IProps,
15
-    mapStateToProps
16
-} from '../AbstractStartRecordingDialogContent';
15
+import AbstractStartRecordingDialogContent, { mapStateToProps } from '../AbstractStartRecordingDialogContent';
17 16
 import {
18 17
     DROPBOX_LOGO,
19 18
     ICON_CLOUD,
@@ -25,7 +24,7 @@ import {
25 24
 /**
26 25
  * The start recording dialog content for the mobile application.
27 26
  */
28
-class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IProps> {
27
+class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
29 28
     /**
30 29
      * Renders the component.
31 30
      *
@@ -41,10 +40,86 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
41 40
                 { this._renderFileSharingContent() }
42 41
                 { this._renderUploadToTheCloudInfo() }
43 42
                 { this._renderIntegrationsContent() }
43
+                { this._renderAdvancedOptions() }
44 44
             </View>
45 45
         );
46 46
     }
47 47
 
48
+    /**
49
+     * Renders the save transcription switch.
50
+     *
51
+     * @returns {React$Component}
52
+     */
53
+    _renderAdvancedOptions() {
54
+        if (!this._canStartTranscribing()) {
55
+            return null;
56
+        }
57
+
58
+        const { showAdvancedOptions } = this.state;
59
+        const {
60
+            _dialogStyles,
61
+            _styles: styles,
62
+            shouldRecordAudioAndVideo,
63
+            shouldRecordTranscription,
64
+            t
65
+        } = this.props;
66
+
67
+        return (
68
+            <>
69
+                <View
70
+                    style = { styles.header }>
71
+                    <Text
72
+                        style = {{
73
+                            ..._dialogStyles.text,
74
+                            ...styles.title
75
+                        }}>
76
+                        { t('recording.showAdvancedOptions') }
77
+                    </Text>
78
+                    <Icon
79
+                        ariaPressed = { showAdvancedOptions }
80
+                        onClick = { this._onToggleShowOptions }
81
+                        role = 'button'
82
+                        size = { 24 }
83
+                        src = { showAdvancedOptions ? IconArrowDown : IconArrowRight } />
84
+                </View>
85
+                {showAdvancedOptions && (
86
+                    <>
87
+                        <View
88
+                            key = 'transcriptionSetting'
89
+                            style = { styles.header }>
90
+                            <Text
91
+                                style = {{
92
+                                    ..._dialogStyles.text,
93
+                                    ...styles.title
94
+                                }}>
95
+                                { t('recording.recordTranscription') }
96
+                            </Text>
97
+                            <Switch
98
+                                checked = { shouldRecordTranscription }
99
+                                onChange = { this._onTranscriptionSwitchChange }
100
+                                style = { styles.switch } />
101
+                        </View>
102
+                        <View
103
+                            key = 'audioVideoSetting'
104
+                            style = { styles.header }>
105
+                            <Text
106
+                                style = {{
107
+                                    ..._dialogStyles.text,
108
+                                    ...styles.title
109
+                                }}>
110
+                                { t('recording.recordAudioAndVideo') }
111
+                            </Text>
112
+                            <Switch
113
+                                checked = { shouldRecordAudioAndVideo }
114
+                                onChange = { this._onRecordAudioAndVideoSwitchChange }
115
+                                style = { styles.switch } />
116
+                        </View>
117
+                    </>
118
+                )}
119
+            </>
120
+        );
121
+    }
122
+
48 123
     /**
49 124
      * Renders the content in case no integrations were enabled.
50 125
      *
@@ -57,6 +132,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
57 132
             integrationsEnabled,
58 133
             isValidating,
59 134
             selectedRecordingService,
135
+            shouldRecordAudioAndVideo,
60 136
             t
61 137
         } = this.props;
62 138
 
@@ -69,7 +145,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
69 145
                 ? (
70 146
                     <Switch
71 147
                         checked = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE }
72
-                        disabled = { isValidating }
148
+                        disabled = { isValidating || !shouldRecordAudioAndVideo }
73 149
                         onChange = { this._onRecordingServiceSwitchChange }
74 150
                         style = { styles.switch } />
75 151
                 ) : null;
@@ -109,6 +185,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
109 185
             isValidating,
110 186
             onSharingSettingChanged,
111 187
             sharingSetting,
188
+            shouldRecordAudioAndVideo,
112 189
             t
113 190
         } = this.props;
114 191
 
@@ -128,7 +205,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
128 205
                 </Text>
129 206
                 <Switch
130 207
                     checked = { sharingSetting }
131
-                    disabled = { isValidating }
208
+                    disabled = { isValidating || !shouldRecordAudioAndVideo }
132 209
                     onChange = { onSharingSettingChanged }
133 210
                     style = { styles.switch } />
134 211
             </View>
@@ -237,6 +314,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
237 314
             isTokenValid,
238 315
             isValidating,
239 316
             selectedRecordingService,
317
+            shouldRecordAudioAndVideo,
240 318
             t
241 319
         } = this.props;
242 320
 
@@ -270,7 +348,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
270 348
             switchContent = (
271 349
                 <Switch
272 350
                     checked = { selectedRecordingService === RECORDING_TYPES.DROPBOX }
273
-                    disabled = { isValidating }
351
+                    disabled = { isValidating || !shouldRecordAudioAndVideo }
274 352
                     onChange = { this._onDropboxSwitchChange }
275 353
                     style = { styles.switch } />
276 354
             );

+ 2
- 0
react/features/recording/components/Recording/styles.web.ts 查看文件

@@ -12,3 +12,5 @@ export const ICON_CLOUD = 'images/icon-cloud.png';
12 12
 export const ICON_INFO = 'images/icon-info.png';
13 13
 
14 14
 export const ICON_USERS = 'images/icon-users.png';
15
+
16
+export const ICON_OPTIONS = 'images/icon-info.png';

+ 16
- 1
react/features/recording/components/Recording/web/StartRecordingDialog.tsx 查看文件

@@ -28,7 +28,16 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
28 28
      * @returns {boolean}
29 29
      */
30 30
     isStartRecordingDisabled() {
31
-        const { isTokenValid, selectedRecordingService } = this.state;
31
+        const {
32
+            isTokenValid,
33
+            selectedRecordingService,
34
+            shouldRecordAudioAndVideo,
35
+            shouldRecordTranscription
36
+        } = this.state;
37
+
38
+        if (!shouldRecordAudioAndVideo && !shouldRecordTranscription) {
39
+            return true;
40
+        }
32 41
 
33 42
         // Start button is disabled if recording service is only shown;
34 43
         // When validating dropbox token, if that is not enabled, we either always
@@ -57,6 +66,8 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
57 66
             localRecordingOnlySelf,
58 67
             selectedRecordingService,
59 68
             sharingEnabled,
69
+            shouldRecordAudioAndVideo,
70
+            shouldRecordTranscription,
60 71
             spaceLeft,
61 72
             userName
62 73
         } = this.state;
@@ -82,9 +93,13 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
82 93
                     localRecordingOnlySelf = { localRecordingOnlySelf }
83 94
                     onChange = { this._onSelectedRecordingServiceChanged }
84 95
                     onLocalRecordingSelfChange = { this._onLocalRecordingSelfChange }
96
+                    onRecordAudioAndVideoChange = { this._onRecordAudioAndVideoChange }
85 97
                     onSharingSettingChanged = { this._onSharingSettingChanged }
98
+                    onTranscriptionChange = { this._onTranscriptionChange }
86 99
                     selectedRecordingService = { selectedRecordingService }
87 100
                     sharingSetting = { sharingEnabled }
101
+                    shouldRecordAudioAndVideo = { shouldRecordAudioAndVideo }
102
+                    shouldRecordTranscription = { shouldRecordTranscription }
88 103
                     spaceLeft = { spaceLeft }
89 104
                     userName = { userName } />
90 105
             </Dialog>

+ 71
- 10
react/features/recording/components/Recording/web/StartRecordingDialogContent.tsx 查看文件

@@ -2,6 +2,8 @@ import React from 'react';
2 2
 import { connect } from 'react-redux';
3 3
 
4 4
 import { translate } from '../../../../base/i18n/functions';
5
+import Icon from '../../../../base/icons/components/Icon';
6
+import { IconArrowDown, IconArrowRight } from '../../../../base/icons/svg';
5 7
 import Container from '../../../../base/react/components/web/Container';
6 8
 import Image from '../../../../base/react/components/web/Image';
7 9
 import LoadingIndicator from '../../../../base/react/components/web/LoadingIndicator';
@@ -11,10 +13,7 @@ import Switch from '../../../../base/ui/components/web/Switch';
11 13
 import { BUTTON_TYPES } from '../../../../base/ui/constants.web';
12 14
 import { RECORDING_TYPES } from '../../../constants';
13 15
 import { getRecordingDurationEstimation } from '../../../functions';
14
-import AbstractStartRecordingDialogContent, {
15
-    IProps,
16
-    mapStateToProps
17
-} from '../AbstractStartRecordingDialogContent';
16
+import AbstractStartRecordingDialogContent, { mapStateToProps } from '../AbstractStartRecordingDialogContent';
18 17
 import {
19 18
     DROPBOX_LOGO,
20 19
     ICON_CLOUD,
@@ -30,7 +29,7 @@ const EMPTY_FUNCTION = () => {
30 29
 /**
31 30
  * The start recording dialog content for the mobile application.
32 31
  */
33
-class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IProps> {
32
+class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
34 33
     /**
35 34
      * Renders the component.
36 35
      *
@@ -49,10 +48,72 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
49 48
                     </>
50 49
                 )}
51 50
                 { this._renderLocalRecordingContent() }
51
+                { this._renderAdvancedOptions() }
52 52
             </Container>
53 53
         );
54 54
     }
55 55
 
56
+    /**
57
+     * Renders the switch for saving the transcription.
58
+     *
59
+     * @returns {React$Component}
60
+     */
61
+    _renderAdvancedOptions() {
62
+        if (!this._canStartTranscribing()) {
63
+            return null;
64
+        }
65
+
66
+        const { showAdvancedOptions } = this.state;
67
+        const { shouldRecordAudioAndVideo, shouldRecordTranscription, t } = this.props;
68
+
69
+        return (
70
+            <>
71
+                <div className = 'recording-header-line' />
72
+                <div
73
+                    className = 'recording-header'
74
+                    onClick = { this._onToggleShowOptions }>
75
+                    <label className = 'recording-title-no-space'>
76
+                        {t('recording.showAdvancedOptions')}
77
+                    </label>
78
+                    <Icon
79
+                        ariaPressed = { showAdvancedOptions }
80
+                        onClick = { this._onToggleShowOptions }
81
+                        role = 'button'
82
+                        size = { 24 }
83
+                        src = { showAdvancedOptions ? IconArrowDown : IconArrowRight } />
84
+                </div>
85
+                {showAdvancedOptions && (
86
+                    <>
87
+                        <div className = 'recording-header space-top'>
88
+                            <label
89
+                                className = 'recording-title'
90
+                                htmlFor = 'recording-switch-transcription'>
91
+                                { t('recording.recordTranscription') }
92
+                            </label>
93
+                            <Switch
94
+                                checked = { shouldRecordTranscription }
95
+                                className = 'recording-switch'
96
+                                id = 'recording-switch-transcription'
97
+                                onChange = { this._onTranscriptionSwitchChange } />
98
+                        </div>
99
+                        <div className = 'recording-header space-top'>
100
+                            <label
101
+                                className = 'recording-title'
102
+                                htmlFor = 'recording-switch-transcription'>
103
+                                { t('recording.recordAudioAndVideo') }
104
+                            </label>
105
+                            <Switch
106
+                                checked = { shouldRecordAudioAndVideo }
107
+                                className = 'recording-switch'
108
+                                id = 'recording-switch-transcription'
109
+                                onChange = { this._onRecordAudioAndVideoSwitchChange } />
110
+                        </div>
111
+                    </>
112
+                )}
113
+            </>
114
+        );
115
+    }
116
+
56 117
     /**
57 118
      * Renders the content in case no integrations were enabled.
58 119
      *
@@ -78,7 +139,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
78 139
                     <Switch
79 140
                         checked = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE }
80 141
                         className = 'recording-switch'
81
-                        disabled = { isValidating }
142
+                        disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
82 143
                         id = 'recording-switch-jitsi'
83 144
                         onChange = { this._onRecordingServiceSwitchChange } />
84 145
                 ) : null;
@@ -148,7 +209,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
148 209
                 <Switch
149 210
                     checked = { sharingSetting }
150 211
                     className = 'recording-switch'
151
-                    disabled = { isValidating }
212
+                    disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
152 213
                     id = 'recording-switch-share'
153 214
                     onChange = { onSharingSettingChanged } />
154 215
             </Container>
@@ -294,7 +355,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
294 355
                     checked = { selectedRecordingService
295 356
                         === RECORDING_TYPES.DROPBOX }
296 357
                     className = 'recording-switch'
297
-                    disabled = { isValidating }
358
+                    disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
298 359
                     id = 'recording-switch-integration'
299 360
                     onChange = { this._onDropboxSwitchChange } />
300 361
             );
@@ -372,7 +433,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
372 433
                             checked = { selectedRecordingService
373 434
                                 === RECORDING_TYPES.LOCAL }
374 435
                             className = 'recording-switch'
375
-                            disabled = { isValidating }
436
+                            disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
376 437
                             id = 'recording-switch-local'
377 438
                             onChange = { this._onLocalRecordingSwitchChange } />
378 439
                     </Container>
@@ -396,7 +457,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
396 457
                                     <Switch
397 458
                                         checked = { Boolean(localRecordingOnlySelf) }
398 459
                                         className = 'recording-switch'
399
-                                        disabled = { isValidating }
460
+                                        disabled = { isValidating || !this.props.shouldRecordAudioAndVideo }
400 461
                                         id = 'recording-switch-myself'
401 462
                                         onChange = { onLocalRecordingSelfChange ?? EMPTY_FUNCTION } />
402 463
                                 </Container>

+ 2
- 5
react/features/subtitles/components/AbstractClosedCaptionButton.tsx 查看文件

@@ -2,9 +2,9 @@ import { createToolbarEvent } from '../../analytics/AnalyticsEvents';
2 2
 import { sendAnalytics } from '../../analytics/functions';
3 3
 import { IReduxState } from '../../app/types';
4 4
 import { MEET_FEATURES } from '../../base/jwt/constants';
5
-import { isLocalParticipantModerator } from '../../base/participants/functions';
6 5
 import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton';
7 6
 import { maybeShowPremiumFeatureDialog } from '../../jaas/actions';
7
+import { canStartTranscribing } from '../functions';
8 8
 
9 9
 export interface IAbstractProps extends AbstractButtonProps {
10 10
 
@@ -103,13 +103,10 @@ export class AbstractClosedCaptionButton
103 103
  */
104 104
 export function _abstractMapStateToProps(state: IReduxState, ownProps: IAbstractProps) {
105 105
     const { _requestingSubtitles, _language } = state['features/subtitles'];
106
-    const { transcription } = state['features/base/config'];
107
-    const { isTranscribing } = state['features/transcribing'];
108 106
 
109 107
     // if the participant is moderator, it can enable transcriptions and if
110 108
     // transcriptions are already started for the meeting, guests can just show them
111
-    const { visible = Boolean(transcription?.enabled
112
-        && (isLocalParticipantModerator(state) || isTranscribing)) } = ownProps;
109
+    const { visible = canStartTranscribing(state) } = ownProps;
113 110
 
114 111
     return {
115 112
         _requestingSubtitles,

+ 15
- 0
react/features/subtitles/functions.ts 查看文件

@@ -0,0 +1,15 @@
1
+import { IReduxState } from '../app/types';
2
+import { isLocalParticipantModerator } from '../base/participants/functions';
3
+
4
+/**
5
+ * Checks whether the participant can start the transcription.
6
+ *
7
+ * @param {IReduxState} state - The redux state.
8
+ * @returns {boolean} - True if the participant can start the transcription.
9
+ */
10
+export function canStartTranscribing(state: IReduxState) {
11
+    const { transcription } = state['features/base/config'];
12
+    const { isTranscribing } = state['features/transcribing'];
13
+
14
+    return Boolean(transcription?.enabled && (isLocalParticipantModerator(state) || isTranscribing));
15
+}

Loading…
取消
儲存