ソースを参照

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年前
コミット
085e6dd3b9
コミッターのメールアドレスに関連付けられたアカウントが存在しません

+ 1
- 0
lang/main.json ファイルの表示

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

+ 14
- 3
react/features/base/conference/actionTypes.ts ファイルの表示

@@ -56,7 +56,7 @@ export const CONFERENCE_LEFT = 'CONFERENCE_LEFT';
56 56
 /**
57 57
  * The type of (redux) action which signals that the conference is out of focus.
58 58
  * For example, if the user navigates to the Chat screen.
59
- * 
59
+ *
60 60
  * {
61 61
  *      type: CONFERENCE_BLURRED,
62 62
  * }
@@ -65,7 +65,7 @@ export const CONFERENCE_BLURRED = 'CONFERENCE_BLURRED';
65 65
 
66 66
 /**
67 67
  * The type of (redux) action which signals that the conference is in focus.
68
- * 
68
+ *
69 69
  * {
70 70
  *      type: CONFERENCE_FOCUSED,
71 71
  * }
@@ -258,6 +258,17 @@ export const SEND_TONES = 'SEND_TONES';
258 258
  */
259 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 273
  * The type of (redux) action which sets the obfuscated room name.
263 274
  *
@@ -338,7 +349,7 @@ export const SET_START_MUTED_POLICY = 'SET_START_MUTED_POLICY';
338 349
 
339 350
 /**
340 351
  * The type of (redux) action which updates the assumed bandwidth bps.
341
- * 
352
+ *
342 353
  * {
343 354
  *      type: SET_ASSUMED_BANDWIDTH_BPS,
344 355
  *      assumedBandwidthBps: number

+ 17
- 0
react/features/base/conference/actions.any.ts ファイルの表示

@@ -61,6 +61,7 @@ import {
61 61
     SEND_TONES,
62 62
     SET_ASSUMED_BANDWIDTH_BPS,
63 63
     SET_FOLLOW_ME,
64
+    SET_FOLLOW_ME_RECORDER,
64 65
     SET_OBFUSCATED_ROOM,
65 66
     SET_PASSWORD,
66 67
     SET_PASSWORD_FAILED,
@@ -847,6 +848,22 @@ export function setFollowMe(enabled: boolean) {
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 868
  * Enables or disables the Mute reaction sounds feature.
852 869
  *

+ 8
- 0
react/features/base/conference/reducer.ts ファイルの表示

@@ -26,6 +26,7 @@ import {
26 26
     P2P_STATUS_CHANGED,
27 27
     SET_ASSUMED_BANDWIDTH_BPS,
28 28
     SET_FOLLOW_ME,
29
+    SET_FOLLOW_ME_RECORDER,
29 30
     SET_OBFUSCATED_ROOM,
30 31
     SET_PASSWORD,
31 32
     SET_PENDING_SUBJECT_CHANGE,
@@ -160,6 +161,7 @@ export interface IConferenceState {
160 161
     e2eeSupported?: boolean;
161 162
     error?: Error;
162 163
     followMeEnabled?: boolean;
164
+    followMeRecorderEnabled?: boolean;
163 165
     joining?: IJitsiConference;
164 166
     leaving?: IJitsiConference;
165 167
     lobbyWaitingForHost?: boolean;
@@ -252,6 +254,12 @@ ReducerRegistry.register<IConferenceState>('features/base/conference',
252 254
         case SET_FOLLOW_ME:
253 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 263
         case SET_START_REACTIONS_MUTED:
256 264
             return set(state, 'startReactionsMuted', action.muted);
257 265
 

+ 6
- 3
react/features/follow-me/actions.ts ファイルの表示

@@ -7,15 +7,18 @@ import {
7 7
  * Sets the current moderator id or clears it.
8 8
  *
9 9
  * @param {?string} id - The Follow Me moderator participant id.
10
+ * @param {?boolean} forRecorder - Whether this is command only for recorder.
10 11
  * @returns {{
11 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 18
     return {
17 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 ファイルの表示

@@ -13,3 +13,16 @@ export function isFollowMeActive(stateful: IStateful) {
13 13
 
14 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 ファイルの表示

@@ -128,7 +128,7 @@ function _onFollowMeCommand(attributes: any = {}, id: string, store: IStore) {
128 128
     }
129 129
 
130 130
     if (!isFollowMeActive(state)) {
131
-        store.dispatch(setFollowMeModerator(id));
131
+        store.dispatch(setFollowMeModerator(id, attributes.recorder));
132 132
     }
133 133
 
134 134
     // just a command that follow me was turned off
@@ -138,6 +138,11 @@ function _onFollowMeCommand(attributes: any = {}, id: string, store: IStore) {
138 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 146
     const oldState = state['features/follow-me'].state || {};
142 147
 
143 148
     store.dispatch(setFollowMeState(attributes));

+ 5
- 1
react/features/follow-me/reducer.ts ファイルの表示

@@ -8,6 +8,7 @@ import {
8 8
 
9 9
 export interface IFollowMeState {
10 10
     moderator?: string;
11
+    recorder?: boolean;
11 12
     state?: {
12 13
         [key: string]: string;
13 14
     };
@@ -24,9 +25,12 @@ ReducerRegistry.register<IFollowMeState>(
24 25
         case SET_FOLLOW_ME_MODERATOR: {
25 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 31
                 // clear the state if feature becomes disabled
29 32
                 newState = set(newState, 'state', undefined);
33
+                newState = set(newState, 'recorder', undefined);
30 34
             }
31 35
 
32 36
             return newState;

+ 1
- 0
react/features/follow-me/subscriber.ts ファイルの表示

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

+ 5
- 0
react/features/settings/actions.web.ts ファイルの表示

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

+ 22
- 1
react/features/settings/components/native/ModeratorSection.tsx ファイルの表示

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

+ 32
- 1
react/features/settings/components/web/ModeratorTab.tsx ファイルの表示

@@ -34,6 +34,16 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
34 34
      */
