Browse Source

feat(follow-me): Adds option to limit it for recorder only. (#15120)

* feat(follow-me): Adds option to limit it for recorder only.

* squash: Fix comments.

* squash: Fix comments.
factor2
Дамян Минков 1 year ago
parent
commit
085e6dd3b9
No account linked to committer's email address

+ 1
- 0
lang/main.json View File

1096
         "desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
1096
         "desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
1097
         "devices": "Devices",
1097
         "devices": "Devices",
1098
         "followMe": "Everyone follows me",
1098
         "followMe": "Everyone follows me",
1099
+        "followMeRecorder": "Recorder follows me",
1099
         "framesPerSecond": "frames-per-second",
1100
         "framesPerSecond": "frames-per-second",
1100
         "incomingMessage": "Incoming message",
1101
         "incomingMessage": "Incoming message",
1101
         "language": "Language",
1102
         "language": "Language",

+ 14
- 3
react/features/base/conference/actionTypes.ts View File

56
 /**
56
 /**
57
  * The type of (redux) action which signals that the conference is out of focus.
57
  * The type of (redux) action which signals that the conference is out of focus.
58
  * For example, if the user navigates to the Chat screen.
58
  * For example, if the user navigates to the Chat screen.
59
- * 
59
+ *
60
  * {
60
  * {
61
  *      type: CONFERENCE_BLURRED,
61
  *      type: CONFERENCE_BLURRED,
62
  * }
62
  * }
65
 
65
 
66
 /**
66
 /**
67
  * The type of (redux) action which signals that the conference is in focus.
67
  * The type of (redux) action which signals that the conference is in focus.
68
- * 
68
+ *
69
  * {
69
  * {
70
  *      type: CONFERENCE_FOCUSED,
70
  *      type: CONFERENCE_FOCUSED,
71
  * }
71
  * }
258
  */
258
  */
259
 export const SET_FOLLOW_ME = 'SET_FOLLOW_ME';
259
 export const SET_FOLLOW_ME = 'SET_FOLLOW_ME';
260
 
260
 
261
+/**
262
+ * The type of (redux) action which updates the current known status of the
263
+ * Follow Me feature that is used only by the recorder.
264
+ *
265
+ * {
266
+ *     type: SET_FOLLOW_ME_RECORDER,
267
+ *     enabled: boolean
268
+ * }
269
+ */
270
+export const SET_FOLLOW_ME_RECORDER = 'SET_FOLLOW_ME_RECORDER';
271
+
261
 /**
272
 /**
262
  * The type of (redux) action which sets the obfuscated room name.
273
  * The type of (redux) action which sets the obfuscated room name.
263
  *
274
  *
338
 
349
 
339
 /**
350
 /**
340
  * The type of (redux) action which updates the assumed bandwidth bps.
351
  * The type of (redux) action which updates the assumed bandwidth bps.
341
- * 
352
+ *
342
  * {
353
  * {
343
  *      type: SET_ASSUMED_BANDWIDTH_BPS,
354
  *      type: SET_ASSUMED_BANDWIDTH_BPS,
344
  *      assumedBandwidthBps: number
355
  *      assumedBandwidthBps: number

+ 17
- 0
react/features/base/conference/actions.any.ts View File

61
     SEND_TONES,
61
     SEND_TONES,
62
     SET_ASSUMED_BANDWIDTH_BPS,
62
     SET_ASSUMED_BANDWIDTH_BPS,
63
     SET_FOLLOW_ME,
63
     SET_FOLLOW_ME,
64
+    SET_FOLLOW_ME_RECORDER,
64
     SET_OBFUSCATED_ROOM,
65
     SET_OBFUSCATED_ROOM,
65
     SET_PASSWORD,
66
     SET_PASSWORD,
66
     SET_PASSWORD_FAILED,
67
     SET_PASSWORD_FAILED,
847
     };
848
     };
848
 }
849
 }
849
 
850
 
851
+/**
852
+ * Enables or disables the Follow Me feature used only for the recorder.
853
+ *
854
+ * @param {boolean} enabled - Whether Follow Me should be enabled and used only by the recorder.
855
+ * @returns {{
856
+ *     type: SET_FOLLOW_ME_RECORDER,
857
+ *     enabled: boolean
858
+ * }}
859
+ */
860
+export function setFollowMeRecorder(enabled: boolean) {
861
+    return {
862
+        type: SET_FOLLOW_ME_RECORDER,
863
+        enabled
864
+    };
865
+}
866
+
850
 /**
867
 /**
851
  * Enables or disables the Mute reaction sounds feature.
868
  * Enables or disables the Mute reaction sounds feature.
852
  *
869
  *

+ 8
- 0
react/features/base/conference/reducer.ts View File

26
     P2P_STATUS_CHANGED,
26
     P2P_STATUS_CHANGED,
27
     SET_ASSUMED_BANDWIDTH_BPS,
27
     SET_ASSUMED_BANDWIDTH_BPS,
28
     SET_FOLLOW_ME,
28
     SET_FOLLOW_ME,
29
+    SET_FOLLOW_ME_RECORDER,
29
     SET_OBFUSCATED_ROOM,
30
     SET_OBFUSCATED_ROOM,
30
     SET_PASSWORD,
31
     SET_PASSWORD,
31
     SET_PENDING_SUBJECT_CHANGE,
32
     SET_PENDING_SUBJECT_CHANGE,
160
     e2eeSupported?: boolean;
161
     e2eeSupported?: boolean;
161
     error?: Error;
162
     error?: Error;
162
     followMeEnabled?: boolean;
163
     followMeEnabled?: boolean;
164
+    followMeRecorderEnabled?: boolean;
163
     joining?: IJitsiConference;
165
     joining?: IJitsiConference;
164
     leaving?: IJitsiConference;
166
     leaving?: IJitsiConference;
165
     lobbyWaitingForHost?: boolean;
167
     lobbyWaitingForHost?: boolean;
252
         case SET_FOLLOW_ME:
254
         case SET_FOLLOW_ME:
253
             return set(state, 'followMeEnabled', action.enabled);
255
             return set(state, 'followMeEnabled', action.enabled);
254
 
256
 
257
+        case SET_FOLLOW_ME_RECORDER:
258
+            return { ...state,
259
+                followMeRecorderEnabled: action.enabled,
260
+                followMeEnabled: action.enabled
261
+            };
262
+
255
         case SET_START_REACTIONS_MUTED:
263
         case SET_START_REACTIONS_MUTED:
256
             return set(state, 'startReactionsMuted', action.muted);
264
             return set(state, 'startReactionsMuted', action.muted);
257
 
265
 

+ 6
- 3
react/features/follow-me/actions.ts View File

7
  * Sets the current moderator id or clears it.
7
  * Sets the current moderator id or clears it.
8
  *
8
  *
9
  * @param {?string} id - The Follow Me moderator participant id.
9
  * @param {?string} id - The Follow Me moderator participant id.
10
+ * @param {?boolean} forRecorder - Whether this is command only for recorder.
10
  * @returns {{
11
  * @returns {{
11
  *     type: SET_FOLLOW_ME_MODERATOR,
12
  *     type: SET_FOLLOW_ME_MODERATOR,
12
- *     id, string
13
+ *     id: string,
14
+ *     forRecorder: boolean
13
  * }}
15
  * }}
14
  */
16
  */
15
-export function setFollowMeModerator(id?: string) {
17
+export function setFollowMeModerator(id?: string, forRecorder?: boolean) {
16
     return {
18
     return {
17
         type: SET_FOLLOW_ME_MODERATOR,
19
         type: SET_FOLLOW_ME_MODERATOR,
18
-        id
20
+        id,
21
+        forRecorder
19
     };
22
     };
20
 }
23
 }
21
 
24
 

+ 13
- 0
react/features/follow-me/functions.ts View File

13
 
13
 
14
     return Boolean(state['features/follow-me'].moderator);
14
     return Boolean(state['features/follow-me'].moderator);
15
 }
