diff --git a/config.js b/config.js
index 0c6a537f79..0d706ad6bd 100644
--- a/config.js
+++ b/config.js
@@ -295,8 +295,13 @@ var config = {
// Whether to enable live streaming or not.
// liveStreamingEnabled: false,
- // Whether to enable local recording or not.
- // enableLocalRecording: false,
+ // Local recording configuration.
+ // localRecording: {
+ // // Whether to enable local recording or not.
+ // enable: false,
+ // // Whether to notify all participants when a participant is recording locally.
+ // notifyAllParticipants: false
+ // },
// Transcription (in interface_config,
// subtitles and buttons can be configured)
diff --git a/css/_recording.scss b/css/_recording.scss
index 1478c2797a..9674f31cd1 100644
--- a/css/_recording.scss
+++ b/css/_recording.scss
@@ -28,8 +28,21 @@
}
.local-recording-warning {
- margin-top: 4px;
+ margin-top: 8px;
display: block;
+ font-size: 14px;
+ line-height: 20px;
+ padding: 8px 16px;
+
+ &.text {
+ color: #fff;
+ background-color: #3D3D3D;
+ }
+
+ &.notification {
+ color: #040404;
+ background-color: #F8AE1A;
+ }
}
.recording-switch-disabled {
@@ -46,7 +59,7 @@
border-radius: 4px;
height: 40px;
justify-content: center;
- width: 56px;
+ width: 42px;
}
.cloud-content-recording-icon-container {
@@ -58,7 +71,7 @@
}
.jitsi-recording-header {
- margin-bottom: 32px;
+ margin-bottom: 16px;
}
.jitsi-content-recording-icon-container-with-switch {
diff --git a/lang/main.json b/lang/main.json
index e03743dc28..b1c8fe9acb 100644
--- a/lang/main.json
+++ b/lang/main.json
@@ -894,6 +894,7 @@
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try {{app}}.",
"linkGenerated": "We have generated a link to your recording.",
"live": "LIVE",
+ "localRecordingNoNotificationWarning": "The recording will not be announced to other participants. You will need to let them know that the meeting is recorded.",
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio. The recording is currently limited to 1GB, which is around 100 minutes.",
"loggedIn": "Logged in as {{userName}}",
"off": "Recording stopped",
diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.js
index 5e824bb82f..9927764dc5 100644
--- a/react/features/base/config/configWhitelist.js
+++ b/react/features/base/config/configWhitelist.js
@@ -143,7 +143,6 @@ export default [
'enableLayerSuspension',
'enableLipSync',
'enableLobbyChat',
- 'enableLocalRecording',
'enableOpusRed',
'enableRemb',
'enableSaveLogs',
@@ -185,6 +184,7 @@ export default [
'ignoreStartMuted',
'inviteAppName',
'liveStreamingEnabled',
+ 'localRecording',
'localSubject',
'maxFullResolutionParticipants',
'mouseMoveCallbackInterval',
diff --git a/react/features/base/participants/middleware.js b/react/features/base/participants/middleware.js
index 7db444e1a8..b4e29e010e 100644
--- a/react/features/base/participants/middleware.js
+++ b/react/features/base/participants/middleware.js
@@ -178,20 +178,24 @@ MiddlewareRegistry.register(store => next => action => {
}
case SET_LOCAL_PARTICIPANT_RECORDING_STATUS: {
+ const state = store.getState();
const { recording } = action;
- const localId = getLocalParticipant(store.getState())?.id;
-
- store.dispatch(participantUpdated({
- // XXX Only the local participant is allowed to update without
- // stating the JitsiConference instance (i.e. participant property
- // `conference` for a remote participant) because the local
- // participant is uniquely identified by the very fact that there is
- // only one local participant.
-
- id: localId,
- local: true,
- localRecording: recording
- }));
+ const localId = getLocalParticipant(state)?.id;
+ const { localRecording } = state['features/base/config'];
+
+ if (localRecording.notifyAllParticipants) {
+ store.dispatch(participantUpdated({
+ // XXX Only the local participant is allowed to update without
+ // stating the JitsiConference instance (i.e. participant property
+ // `conference` for a remote participant) because the local
+ // participant is uniquely identified by the very fact that there is
+ // only one local participant.
+
+ id: localId,
+ local: true,
+ localRecording: recording
+ }));
+ }
break;
}
diff --git a/react/features/recording/components/Recording/StartRecordingDialogContent.js b/react/features/recording/components/Recording/StartRecordingDialogContent.js
index 3eeecc4512..8a0bacd513 100644
--- a/react/features/recording/components/Recording/StartRecordingDialogContent.js
+++ b/react/features/recording/components/Recording/StartRecordingDialogContent.js
@@ -49,6 +49,11 @@ type Props = {
*/
_localRecordingEnabled: boolean,
+ /**
+ * Whether we won't notify the other participants about the recording.
+ */
+ _localRecordingNoNotification: boolean,
+
/**
* The color-schemed stylesheet of this component.
*/
@@ -442,7 +447,8 @@ class StartRecordingDialogContent extends Component {
return (
@@ -609,7 +615,14 @@ class StartRecordingDialogContent extends Component {
* @returns {React$Component}
*/
_renderLocalRecordingContent() {
- const { _styles: styles, isValidating, t, _dialogStyles, selectedRecordingService } = this.props;
+ const {
+ _styles: styles,
+ isValidating,
+ t,
+ _dialogStyles,
+ selectedRecordingService,
+ _localRecordingNoNotification
+ } = this.props;
if (!this._localRecordingAvailable) {
return null;
@@ -645,9 +658,14 @@ class StartRecordingDialogContent extends Component {
=== RECORDING_TYPES.LOCAL } />
{selectedRecordingService === RECORDING_TYPES.LOCAL
- &&
- {t('recording.localRecordingWarning')}
-
+ && <>
+
+ {t('recording.localRecordingWarning')}
+
+ {_localRecordingNoNotification &&
+ {t('recording.localRecordingNoNotificationWarning')}
+ }
+ >
}
@@ -689,7 +707,8 @@ function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
- _localRecordingEnabled: state['features/base/config'].enableLocalRecording,
+ _localRecordingEnabled: state['features/base/config'].localRecording.enable,
+ _localRecordingNoNotification: !state['features/base/config'].localRecording.notifyAllParticipants,
_styles: ColorSchemeRegistry.get(state, 'StartRecordingDialogContent')
};
}
diff --git a/react/features/recording/middleware.js b/react/features/recording/middleware.js
index 49bfb1bbe4..138d8f0cf0 100644
--- a/react/features/recording/middleware.js
+++ b/react/features/recording/middleware.js
@@ -132,6 +132,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
}
case START_LOCAL_RECORDING: {
+ const { localRecording } = getState()['features/base/config'];
+
try {
await LocalRecordingManager.startLocalRecording({ dispatch,
getState });
@@ -140,9 +142,12 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
titleKey: 'dialog.recording'
};
- dispatch(playSound(RECORDING_ON_SOUND_ID));
+ if (localRecording.notifyAllParticipants) {
+ dispatch(playSound(RECORDING_ON_SOUND_ID));
+ }
dispatch(showNotification(props, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
dispatch(updateLocalRecordingStatus(true));
+ sendAnalytics(createRecordingEvent('started', 'local'));
} catch (err) {
logger.error('Capture failed', err);
@@ -158,10 +163,14 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
}
case STOP_LOCAL_RECORDING: {
+ const { localRecording } = getState()['features/base/config'];
+
if (LocalRecordingManager.isRecordingLocally()) {
LocalRecordingManager.stopLocalRecording();
- dispatch(playSound(RECORDING_OFF_SOUND_ID));
dispatch(updateLocalRecordingStatus(false));
+ if (localRecording.notifyAllParticipants) {
+ dispatch(playSound(RECORDING_OFF_SOUND_ID));
+ }
}
break;
}