35 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 48
      * Whether or not the user has selected the Start Audio Muted feature to be
39 49
      * enabled.
@@ -92,6 +102,7 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
92 102
         this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
93 103
         this._onStartReactionsMutedChanged = this._onStartReactionsMutedChanged.bind(this);
94 104
         this._onFollowMeEnabledChanged = this._onFollowMeEnabledChanged.bind(this);
105
+        this._onFollowMeRecorderEnabledChanged = this._onFollowMeRecorderEnabledChanged.bind(this);
95 106
     }
96 107
 
97 108
     /**
@@ -142,6 +153,17 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
142 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 168
      * Implements React's {@link Component#render()}.
147 169
      *
@@ -153,6 +175,8 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
153 175
             disableReactionsModeration,
154 176
             followMeActive,
155 177
             followMeEnabled,
178
+            followMeRecorderActive,
179
+            followMeRecorderEnabled,
156 180
             startAudioMuted,
157 181
             startVideoMuted,
158 182
             startReactionsMuted,
@@ -182,10 +206,17 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
182 206
                 <Checkbox
183 207
                     checked = { followMeEnabled && !followMeActive }
184 208
                     className = { classes.checkbox }
185
-                    disabled = { followMeActive }
209
+                    disabled = { followMeActive || followMeRecorderActive }
186 210
                     label = { t('settings.followMe') }
187 211
                     name = 'follow-me'
188 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 220
                 { !disableReactionsModeration
190 221
                         && <Checkbox
191 222
                             checked = { startReactionsMuted }

+ 1
- 0
react/features/settings/components/web/SettingsDialog.tsx ファイルの表示

@@ -253,6 +253,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
253 253
                 return {
254 254
                     ...newProps,
255 255
                     followMeEnabled: tabState?.followMeEnabled,
256
+                    followMeRecorderEnabled: tabState?.followMeRecorderEnabled,
256 257
                     startAudioMuted: tabState?.startAudioMuted,
257 258
                     startVideoMuted: tabState?.startVideoMuted,
258 259
                     startReactionsMuted: tabState?.startReactionsMuted

+ 5
- 1
react/features/settings/functions.any.ts ファイルの表示

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

読み込み中…
キャンセル
保存