15
 }
16
+
17
+/**
18
+ * Returns true if follow me is active only for the recorder and false otherwise.
19
+ *
20
+ * @param {Object|Function} stateful - Object or function that can be resolved
21
+ * to the Redux state.
22
+ * @returns {boolean} - True if follow me is active and false otherwise.
23
+ */
24
+export function isFollowMeRecorderActive(stateful: IStateful) {
25
+    const state = toState(stateful);
26
+
27
+    return Boolean(state['features/follow-me'].recorder);
28
+}

+ 6
- 1
react/features/follow-me/middleware.ts View File

128
     }
128
     }
129
 
129
 
130
     if (!isFollowMeActive(state)) {
130
     if (!isFollowMeActive(state)) {
131
-        store.dispatch(setFollowMeModerator(id));
131
+        store.dispatch(setFollowMeModerator(id, attributes.recorder));
132
     }
132
     }
133
 
133
 
134
     // just a command that follow me was turned off
134
     // just a command that follow me was turned off
138
         return;
138
         return;
139
     }
139
     }
140
 
140
 
141
+    // when recorder flag is on, follow me is handled only on recorder side
142
+    if (attributes.recorder && !store.getState()['features/base/config'].iAmRecorder) {
143
+        return;
144
+    }
145
+
141
     const oldState = state['features/follow-me'].state || {};
