浏览代码

feat: Change the screenshare capture fps from UI. (#9438)

* feat: Change the screenshare capture fps from UI.
Add the ability to change the capture frame rate for screenshare from the UI. The fps becomes effective only on screen shares that are started after the setting is changed.

* squash: add missing JSDOCs and translations for frames-per-second.
j8
Jaya Allamsetty 4 年前
父节点
当前提交
765fbe5e1d
没有帐户链接到提交者的电子邮件

+ 18
- 6
css/modals/settings/_settings.scss 查看文件

@@ -36,6 +36,12 @@
36 36
 
37 37
     .calendar-tab,
38 38
     .more-tab,
39
+    .box {
40
+        display: flex;
41
+        justify-content: space-between;
42
+        width: 100%;
43
+    }
44
+
39 45
     .profile-edit {
40 46
         display: flex;
41 47
         width: 100%;
@@ -45,16 +51,22 @@
45 51
         flex: 1;
46 52
     }
47 53
     .settings-sub-pane {
48
-        flex-grow: 1;
54
+        flex: 1;
49 55
     }
50 56
 
51
-    .profile-edit-field {
52
-        margin-right: 20px;
57
+    .settings-sub-pane .right {
58
+        flex: 1;
59
+    }
60
+    .settings-sub-pane .left {
61
+        flex: 1;
53 62
     }
54 63
 
55
-    .language-settings {
56
-        max-width: 50%;
57
-        width: 35%;
64
+    .settings-sub-pane-element {
65
+        text-align: left;
66
+        flex: 1;
67
+    }
68
+    .profile-edit-field {
69
+        margin-right: 20px;
58 70
     }
59 71
 
60 72
     .calendar-tab {

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

@@ -719,8 +719,12 @@
719 719
             "signedIn": "Currently accessing calendar events for {{email}}. Click the Disconnect button below to stop accessing calendar events.",
720 720
             "title": "Calendar"
721 721
         },
722
+        "desktopShareFramerate": "Desktop sharing frame rate",
723
+        "desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
724
+        "desktopShareHighFpsWarning": "A higher frame rate for desktop sharing might affect your bandwidth. You need to restart the screen share for the new settings to take effect.",
722 725
         "devices": "Devices",
723 726
         "followMe": "Everyone follows me",
727
+        "framesPerSecond": "frames-per-second",
724 728
         "language": "Language",
725 729
         "loggedIn": "Logged in as {{name}}",
726 730
         "microphones": "Microphones",

+ 1
- 0
react/features/app/middlewares.web.js 查看文件

@@ -13,6 +13,7 @@ import '../old-client-notification/middleware';
13 13
 import '../power-monitor/middleware';
14 14
 import '../prejoin/middleware';
15 15
 import '../remote-control/middleware';
16
+import '../screen-share/middleware';
16 17
 import '../shared-video/middleware';
17 18
 import '../talk-while-muted/middleware';
18 19
 import '../virtual-background/middleware';

+ 1
- 0
react/features/app/reducers.web.js 查看文件

@@ -11,6 +11,7 @@ import '../participants-pane/reducer';
11 11
 import '../power-monitor/reducer';
12 12
 import '../prejoin/reducer';
13 13
 import '../remote-control/reducer';
14
+import '../screen-share/reducer';
14 15
 import '../screenshot-capture/reducer';
15 16
 import '../shared-video/reducer';
16 17
 import '../talk-while-muted/reducer';

+ 8
- 0
react/features/screen-share/actionTypes.js 查看文件

@@ -10,3 +10,11 @@
10 10
  */
11 11
 export const SET_SCREEN_AUDIO_SHARE_STATE = 'SET_SCREEN_AUDIO_SHARE_STATE';
12 12
 
13
+/**
14
+ * Type of action which sets the capture frame rate for screenshare.
15
+ * {
16
+ *      type: SET_SCREENSHARE_CAPTURE_FRAME_RATE,
17
+ *      captureFrameRate: number
18
+ * }
19
+ */
20
+export const SET_SCREENSHARE_CAPTURE_FRAME_RATE = 'SET_SCREENSHARE_CAPTURE_FRAME_RATE';

+ 17
- 1
react/features/screen-share/actions.js 查看文件

@@ -1,6 +1,6 @@
1 1
 // @flow
2 2
 
3
-import { SET_SCREEN_AUDIO_SHARE_STATE } from './actionTypes';
3
+import { SET_SCREEN_AUDIO_SHARE_STATE, SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
4 4
 
5 5
 /**
6 6
  * Updates the current known status of the shared video.
@@ -17,3 +17,19 @@ export function setScreenAudioShareState(isSharingAudio: boolean) {
17 17
         isSharingAudio
18 18
     };
19 19
 }
20
+
21
+/**
22
+ * Updates the capture frame rate for screenshare in redux.
23
+ *
24
+ * @param {number} captureFrameRate - The frame rate to be used for screenshare.
25
+ * @returns {{
26
+ *      type: SET_SCREENSHARE_CAPTURE_FRAME_RATE,
27
+ *      captureFrameRate: number
28
+ * }}
29
+ */
30
+export function setScreenshareFramerate(captureFrameRate: number) {
31
+    return {
32
+        type: SET_SCREENSHARE_CAPTURE_FRAME_RATE,
33
+        captureFrameRate
34
+    };
35
+}

+ 5
- 0
react/features/screen-share/logger.js 查看文件

@@ -0,0 +1,5 @@
1
+// @flow
2
+
3
+import { getLogger } from '../base/logging/functions';
4
+
5
+export default getLogger('features/screen-share');

+ 57
- 0
react/features/screen-share/middleware.js 查看文件

@@ -0,0 +1,57 @@
1
+// @flow
2
+
3
+import { CONFERENCE_JOINED } from '../base/conference';
4
+import { MiddlewareRegistry } from '../base/redux';
5
+
6
+import { SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
7
+import logger from './logger';
8
+
9
+/**
10
+ * Implements the middleware of the feature screen-share.
11
+ *
12
+ * @param {Store} store - The redux store.
13
+ * @returns {Function}
14
+ */
15
+MiddlewareRegistry.register(store => next => action => {
16
+    const result = next(action);
17
+
18
+    switch (action.type) {
19
+    case CONFERENCE_JOINED: {
20
+        _setScreenshareCaptureFps(store);
21
+        break;
22
+    }
23
+    case SET_SCREENSHARE_CAPTURE_FRAME_RATE: {
24
+        const { captureFrameRate } = action;
25
+
26
+        _setScreenshareCaptureFps(store, captureFrameRate);
27
+        break;
28
+    }
29
+    }
30
+
31
+    return result;
32
+});
33
+
34
+/**
35
+ * Sets the capture frame rate for screenshare.
36
+ *
37
+ * @param {Store} store - The redux store.
38
+ * @param {number} frameRate - Frame rate to be configured.
39
+ * @private
40
+ * @returns {void}
41
+ */
42
+function _setScreenshareCaptureFps(store, frameRate) {
43
+    const state = store.getState();
44
+    const { conference } = state['features/base/conference'];
45
+    const { captureFrameRate } = state['features/screen-share'];
46
+    const screenShareFps = frameRate ?? captureFrameRate;
47
+
48
+    if (!conference) {
49
+        return;
50
+    }
51
+
52
+    if (screenShareFps) {
53
+        logger.debug(`Setting screenshare capture frame rate as ${screenShareFps}`);
54
+        conference.setDesktopSharingFrameRate(screenShareFps);
55
+    }
56
+
57
+}

+ 8
- 2
react/features/screen-share/reducer.js 查看文件

@@ -1,13 +1,13 @@
1 1
 
2 2
 import { ReducerRegistry } from '../base/redux';
3 3
 
4
-import { SET_SCREEN_AUDIO_SHARE_STATE } from './actionTypes';
4
+import { SET_SCREEN_AUDIO_SHARE_STATE, SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
5 5
 
6 6
 /**
7 7
  * Reduces the Redux actions of the feature features/screen-share.
8 8
  */
9 9
 ReducerRegistry.register('features/screen-share', (state = {}, action) => {
10
-    const { isSharingAudio } = action;
10
+    const { captureFrameRate, isSharingAudio } = action;
11 11
 
12 12
     switch (action.type) {
13 13
     case SET_SCREEN_AUDIO_SHARE_STATE:
@@ -16,6 +16,12 @@ ReducerRegistry.register('features/screen-share', (state = {}, action) => {
16 16
             isSharingAudio
17 17
         };
18 18
 
19
+    case SET_SCREENSHARE_CAPTURE_FRAME_RATE:
20
+        return {
21
+            ...state,
22
+            captureFrameRate
23
+        };
24
+
19 25
     default:
20 26
         return state;
21 27
     }

+ 7
- 0
react/features/settings/actions.js 查看文件

@@ -5,6 +5,7 @@ import { openDialog } from '../base/dialog';
5 5
 import { i18next } from '../base/i18n';
6 6
 import { updateSettings } from '../base/settings';
7 7
 import { setPrejoinPageVisibility } from '../prejoin/actions';
8
+import { setScreenshareFramerate } from '../screen-share/actions';
8 9
 
9 10
 import {
10 11
     SET_AUDIO_SETTINGS_VISIBILITY,
@@ -99,6 +100,12 @@ export function submitMoreTab(newState: Object): Function {
99 100
         if (newState.currentLanguage !== currentState.currentLanguage) {
100 101
             i18next.changeLanguage(newState.currentLanguage);
101 102
         }
103
+
104
+        if (newState.currentFramerate !== currentState.currentFramerate) {
105
+            const frameRate = parseInt(newState.currentFramerate, 10);
106
+
107
+            dispatch(setScreenshareFramerate(frameRate));
108
+        }
102 109
     };
103 110
 }
104 111
 

+ 151
- 32
react/features/settings/components/web/MoreTab.js 查看文件

@@ -11,6 +11,7 @@ import { AbstractDialogTab } from '../../../base/dialog';
11 11
 import type { Props as AbstractDialogTabProps } from '../../../base/dialog';
12 12
 import { translate } from '../../../base/i18n';
13 13
 import TouchmoveHack from '../../../chat/components/web/TouchmoveHack';
14
+import { SS_DEFAULT_FRAME_RATE } from '../../constants';
14 15
 
15 16
 /**
16 17
  * The type of the React {@code Component} props of {@link MoreTab}.
@@ -18,12 +19,22 @@ import TouchmoveHack from '../../../chat/components/web/TouchmoveHack';
18 19
 export type Props = {
19 20
     ...$Exact<AbstractDialogTabProps>,
20 21
 
22
+    /**
23
+     * The currently selected desktop share frame rate in the frame rate select dropdown.
24
+     */
25
+     currentFramerate: string,
26
+
21 27
     /**
22 28
      * The currently selected language to display in the language select
23 29
      * dropdown.
24 30
      */
25 31
     currentLanguage: string,
26 32
 
33
+    /**
34
+     * All available desktop capture frame rates.
35
+     */
36
+    desktopShareFramerates: Array<number>,
37
+
27 38
     /**
28 39
      * Whether or not follow me is currently active (enabled by some other participant).
29 40
      */
@@ -59,7 +70,6 @@ export type Props = {
59 70
      */
60 71
     showPrejoinPage: boolean,
61 72
 
62
-
63 73
     /**
64 74
      * Whether or not the user has selected the Start Audio Muted feature to be
65 75
      * enabled.
@@ -83,6 +93,11 @@ export type Props = {
83 93
  */
84 94
 type State = {
85 95
 
96
+    /**
97
+     * Whether or not the desktop share frame rate select dropdown is open.
98
+     */
99
+     isFramerateSelectOpen: boolean,
100
+
86 101
     /**
87 102
      * Whether or not the language select dropdown is open.
88 103
      */
@@ -105,12 +120,14 @@ class MoreTab extends AbstractDialogTab<Props, State> {
105 120
         super(props);
106 121
 
107 122
         this.state = {
123
+            isFramerateSelectOpen: false,
108 124
             isLanguageSelectOpen: false
109 125
         };
110 126
 
111 127
         // Bind event handler so it is only bound once for every instance.
112
-        this._onLanguageDropdownOpenChange
113
-            = this._onLanguageDropdownOpenChange.bind(this);
128
+        this._onFramerateDropdownOpenChange = this._onFramerateDropdownOpenChange.bind(this);
129
+        this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this);
130
+        this._onLanguageDropdownOpenChange = this._onLanguageDropdownOpenChange.bind(this);
114 131
         this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this);
115 132
         this._onStartAudioMutedChanged = this._onStartAudioMutedChanged.bind(this);
116 133
         this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
@@ -126,25 +143,40 @@ class MoreTab extends AbstractDialogTab<Props, State> {
126 143
      * @returns {ReactElement}
127 144
      */
128 145
     render() {
129
-        const { showModeratorSettings, showLanguageSettings, showPrejoinSettings } = this.props;
130 146
         const content = [];
131 147
 
132
-        if (showPrejoinSettings) {
133
-            content.push(this._renderPrejoinScreenSettings());
134
-        }
148
+        content.push(this._renderSettingsLeft());
149
+        content.push(this._renderSettingsRight());
135 150
 
136
-        content.push(this._renderKeyboardShortcutCheckbox());
151
+        return <div className = 'more-tab box'>{ content }</div>;
152
+    }
137 153
 
154
+    _onFramerateDropdownOpenChange: (Object) => void;
138 155
 
139
-        if (showModeratorSettings) {
140
-            content.push(this._renderModeratorSettings());
141
-        }
156
+    /**
157
+     * Callback invoked to toggle display of the desktop share framerate select dropdown.
158
+     *
159
+     * @param {Object} event - The event for opening or closing the dropdown.
160
+     * @private
161
+     * @returns {void}
162
+     */
163
+    _onFramerateDropdownOpenChange({ isOpen }) {
164
+        this.setState({ isFramerateSelectOpen: isOpen });
165
+    }
166
+
167
+    _onFramerateItemSelect: (Object) => void;
142 168
 
143
-        if (showLanguageSettings) {
144
-            content.push(this._renderLangaugeSelect());
145
-        }
169
+    /**
170
+     * Callback invoked to select a frame rate from the select dropdown.
171
+     *
172
+     * @param {Object} e - The key event to handle.
173
+     * @private
174
+     * @returns {void}
175
+     */
176
+    _onFramerateItemSelect(e) {
177
+        const frameRate = e.currentTarget.getAttribute('data-framerate');
146 178
 
147
-        return <div className = 'more-tab'>{ content }</div>;
179
+        super._onChange({ currentFramerate: frameRate });
148 180
     }
149 181
 
150 182
     _onLanguageDropdownOpenChange: (Object) => void;
@@ -246,13 +278,89 @@ class MoreTab extends AbstractDialogTab<Props, State> {
246 278
         super._onChange({ keyboardShortcutEnable: checked });
247 279
     }
248 280
 
281
+    /**
282
+     * Returns the React Element for the desktop share frame rate dropdown.
283
+     *
284
+     * @returns {ReactElement}
285
+     */
286
+    _renderFramerateSelect() {
287
+        const { currentFramerate, desktopShareFramerates, t } = this.props;
288
+        const frameRateItems = desktopShareFramerates.map(frameRate => (
289
+            <DropdownItem
290
+                data-framerate = { frameRate }
291
+                key = { frameRate }
292
+                onClick = { this._onFramerateItemSelect }>
293
+                { `${frameRate} ${t('settings.framesPerSecond')}` }
294
+            </DropdownItem>));
295
+
296
+        return (
297
+            <div
298
+                className = 'settings-sub-pane-element'
299
+                key = 'frameRate'>
300
+                <h2 className = 'mock-atlaskit-label'>
301
+                    { t('settings.desktopShareFramerate') }
302
+                </h2>
303
+                <div className = 'dropdown-menu'>
304
+                    <TouchmoveHack isModal = { true }>
305
+                        <DropdownMenu
306
+                            isOpen = { this.state.isFramerateSelectOpen }
307
+                            onOpenChange = { this._onFramerateDropdownOpenChange }
308
+                            shouldFitContainer = { true }
309
+                            trigger = { currentFramerate
310
+                                ? `${currentFramerate} ${t('settings.framesPerSecond')}`
311
+                                : '' }
312
+                            triggerButtonProps = {{
313
+                                shouldFitContainer: true
314
+                            }}
315
+                            triggerType = 'button'>
316
+                            <DropdownItemGroup>
317
+                                { frameRateItems }
318
+                            </DropdownItemGroup>
319
+                        </DropdownMenu>
320
+                    </TouchmoveHack>
321
+                </div>
322
+                <div
323
+                    className = 'mock-atlaskit-label'>
324
+                    { parseInt(currentFramerate, 10) > SS_DEFAULT_FRAME_RATE
325
+                        ? t('settings.desktopShareHighFpsWarning')
326
+                        : t('settings.desktopShareWarning') }
327
+                </div>
328
+            </div>
329
+        );
330
+    }
331
+
332
+    /**
333
+     * Returns the React Element for keyboardShortcut settings.
334
+     *
335
+     * @private
336
+     * @returns {ReactElement}
337
+     */
338
+    _renderKeyboardShortcutCheckbox() {
339
+        const { t } = this.props;
340
+
341
+        return (
342
+            <div
343
+                className = 'settings-sub-pane-element'
344
+                key = 'keyboard-shortcut'>
345
+                <h2 className = 'mock-atlaskit-label'>
346
+                    { t('keyboardShortcuts.keyboardShortcuts') }
347
+                </h2>
348
+                <Checkbox
349
+                    isChecked = { keyboardShortcut.getEnabled() }
350
+                    label = { t('prejoin.keyboardShortcuts') }
351
+                    name = 'enable-keyboard-shortcuts'
352
+                    onChange = { this._onKeyboardShortcutEnableChanged } />
353
+            </div>
354
+        );
355
+    }
356
+
249 357
     /**
250 358
      * Returns the menu item for changing displayed language.
251 359
      *
252 360
      * @private
253 361
      * @returns {ReactElement}
254 362
      */
255
-    _renderLangaugeSelect() {
363
+    _renderLanguageSelect() {
256 364
         const {
257 365
             currentLanguage,
258 366
             languages,
@@ -270,7 +378,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
270 378
 
271 379
         return (
272 380
             <div
273
-                className = 'settings-sub-pane language-settings'
381
+                className = 'settings-sub-pane-element'
274 382
                 key = 'language'>
275 383
                 <h2 className = 'mock-atlaskit-label'>
276 384
                     { t('settings.language') }
@@ -315,7 +423,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
315 423
 
316 424
         return (
317 425
             <div
318
-                className = 'settings-sub-pane'
426
+                className = 'settings-sub-pane-element'
319 427
                 key = 'moderator'>
320 428
                 <h2 className = 'mock-atlaskit-label'>
321 429
                     { t('settings.moderator') }
@@ -351,7 +459,7 @@ class MoreTab extends AbstractDialogTab<Props, State> {
351 459
 
352 460
         return (
353 461
             <div
354
-                className = 'settings-sub-pane'
462
+                className = 'settings-sub-pane-element'
355 463
                 key = 'prejoin-screen'>
356 464
                 <h2 className = 'mock-atlaskit-label'>
357 465
                     { t('prejoin.premeeting') }
@@ -366,26 +474,37 @@ class MoreTab extends AbstractDialogTab<Props, State> {
366 474
     }
367 475
 
368 476
     /**
369
-     * Returns the React Element for keyboardShortcut settings.
477
+     * Returns the React element that needs to be displayed on the right half of the more tabs.
370 478
      *
371 479
      * @private
372 480
      * @returns {ReactElement}
373 481
      */
374
-    _renderKeyboardShortcutCheckbox() {
375
-        const { t } = this.props;
482
+    _renderSettingsRight() {
483
+        const { showLanguageSettings } = this.props;
376 484
 
377 485
         return (
378 486
             <div
379
-                className = 'settings-sub-pane'
380
-                key = 'keyboard-shortcut'>
381
-                <h2 className = 'mock-atlaskit-label'>
382
-                    { t('keyboardShortcuts.keyboardShortcuts') }
383
-                </h2>
384
-                <Checkbox
385
-                    isChecked = { keyboardShortcut.getEnabled() }
386
-                    label = { t('prejoin.keyboardShortcuts') }
387
-                    name = 'enable-keyboard-shortcuts'
388
-                    onChange = { this._onKeyboardShortcutEnableChanged } />
487
+                className = 'settings-sub-pane right'>
488
+                { showLanguageSettings && this._renderLanguageSelect() }
489
+                { this._renderFramerateSelect() }
490
+            </div>
491
+        );
492
+    }
493
+
494
+    /**
495
+     * Returns the React element that needs to be displayed on the left half of the more tabs.
496
+     *
497
+     * @returns {ReactElement}
498
+     */
499
+    _renderSettingsLeft() {
500
+        const { showPrejoinSettings, showModeratorSettings } = this.props;
501
+
502
+        return (
503
+            <div
504
+                className = 'settings-sub-pane left'>
505
+                { showPrejoinSettings && this._renderPrejoinScreenSettings() }
506
+                { this._renderKeyboardShortcutCheckbox() }
507
+                { showModeratorSettings && this._renderModeratorSettings() }
389 508
             </div>
390 509
         );
391 510
     }

+ 1
- 0
react/features/settings/components/web/SettingsDialog.js 查看文件

@@ -194,6 +194,7 @@ function _mapStateToProps(state) {
194 194
 
195 195
                 return {
196 196
                     ...newProps,
197
+                    currentFramerate: tabState.currentFramerate,
197 198
                     currentLanguage: tabState.currentLanguage,
198 199
                     followMeEnabled: tabState.followMeEnabled,
199 200
                     showPrejoinPage: tabState.showPrejoinPage,

+ 10
- 0
react/features/settings/constants.js 查看文件

@@ -9,3 +9,13 @@ export const SETTINGS_TABS = {
9 9
  * View ID for the Settings modal.
10 10
  */
11 11
 export const SETTINGS_VIEW_ID = 'SETTINGS_VIEW_ID';
12
+
13
+/**
14
+ * Default frame rate to be used for capturing screenshare.
15
+ */
16
+export const SS_DEFAULT_FRAME_RATE = 5;
17
+
18
+/**
19
+ * Supported framerates to be used for capturing screenshare.
20
+ */
21
+export const SS_SUPPORTED_FRAMERATES = [ 5, 15, 30 ];

+ 5
- 0
react/features/settings/functions.js 查看文件

@@ -11,6 +11,8 @@ import { toState } from '../base/redux';
11 11
 import { parseStandardURIString } from '../base/util';
12 12
 import { isFollowMeActive } from '../follow-me';
13 13
 
14
+import { SS_DEFAULT_FRAME_RATE, SS_SUPPORTED_FRAMERATES } from './constants';
15
+
14 16
 declare var interfaceConfig: Object;
15 17
 
16 18
 /**
@@ -95,6 +97,7 @@ export function shouldShowOnlyDeviceSelection() {
95 97
  */
96 98
 export function getMoreTabProps(stateful: Object | Function) {
97 99
     const state = toState(stateful);
100
+    const framerate = state['features/screen-share'].captureFrameRate ?? SS_DEFAULT_FRAME_RATE;
98 101
     const language = i18next.language || DEFAULT_LANGUAGE;
99 102
     const {
100 103
         conference,
@@ -112,7 +115,9 @@ export function getMoreTabProps(stateful: Object | Function) {
112 115
             && isLocalParticipantModerator(state));
113 116
 
114 117
     return {
118
+        currentFramerate: framerate,
115 119
         currentLanguage: language,
120
+        desktopShareFramerates: SS_SUPPORTED_FRAMERATES,
116 121
         followMeActive: Boolean(conference && followMeActive),
117 122
         followMeEnabled: Boolean(conference && followMeEnabled),
118 123
         languages: LANGUAGES,

正在加载...
取消
保存