146
     const oldState = state['features/follow-me'].state || {};
142
 
147
 
143
     store.dispatch(setFollowMeState(attributes));
148
     store.dispatch(setFollowMeState(attributes));

+ 5
- 1
react/features/follow-me/reducer.ts View File

8
 
8
 
9
 export interface IFollowMeState {
9
 export interface IFollowMeState {
10
     moderator?: string;
10
     moderator?: string;
11
+    recorder?: boolean;
11
     state?: {
12
     state?: {
12
         [key: string]: string;
13
         [key: string]: string;
13
     };
14
     };
24
         case SET_FOLLOW_ME_MODERATOR: {
25
         case SET_FOLLOW_ME_MODERATOR: {
25
             let newState = set(state, 'moderator', action.id);
26
             let newState = set(state, 'moderator', action.id);
26
 
27
 
27
-            if (!action.id) {
28
+            if (action.id) {
29
+                newState = set(newState, 'recorder', action.forRecorder);
30
+            } else {
28
                 // clear the state if feature becomes disabled
31
                 // clear the state if feature becomes disabled
29
                 newState = set(newState, 'state', undefined);
32
                 newState = set(newState, 'state', undefined);
33
+                newState = set(newState, 'recorder', undefined);
30
             }
34
             }
31
 
35
 
32
             return newState;
36
             return newState;

+ 1
- 0
react/features/follow-me/subscriber.ts View File

88
     const stageFilmstrip = isStageFilmstripEnabled(state);
88
     const stageFilmstrip = isStageFilmstripEnabled(state);
89
 
89
 
90
     return {
90
     return {
91
+        recorder: state['features/base/conference'].followMeRecorderEnabled,
91
         filmstripVisible: state['features/filmstrip'].visible,
92
         filmstripVisible: state['features/filmstrip'].visible,
92
         maxStageParticipants: stageFilmstrip ? state['features/base/settings'].maxStageParticipants : undefined,
93
         maxStageParticipants: stageFilmstrip ? state['features/base/settings'].maxStageParticipants : undefined,
93
         nextOnStage: pinnedParticipant?.id,
94
         nextOnStage: pinnedParticipant?.id,

+ 5
- 0
react/features/settings/actions.web.ts View File

5
 import { isTokenAuthEnabled } from '../authentication/functions';
5
 import { isTokenAuthEnabled } from '../authentication/functions';
6
 import {
6
 import {
7
     setFollowMe,
7
     setFollowMe,
8
+    setFollowMeRecorder,
8
     setStartMutedPolicy,
9
     setStartMutedPolicy,
9
     setStartReactionsMuted
10
     setStartReactionsMuted
10
 } from '../base/conference/actions';
11
 } from '../base/conference/actions';
165
             dispatch(setFollowMe(newState.followMeEnabled));
166
             dispatch(setFollowMe(newState.followMeEnabled));
166
         }
167
         }
167
 
168
 
169
+        if (newState.followMeRecorderEnabled !== currentState.followMeRecorderEnabled) {
170
+            dispatch(setFollowMeRecorder(newState.followMeRecorderEnabled));
171
+        }
172
+
168
         if (newState.startReactionsMuted !== currentState.startReactionsMuted) {
173
         if (newState.startReactionsMuted !== currentState.startReactionsMuted) {
169
             batch(() => {
174
             batch(() => {
170
                 // updating settings we want to update and backend (notify the rest of the participants)
175
                 // updating settings we want to update and backend (notify the rest of the participants)

+ 22
- 1
react/features/settings/components/native/ModeratorSection.tsx View File

2
 import { useDispatch, useSelector } from 'react-redux';
2
 import { useDispatch, useSelector } from 'react-redux';
3
 
3
 
4
 import { IReduxState } from '../../../app/types';
4
 import { IReduxState } from '../../../app/types';
5
-import { setFollowMe, setStartMutedPolicy, setStartReactionsMuted } from '../../../base/conference/actions';
5
+import {
6
+    setFollowMe,
7
+    setFollowMeRecorder,
8
+    setStartMutedPolicy,
9
+    setStartReactionsMuted
10
+} from '../../../base/conference/actions';
6
 import { updateSettings } from '../../../base/settings/actions';
11
 import { updateSettings } from '../../../base/settings/actions';
7
 import Switch from '../../../base/ui/components/native/Switch';
12
 import Switch from '../../../base/ui/components/native/Switch';
8
 import { getModeratorTabProps } from '../../functions.native';
13
 import { getModeratorTabProps } from '../../functions.native';
14
     const dispatch = useDispatch();
19
     const dispatch = useDispatch();
15
     const {
20
     const {
16
         followMeEnabled,
21
         followMeEnabled,
22
+        followMeRecorderEnabled,
17
         startAudioMuted,
23
         startAudioMuted,
18
         startVideoMuted,
24
         startVideoMuted,
19
         startReactionsMuted
25
         startReactionsMuted
35
         dispatch(setFollowMe(Boolean(enabled)));
41
         dispatch(setFollowMe(Boolean(enabled)));
36
     }, [ dispatch, setFollowMe ]);
42
     }, [ dispatch, setFollowMe ]);
37
 
43
 
44
+    const onFollowMeRecorderToggled = useCallback((enabled?: boolean) => {
45
+        dispatch(setFollowMeRecorder(Boolean(enabled)));
46
+    }, [ dispatch, setFollowMeRecorder ]);
47
+
38
     const onStartReactionsMutedToggled = useCallback((enabled?: boolean) => {
48
     const onStartReactionsMutedToggled = useCallback((enabled?: boolean) => {
39
         dispatch(setStartReactionsMuted(Boolean(enabled), true));
49
         dispatch(setStartReactionsMuted(Boolean(enabled), true));
40
         dispatch(updateSettings({ soundsReactions: enabled }));
50
         dispatch(updateSettings({ soundsReactions: enabled }));
57
                 state: followMeEnabled,
67
                 state: followMeEnabled,
58
                 onChange: onFollowMeToggled
68
                 onChange: onFollowMeToggled
59
             },
69
             },
70
+            {
71
+                label: 'settings.followMeRecorder',
72
+                state: followMeRecorderEnabled,
73
+                onChange: onFollowMeRecorderToggled
74
+            },
60
             {
75
             {
61
                 label: 'settings.startReactionsMuted',
76
                 label: 'settings.startReactionsMuted',
62
                 state: startReactionsMuted,
77
                 state: startReactionsMuted,
72
     }, [ startAudioMuted,
87
     }, [ startAudioMuted,
73
         startVideoMuted,
88
         startVideoMuted,
74
         followMeEnabled,
89
         followMeEnabled,
90
+        followMeRecorderEnabled,
75
         disableReactionsModeration,
91
         disableReactionsModeration,
92
+        onStartAudioMutedToggled,
93
+        onStartVideoMutedToggled,
94
+        onFollowMeToggled,
95
+        onFollowMeRecorderToggled,
96
+        onStartReactionsMutedToggled,
76
         startReactionsMuted ]);
97
         startReactionsMuted ]);
77
 
98
 
78
     return (
99
     return (

+ 32
- 1
react/features/settings/components/web/ModeratorTab.tsx View File

34
      */
34
      */
35
     followMeEnabled: boolean;
35
     followMeEnabled: boolean;
36
 
36
 
37
+    /**
38
+     * Whether follow me for recorder is currently active (enabled by some other participant).
39
+     */
40
+    followMeRecorderActive: boolean;
41
+
42
+    /**
43
+     * Whether the user has selected the Follow Me Recorder feature to be enabled.
44
+     */
45
+    followMeRecorderEnabled: boolean;
46
+
37
     /**
47
     /**
38
      * Whether or not the user has selected the Start Audio Muted feature to be
48
      * Whether or not the user has selected the Start Audio Muted feature to be
39
      * enabled.
49
      * enabled.
92
         this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
102
         this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
93
         this._onStartReactionsMutedChanged = this._onStartReactionsMutedChanged.bind(this);
103
         this._onStartReactionsMutedChanged = this._onStartReactionsMutedChanged.bind(this);
94
         this._onFollowMeEnabledChanged = this._onFollowMeEnabledChanged.bind(this);
104
         this._onFollowMeEnabledChanged = this._onFollowMeEnabledChanged.bind(this);
105
+        this._onFollowMeRecorderEnabledChanged = this._onFollowMeRecorderEnabledChanged.bind(this);
95
     }
106
     }
96
 
107
 
97
     /**
108
     /**
142
         super._onChange({ followMeEnabled: checked });
153
         super._onChange({ followMeEnabled: checked });
143
     }
154
     }
144
 
155
 
156
+    /**
157
+     * Callback invoked to select if follow-me for recorder mode should be activated.
158
+     *
159
+     * @param {Object} e - The key event to handle.
160
+     *
161
+     * @returns {void}
162
+     */
163
+    _onFollowMeRecorderEnabledChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) {
164
+        super._onChange({ followMeRecorderEnabled: checked });
165
+    }
166
+
145
     /**
167
     /**
146
      * Implements React's {@link Component#render()}.
168
      * Implements React's {@link Component#render()}.
147
      *
169
      *
153
             disableReactionsModeration,
175
             disableReactionsModeration,
154
             followMeActive,
176
             followMeActive,
155
             followMeEnabled,
177
             followMeEnabled,
178
+            followMeRecorderActive,
179
+            followMeRecorderEnabled,
156
             startAudioMuted,
180
             startAudioMuted,
157
             startVideoMuted,
181
             startVideoMuted,
158
             startReactionsMuted,
182
             startReactionsMuted,
182
                 <Checkbox
206
                 <Checkbox
183
                     checked = { followMeEnabled && !followMeActive }
207
                     checked = { followMeEnabled && !followMeActive }
184
                     className = { classes.checkbox }
208
                     className = { classes.checkbox }
185
-                    disabled = { followMeActive }
209
+                    disabled = { followMeActive || followMeRecorderActive }
186
                     label = { t('settings.followMe') }
210
                     label = { t('settings.followMe') }
187
                     name = 'follow-me'
211
                     name = 'follow-me'
188
                     onChange = { this._onFollowMeEnabledChanged } />
212
                     onChange = { this._onFollowMeEnabledChanged } />
213
+                <Checkbox
214
+                    checked = { followMeRecorderEnabled && !followMeRecorderActive }
215
+                    className = { classes.checkbox }
216
+                    disabled = { followMeRecorderActive || followMeActive }
217
+                    label = { t('settings.followMeRecorder') }
218
+                    name = 'follow-me-recorder'
219
+                    onChange = { this._onFollowMeRecorderEnabledChanged } />
189
                 { !disableReactionsModeration
220
                 { !disableReactionsModeration
190
                         && <Checkbox
221
                         && <Checkbox
191
                             checked = { startReactionsMuted }
222
                             checked = { startReactionsMuted }

+ 1
- 0
react/features/settings/components/web/SettingsDialog.tsx View File

253
                 return {
253
                 return {
254
                     ...newProps,
254
                     ...newProps,
255
                     followMeEnabled: tabState?.followMeEnabled,
255
                     followMeEnabled: tabState?.followMeEnabled,
256
+                    followMeRecorderEnabled: tabState?.followMeRecorderEnabled,
256
                     startAudioMuted: tabState?.startAudioMuted,
257
                     startAudioMuted: tabState?.startAudioMuted,
257
                     startVideoMuted: tabState?.startVideoMuted,
258
                     startVideoMuted: tabState?.startVideoMuted,
258
                     startReactionsMuted: tabState?.startReactionsMuted
259
                     startReactionsMuted: tabState?.startReactionsMuted

+ 5
- 1
react/features/settings/functions.any.ts View File

9
 import { getHideSelfView } from '../base/settings/functions.any';
9
 import { getHideSelfView } from '../base/settings/functions.any';
10
 import { parseStandardURIString } from '../base/util/uri';
10
 import { parseStandardURIString } from '../base/util/uri';
11
 import { isStageFilmstripEnabled } from '../filmstrip/functions';
11
 import { isStageFilmstripEnabled } from '../filmstrip/functions';
12
-import { isFollowMeActive } from '../follow-me/functions';
12
+import { isFollowMeActive, isFollowMeRecorderActive } from '../follow-me/functions';
13
 import { isPrejoinEnabledInConfig } from '../prejoin/functions';
13
 import { isPrejoinEnabledInConfig } from '../prejoin/functions';
14
 import { isReactionsEnabled } from '../reactions/functions.any';
14
 import { isReactionsEnabled } from '../reactions/functions.any';
15
 import { iAmVisitor } from '../visitors/functions';
15
 import { iAmVisitor } from '../visitors/functions';
133
     const {
133
     const {
134
         conference,
134
         conference,
135
         followMeEnabled,
135
         followMeEnabled,
136
+        followMeRecorderEnabled,
136
         startAudioMutedPolicy,
137
         startAudioMutedPolicy,
137
         startVideoMutedPolicy,
138
         startVideoMutedPolicy,
138
         startReactionsMuted
139
         startReactionsMuted
139
     } = state['features/base/conference'];
140
     } = state['features/base/conference'];
140
     const { disableReactionsModeration } = state['features/base/config'];
141
     const { disableReactionsModeration } = state['features/base/config'];
141
     const followMeActive = isFollowMeActive(state);
142
     const followMeActive = isFollowMeActive(state);
143
+    const followMeRecorderActive = isFollowMeRecorderActive(state);
142
     const showModeratorSettings = shouldShowModeratorSettings(state);
144
     const showModeratorSettings = shouldShowModeratorSettings(state);
143
 
145
 
144
     // The settings sections to display.
146
     // The settings sections to display.
147
         disableReactionsModeration: Boolean(disableReactionsModeration),
149
         disableReactionsModeration: Boolean(disableReactionsModeration),
148
         followMeActive: Boolean(conference && followMeActive),
150
         followMeActive: Boolean(conference && followMeActive),
149
         followMeEnabled: Boolean(conference && followMeEnabled),
151
         followMeEnabled: Boolean(conference && followMeEnabled),
152
+        followMeRecorderActive: Boolean(conference && followMeRecorderActive),
153
+        followMeRecorderEnabled: Boolean(conference && followMeRecorderEnabled),
150
         startReactionsMuted: Boolean(conference && startReactionsMuted),
154
         startReactionsMuted: Boolean(conference && startReactionsMuted),
151
         startAudioMuted: Boolean(conference && startAudioMutedPolicy),
155
         startAudioMuted: Boolean(conference && startAudioMutedPolicy),
152
         startVideoMuted: Boolean(conference && startVideoMutedPolicy)
156
         startVideoMuted: Boolean(conference && startVideoMutedPolicy)

Loading…
Cancel
Save