Browse Source

feat(self-view) Added ability to hide self view

Added config option disableSelfView. This disables it on web and native

Added button on local video menu and toggle in settings on web to change the setting
factor2
robertpin 4 years ago
parent
commit
41f11e5adb

+ 3
- 0
config.js View File

@@ -83,6 +83,9 @@ var config = {
83 83
     // Disables polls feature.
84 84
     // disablePolls: false,
85 85
 
86
+    // Disables self-view tile. (hides it from tile view and from filmstrip)
87
+    // disableSelfView: false,
88
+
86 89
     // Disables ICE/UDP by filtering out local and remote UDP candidates in
87 90
     // signalling.
88 91
     // webrtcIceUdpDisable: false,

+ 2
- 0
lang/main.json View File

@@ -608,6 +608,7 @@
608 608
         "raisedHands": "{{participantName}} and {{raisedHands}} more people",
609 609
         "screenShareNoAudio": " Share audio box was not checked in the window selection screen.",
610 610
         "screenShareNoAudioTitle": "Couldn't share system audio!",
611
+        "selfViewTitle": "You can always un-hide the self-view from settings",
611 612
         "somebody": "Somebody",
612 613
         "startSilentTitle": "You joined with no audio output!",
613 614
         "startSilentDescription": "Rejoin the meeting to enable audio",
@@ -1124,6 +1125,7 @@
1124 1125
         "domuteVideoOfOthers": "Disable camera of everyone else",
1125 1126
         "flip": "Flip",
1126 1127
         "grantModerator": "Grant Moderator Rights",
1128
+        "hideSelfView": "Hide self view",
1127 1129
         "kick": "Kick out",
1128 1130
         "moderator": "Moderator",
1129 1131
         "mute": "Participant is muted",

+ 1
- 0
react/features/base/config/configWhitelist.js View File

@@ -113,6 +113,7 @@ export default [
113 113
     'disableRemoteMute',
114 114
     'disableResponsiveTiles',
115 115
     'disableRtx',
116
+    'disableSelfView',
116 117
     'disableScreensharingVirtualBackground',
117 118
     'disableShortcuts',
118 119
     'disableShowMoreStats',

+ 6
- 0
react/features/base/config/middleware.js View File

@@ -125,6 +125,12 @@ function _setConfig({ dispatch, getState }, next, action) {
125 125
         }));
126 126
     }
127 127
 
128
+    if (action.config.disableSelfView) {
129
+        dispatch(updateSettings({
130
+            disableSelfView: true
131
+        }));
132
+    }
133
+
128 134
     dispatch(updateConfig(config));
129 135
 
130 136
     // FIXME On Web we rely on the global 'config' variable which gets altered

+ 1
- 0
react/features/base/settings/reducer.js View File

@@ -21,6 +21,7 @@ const DEFAULT_STATE = {
21 21
     disableCallIntegration: undefined,
22 22
     disableCrashReporting: undefined,
23 23
     disableP2P: undefined,
24
+    disableSelfView: false,
24 25
     displayName: undefined,
25 26
     email: undefined,
26 27
     localFlipX: true,

+ 6
- 2
react/features/filmstrip/actions.web.js View File

@@ -23,6 +23,7 @@ import {
23 23
     calculateThumbnailSizeForTileView,
24 24
     calculateThumbnailSizeForVerticalView
25 25
 } from './functions';
26
+import { getDisableSelfView } from './functions.any';
26 27
 
27 28
 export * from './actions.any';
28 29
 
@@ -78,6 +79,7 @@ export function setVerticalViewDimensions() {
78 79
     return (dispatch: Dispatch<any>, getState: Function) => {
79 80
         const state = getState();
80 81
         const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
82
+        const disableSelfView = getDisableSelfView(state);
81 83
         const thumbnails = calculateThumbnailSizeForVerticalView(clientWidth);
82 84
 
83 85
         dispatch({
@@ -87,7 +89,8 @@ export function setVerticalViewDimensions() {
87 89
                 remoteVideosContainer: {
88 90
                     width: thumbnails?.local?.width
89 91
                         + TILE_HORIZONTAL_MARGIN + STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER + SCROLL_SIZE,
90
-                    height: clientHeight - thumbnails?.local?.height - VERTICAL_FILMSTRIP_VERTICAL_MARGIN
92
+                    height: clientHeight - (disableSelfView ? 0 : thumbnails?.local?.height)
93
+                        - VERTICAL_FILMSTRIP_VERTICAL_MARGIN
91 94
                 }
92 95
             }
93 96
 
@@ -104,6 +107,7 @@ export function setHorizontalViewDimensions() {
104 107
     return (dispatch: Dispatch<any>, getState: Function) => {
105 108
         const state = getState();
106 109
         const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
110
+        const disableSelfView = getDisableSelfView(state);
107 111
         const thumbnails = calculateThumbnailSizeForHorizontalView(clientHeight);
108 112
 
109 113
         dispatch({
@@ -111,7 +115,7 @@ export function setHorizontalViewDimensions() {
111 115
             dimensions: {
112 116
                 ...thumbnails,
113 117
                 remoteVideosContainer: {
114
-                    width: clientWidth - thumbnails?.local?.width - HORIZONTAL_FILMSTRIP_MARGIN,
118
+                    width: clientWidth - (disableSelfView ? 0 : thumbnails?.local?.width) - HORIZONTAL_FILMSTRIP_MARGIN,
115 119
                     height: thumbnails?.local?.height
116 120
                         + TILE_VERTICAL_MARGIN + STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER + SCROLL_SIZE
117 121
                 }

+ 15
- 3
react/features/filmstrip/components/native/Filmstrip.js View File

@@ -9,6 +9,7 @@ import { connect } from '../../../base/redux';
9 9
 import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
10 10
 import { setVisibleRemoteParticipants } from '../../actions';
11 11
 import { isFilmstripVisible, shouldRemoteVideosBeVisible } from '../../functions';
12
+import { getDisableSelfView } from '../../functions.any';
12 13
 
13 14
 import LocalThumbnail from './LocalThumbnail';
14 15
 import Thumbnail from './Thumbnail';
@@ -31,6 +32,11 @@ type Props = {
31 32
 
32 33
     _clientHeight: number,
33 34
 
35
+    /**
36
+     * Whether or not to hide the self view.
37
+     */
38
+    _disableSelfView: boolean,
39
+
34 40
     _localParticipantId: string,
35 41
 
36 42
     /**
@@ -215,7 +221,7 @@ class Filmstrip extends PureComponent<Props> {
215 221
      * @returns {ReactElement}
216 222
      */
217 223
     render() {
218
-        const { _aspectRatio, _localParticipantId, _participants, _visible } = this.props;
224
+        const { _aspectRatio, _localParticipantId, _participants, _visible, _disableSelfView } = this.props;
219 225
 
220 226
         if (!_visible) {
221 227
             return null;
@@ -229,13 +235,15 @@ class Filmstrip extends PureComponent<Props> {
229 235
             ? width / (thumbnailWidth + (2 * margin))
230 236
             : height / (thumbnailHeight + (2 * margin))
231 237
         );
232
-        const participants = this._separateLocalThumbnail ? _participants : [ _localParticipantId, ..._participants ];
238
+        const participants = this._separateLocalThumbnail || _disableSelfView
239
+            ? _participants : [ _localParticipantId, ..._participants ];
233 240
 
234 241
         return (
235 242
             <SafeAreaView style = { filmstripStyle }>
236 243
                 {
237 244
                     this._separateLocalThumbnail
238 245
                         && !isNarrowAspectRatio
246
+                        && !_disableSelfView
239 247
                         && <LocalThumbnail />
240 248
                 }
241 249
                 <FlatList
@@ -254,7 +262,9 @@ class Filmstrip extends PureComponent<Props> {
254 262
                     viewabilityConfig = { this._viewabilityConfig }
255 263
                     windowSize = { 2 } />
256 264
                 {
257
-                    this._separateLocalThumbnail && isNarrowAspectRatio
265
+                    this._separateLocalThumbnail
266
+                        && isNarrowAspectRatio
267
+                        && !_disableSelfView
258 268
                         && <LocalThumbnail />
259 269
                 }
260 270
             </SafeAreaView>
@@ -271,6 +281,7 @@ class Filmstrip extends PureComponent<Props> {
271 281
  */
272 282
 function _mapStateToProps(state) {
273 283
     const { enabled, remoteParticipants } = state['features/filmstrip'];
284
+    const disableSelfView = getDisableSelfView(state);
274 285
     const showRemoteVideos = shouldRemoteVideosBeVisible(state);
275 286
     const responsiveUI = state['features/base/responsive-ui'];
276 287
 
@@ -278,6 +289,7 @@ function _mapStateToProps(state) {
278 289
         _aspectRatio: state['features/base/responsive-ui'].aspectRatio,
279 290
         _clientHeight: responsiveUI.clientHeight,
280 291
         _clientWidth: responsiveUI.clientWidth,
292
+        _disableSelfView: disableSelfView,
281 293
         _localParticipantId: getLocalParticipant(state)?.id,
282 294
         _participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS,
283 295
         _visible: enabled && isFilmstripVisible(state)

+ 13
- 1
react/features/filmstrip/components/native/TileView.js View File

@@ -11,6 +11,7 @@ import type { Dispatch } from 'redux';
11 11
 import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
12 12
 import { connect } from '../../../base/redux';
13 13
 import { setVisibleRemoteParticipants } from '../../actions.web';
14
+import { getDisableSelfView } from '../../functions.any';
14 15
 
15 16
 import Thumbnail from './Thumbnail';
16 17
 import styles from './styles';
@@ -30,6 +31,11 @@ type Props = {
30 31
      */
31 32
     _columns: number,
32 33
 
34
+    /**
35
+     * Whether or not to hide the self view.
36
+     */
37
+    _disableSelfView: boolean,
38
+
33 39
     /**
34 40
      * Application's viewport height.
35 41
      */
@@ -221,12 +227,16 @@ class TileView extends PureComponent<Props> {
221 227
      * @returns {Participant[]}
222 228
      */
223 229
     _getSortedParticipants() {
224
-        const { _localParticipant, _remoteParticipants } = this.props;
230
+        const { _localParticipant, _remoteParticipants, _disableSelfView } = this.props;
225 231
 
226 232
         if (!_localParticipant) {
227 233
             return EMPTY_ARRAY;
228 234
         }
229 235
 
236
+        if (_disableSelfView) {
237
+            return _remoteParticipants;
238
+        }
239
+
230 240
         return [ _localParticipant?.id, ..._remoteParticipants ];
231 241
     }
232 242
 
@@ -263,12 +273,14 @@ class TileView extends PureComponent<Props> {
263 273
 function _mapStateToProps(state) {
264 274
     const responsiveUi = state['features/base/responsive-ui'];
265 275
     const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
276
+    const disableSelfView = getDisableSelfView(state);
266 277
     const { height } = tileViewDimensions.thumbnailSize;
267 278
     const { columns } = tileViewDimensions;
268 279
 
269 280
     return {
270 281
         _aspectRatio: responsiveUi.aspectRatio,
271 282
         _columns: columns,
283
+        _disableSelfView: disableSelfView,
272 284
         _height: responsiveUi.clientHeight,
273 285
         _localParticipant: getLocalParticipant(state),
274 286
         _participantCount: getParticipantCountWithFake(state),

+ 23
- 12
react/features/filmstrip/components/web/Filmstrip.js View File

@@ -26,6 +26,7 @@ import {
26 26
     TOOLBAR_HEIGHT_MOBILE
27 27
 } from '../../constants';
28 28
 import { shouldRemoteVideosBeVisible } from '../../functions';
29
+import { getDisableSelfView } from '../../functions.any';
29 30
 
30 31
 import AudioTracksContainer from './AudioTracksContainer';
31 32
 import Thumbnail from './Thumbnail';
@@ -54,6 +55,11 @@ type Props = {
54 55
      */
55 56
     _columns: number,
56 57
 
58
+    /**
59
+     * Whether or not to hide the self view.
60
+     */
61
+    _disableSelfView: boolean,
62
+
57 63
     /**
58 64
      * The width of the filmstrip.
59 65
      */
@@ -189,7 +195,7 @@ class Filmstrip extends PureComponent <Props> {
189 195
      */
190 196
     render() {
191 197
         const filmstripStyle = { };
192
-        const { _currentLayout } = this.props;
198
+        const { _currentLayout, _disableSelfView } = this.props;
193 199
         const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
194 200
 
195 201
         switch (_currentLayout) {
@@ -214,16 +220,18 @@ class Filmstrip extends PureComponent <Props> {
214 220
                 <div
215 221
                     className = { this.props._videosClassName }
216 222
                     id = 'remoteVideos'>
217
-                    <div
218
-                        className = 'filmstrip__videos'
219
-                        id = 'filmstripLocalVideo'>
220
-                        <div id = 'filmstripLocalVideoThumbnail'>
221
-                            {
222
-                                !tileViewActive && <Thumbnail
223
-                                    key = 'local' />
224
-                            }
223
+                    {!_disableSelfView && (
224
+                        <div
225
+                            className = 'filmstrip__videos'
226
+                            id = 'filmstripLocalVideo'>
227
+                            <div id = 'filmstripLocalVideoThumbnail'>
228
+                                {
229
+                                    !tileViewActive && <Thumbnail
230
+                                        key = 'local' />
231
+                                }
232
+                            </div>
225 233
                         </div>
226
-                    </div>
234
+                    )}
227 235
                     {
228 236
                         this._renderRemoteParticipants()
229 237
                     }
@@ -301,6 +309,7 @@ class Filmstrip extends PureComponent <Props> {
301 309
      */
302 310
     _gridItemKey({ columnIndex, rowIndex }) {
303 311
         const {
312
+            _disableSelfView,
304 313
             _columns,
305 314
             _iAmRecorder,
306 315
             _remoteParticipants,
@@ -310,8 +319,8 @@ class Filmstrip extends PureComponent <Props> {
310 319
         const index = (rowIndex * _columns) + columnIndex;
311 320
 
312 321
         // When the thumbnails are reordered, local participant is inserted at index 0.
313
-        const localIndex = _thumbnailsReordered ? 0 : _remoteParticipantsLength;
314
-        const remoteIndex = _thumbnailsReordered && !_iAmRecorder ? index - 1 : index;
322
+        const localIndex = _thumbnailsReordered && !_disableSelfView ? 0 : _remoteParticipantsLength;
323
+        const remoteIndex = _thumbnailsReordered && !_iAmRecorder && !_disableSelfView ? index - 1 : index;
315 324
 
316 325
         if (index > _remoteParticipantsLength - (_iAmRecorder ? 1 : 0)) {
317 326
             return `empty-${index}`;
@@ -571,6 +580,7 @@ function _mapStateToProps(state) {
571 580
         thumbnailSize: tileViewThumbnailSize
572 581
     } = state['features/filmstrip'].tileViewDimensions;
573 582
     const _currentLayout = getCurrentLayout(state);
583
+    const disableSelfView = getDisableSelfView(state);
574 584
 
575 585
     const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
576 586
     const availableSpace = clientHeight - filmstripHeight;
@@ -624,6 +634,7 @@ function _mapStateToProps(state) {
624 634
         _className: className,
625 635
         _columns: gridDimensions.columns,
626 636
         _currentLayout,
637
+        _disableSelfView: disableSelfView,
627 638
         _filmstripHeight: remoteFilmstripHeight,
628 639
         _filmstripWidth: remoteFilmstripWidth,
629 640
         _iAmRecorder: Boolean(iAmRecorder),

+ 13
- 5
react/features/filmstrip/components/web/ThumbnailWrapper.js View File

@@ -4,6 +4,7 @@ import { shouldComponentUpdate } from 'react-window';
4 4
 
5 5
 import { connect } from '../../../base/redux';
6 6
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
7
+import { getDisableSelfView } from '../../functions.any';
7 8
 
8 9
 import Thumbnail from './Thumbnail';
9 10
 
@@ -12,6 +13,11 @@ import Thumbnail from './Thumbnail';
12 13
  */
13 14
 type Props = {
14 15
 
16
+    /**
17
+     * Whether or not to hide the self view.
18
+     */
19
+    _disableSelfView: boolean,
20
+
15 21
     /**
16 22
      * The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view.
17 23
      */
@@ -69,14 +75,14 @@ class ThumbnailWrapper extends Component<Props> {
69 75
      * @returns {ReactElement}
70 76
      */
71 77
     render() {
72
-        const { _participantID, style, _horizontalOffset = 0 } = this.props;
78
+        const { _participantID, style, _horizontalOffset = 0, _disableSelfView } = this.props;
73 79
 
74 80
         if (typeof _participantID !== 'string') {
75 81
             return null;
76 82
         }
77 83
 
78 84
         if (_participantID === 'local') {
79
-            return (
85
+            return _disableSelfView ? null : (
80 86
                 <Thumbnail
81 87
                     horizontalOffset = { _horizontalOffset }
82 88
                     key = 'local'
@@ -105,6 +111,7 @@ function _mapStateToProps(state, ownProps) {
105 111
     const { remoteParticipants } = state['features/filmstrip'];
106 112
     const remoteParticipantsLength = remoteParticipants.length;
107 113
     const { testing = {} } = state['features/base/config'];
114
+    const disableSelfView = getDisableSelfView(state);
108 115
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
109 116
 
110 117
     if (_currentLayout === LAYOUTS.TILE_VIEW) {
@@ -114,7 +121,7 @@ function _mapStateToProps(state, ownProps) {
114 121
         const index = (rowIndex * columns) + columnIndex;
115 122
         let horizontalOffset;
116 123
         const { iAmRecorder } = state['features/base/config'];
117
-        const participantsLenght = remoteParticipantsLength + (iAmRecorder ? 0 : 1);
124
+        const participantsLenght = remoteParticipantsLength + (iAmRecorder ? 0 : 1) - (disableSelfView ? 1 : 0);
118 125
 
119 126
         if (rowIndex === rows - 1) { // center the last row
120 127
             const { width: thumbnailWidth } = thumbnailSize;
@@ -130,11 +137,12 @@ function _mapStateToProps(state, ownProps) {
130 137
         }
131 138
 
132 139
         // When the thumbnails are reordered, local participant is inserted at index 0.
133
-        const localIndex = enableThumbnailReordering ? 0 : remoteParticipantsLength;
134
-        const remoteIndex = enableThumbnailReordering && !iAmRecorder ? index - 1 : index;
140
+        const localIndex = enableThumbnailReordering && !disableSelfView ? 0 : remoteParticipantsLength;
141
+        const remoteIndex = enableThumbnailReordering && !iAmRecorder && !disableSelfView ? index - 1 : index;
135 142
 
136 143
         if (!iAmRecorder && index === localIndex) {
137 144
             return {
145
+                _disableSelfView: disableSelfView,
138 146
                 _participantID: 'local',
139 147
                 _horizontalOffset: horizontalOffset
140 148
             };

+ 15
- 0
react/features/filmstrip/functions.any.js View File

@@ -1,5 +1,7 @@
1 1
 // @flow
2 2
 
3
+import { getParticipantCount } from '../base/participants';
4
+
3 5
 import { setRemoteParticipants } from './actions';
4 6
 
5 7
 /**
@@ -80,3 +82,16 @@ export function updateRemoteParticipantsOnLeave(store: Object, participantId: ?s
80 82
     reorderedParticipants.delete(participantId)
81 83
         && store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
82 84
 }
85
+
86
+/**
87
+ * Gets the disable self view flag.
88
+ *
89
+ * @param {Object} state - Redux state.
90
+ * @returns {boolean}
91
+ */
92
+export function getDisableSelfView(state: Object) {
93
+    const { disableSelfView } = state['features/base/settings'];
94
+    const participantsCount = getParticipantCount(state);
95
+
96
+    return participantsCount === 1 ? false : disableSelfView;
97
+}

+ 9
- 2
react/features/filmstrip/subscriber.web.js View File

@@ -27,8 +27,13 @@ import './subscriber.any';
27 27
  * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
28 28
  */
29 29
 StateListenerRegistry.register(
30
-    /* selector */ getParticipantCountWithFake,
31
-    /* listener */ (numberOfParticipants, store) => {
30
+    /* selector */ state => {
31
+        return {
32
+            numberOfParticipants: getParticipantCountWithFake(state),
33
+            disableSelfView: state['features/base/settings'].disableSelfView
34
+        };
35
+    },
36
+    /* listener */ (currentState, store) => {
32 37
         const state = store.getState();
33 38
 
34 39
         if (shouldDisplayTileView(state)) {
@@ -39,6 +44,8 @@ StateListenerRegistry.register(
39 44
                 store.dispatch(setTileViewDimensions(gridDimensions));
40 45
             }
41 46
         }
47
+    }, {
48
+        deepEquals: true
42 49
     });
43 50
 
44 51
 /**

+ 17
- 1
react/features/notifications/middleware.js View File

@@ -1,6 +1,6 @@
1 1
 /* @flow */
2 2
 
3
-import { getCurrentConference } from '../base/conference';
3
+import { CONFERENCE_JOINED, getCurrentConference } from '../base/conference';
4 4
 import {
5 5
     PARTICIPANT_JOINED,
6 6
     PARTICIPANT_LEFT,
@@ -12,6 +12,7 @@ import {
12 12
 } from '../base/participants';
13 13
 import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
14 14
 import { PARTICIPANTS_PANE_OPEN } from '../participants-pane/actionTypes';
15
+import { openSettingsDialog, SETTINGS_TABS } from '../settings';
15 16
 
16 17
 import {
17 18
     clearNotifications,
@@ -31,6 +32,21 @@ import { joinLeaveNotificationsDisabled } from './functions';
31 32
  */
32 33
 MiddlewareRegistry.register(store => next => action => {
33 34
     switch (action.type) {
35
+    case CONFERENCE_JOINED: {
36
+        const { dispatch, getState } = store;
37
+        const { disableSelfView } = getState()['features/base/settings'];
38
+
39
+        if (disableSelfView) {
40
+            dispatch(showNotification({
41
+                titleKey: 'notify.selfViewTitle',
42
+                customActionNameKey: [ 'settings.title' ],
43
+                customActionHandler: [ () =>
44
+                    dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
45
+                ]
46
+            }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
47
+        }
48
+        break;
49
+    }
34 50
     case PARTICIPANT_JOINED: {
35 51
         const result = next(action);
36 52
         const { participant: p } = action;

+ 17
- 0
react/features/settings/actions.js View File

@@ -1,6 +1,7 @@
1 1
 // @flow
2 2
 import { batch } from 'react-redux';
3 3
 
4
+
4 5
 import {
5 6
     setFollowMe,
6 7
     setStartMutedPolicy,
@@ -9,6 +10,7 @@ import {
9 10
 import { openDialog } from '../base/dialog';
10 11
 import { i18next } from '../base/i18n';
11 12
 import { updateSettings } from '../base/settings';
13
+import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
12 14
 import { setPrejoinPageVisibility } from '../prejoin/actions';
13 15
 import { setScreenshareFramerate } from '../screen-share/actions';
14 16
 
@@ -24,6 +26,8 @@ import {
24 26
     getSoundsTabProps
25 27
 } from './functions';
26 28
 
29
+import { SETTINGS_TABS } from '.';
30
+
27 31
 declare var APP: Object;
28 32
 
29 33
 /**
@@ -155,6 +159,19 @@ export function submitProfileTab(newState: Object): Function {
155 159
         if (newState.email !== currentState.email) {
156 160
             APP.conference.changeLocalEmail(newState.email);
157 161
         }
162
+
163
+        if (newState.disableSelfView !== currentState.disableSelfView) {
164
+            dispatch(updateSettings({ disableSelfView: newState.disableSelfView }));
165
+            if (newState.disableSelfView) {
166
+                dispatch(showNotification({
167
+                    titleKey: 'notify.selfViewTitle',
168
+                    customActionNameKey: [ 'settings.title' ],
169
+                    customActionHandler: [ () =>
170
+                        dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
171
+                    ]
172
+                }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
173
+            }
174
+        }
158 175
     };
159 176
 }
160 177
 

+ 27
- 0
react/features/settings/components/web/ProfileTab.js View File

@@ -1,6 +1,7 @@
1 1
 // @flow
2 2
 
3 3
 import Button from '@atlaskit/button/standard-button';
4
+import Checkbox from '@atlaskit/checkbox';
4 5
 import { FieldTextStateless } from '@atlaskit/field-text';
5 6
 import React from 'react';
6 7
 
@@ -32,6 +33,11 @@ export type Props = {
32 33
      */
33 34
     authLogin: string,
34 35
 
36
+    /**
37
+     * Whether or not to hide the self view.
38
+     */
39
+    disableSelfView: boolean,
40
+
35 41
     /**
36 42
      * The display name to display for the local participant.
37 43
      */
@@ -77,6 +83,7 @@ class ProfileTab extends AbstractDialogTab<Props> {
77 83
         this._onAuthToggle = this._onAuthToggle.bind(this);
78 84
         this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
79 85
         this._onEmailChange = this._onEmailChange.bind(this);
86
+        this._onChange = this._onChange.bind(this);
80 87
     }
81 88
 
82 89
     _onDisplayNameChange: (Object) => void;
@@ -105,6 +112,19 @@ class ProfileTab extends AbstractDialogTab<Props> {
105 112
         super._onChange({ email: value });
106 113
     }
107 114
 
115
+    _onChange: (Object) => void;
116
+
117
+    /**
118
+     * Changes the disable self view state.
119
+     *
120
+     * @param {Object} e - The key event to handle.
121
+     *
122
+     * @returns {void}
123
+     */
124
+    _onChange({ target }) {
125
+        super._onChange({ disableSelfView: target.checked });
126
+    }
127
+
108 128
     /**
109 129
      * Implements React's {@link Component#render()}.
110 130
      *
@@ -115,6 +135,7 @@ class ProfileTab extends AbstractDialogTab<Props> {
115 135
         const {
116 136
             authEnabled,
117 137
             displayName,
138
+            disableSelfView,
118 139
             email,
119 140
             readOnlyName,
120 141
             t
@@ -148,6 +169,12 @@ class ProfileTab extends AbstractDialogTab<Props> {
148 169
                             value = { email } />
149 170
                     </div>
150 171
                 </div>
172
+                <br />
173
+                <Checkbox
174
+                    isChecked = { disableSelfView }
175
+                    label = { t('videothumbnail.hideSelfView') }
176
+                    name = 'disableSelfView'
177
+                    onChange = { this._onChange } />
151 178
                 { authEnabled && this._renderAuth() }
152 179
             </div>
153 180
         );

+ 2
- 0
react/features/settings/functions.js View File

@@ -156,11 +156,13 @@ export function getProfileTabProps(stateful: Object | Function) {
156 156
         conference
157 157
     } = state['features/base/conference'];
158 158
     const localParticipant = getLocalParticipant(state);
159
+    const { disableSelfView } = state['features/base/settings'];
159 160
 
160 161
     return {
161 162
         authEnabled: Boolean(conference && authEnabled),
162 163
         authLogin,
163 164
         displayName: localParticipant.name,
165
+        disableSelfView: Boolean(disableSelfView),
164 166
         email: localParticipant.email,
165 167
         readOnlyName: isNameReadOnly(state)
166 168
     };

+ 5
- 1
react/features/video-layout/functions.js View File

@@ -15,6 +15,7 @@ import {
15 15
     SINGLE_COLUMN_BREAKPOINT,
16 16
     TWO_COLUMN_BREAKPOINT
17 17
 } from '../filmstrip/constants';
18
+import { getDisableSelfView } from '../filmstrip/functions.any';
18 19
 import { isVideoPlaying } from '../shared-video/functions';
19 20
 
20 21
 import { LAYOUTS } from './constants';
@@ -104,7 +105,10 @@ export function getTileViewGridDimensions(state: Object) {
104 105
     // When in tile view mode, we must discount ourselves (the local participant) because our
105 106
     // tile is not visible.
106 107
     const { iAmRecorder } = state['features/base/config'];
107
-    const numberOfParticipants = getParticipantCountWithFake(state) - (iAmRecorder ? 1 : 0);
108
+    const disableSelfView = getDisableSelfView(state);
109
+    const numberOfParticipants = getParticipantCountWithFake(state)
110
+        - (iAmRecorder ? 1 : 0)
111
+        - (disableSelfView ? 1 : 0);
108 112
 
109 113
     const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants));
110 114
     const columns = Math.min(columnsToMaintainASquare, maxColumns);

+ 120
- 0
react/features/video-menu/components/web/HideSelfViewVideoButton.js View File

@@ -0,0 +1,120 @@
1
+/* @flow */
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { translate } from '../../../base/i18n';
6
+import { connect } from '../../../base/redux';
7
+import { updateSettings } from '../../../base/settings';
8
+import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../../notifications';
9
+import { openSettingsDialog, SETTINGS_TABS } from '../../../settings';
10
+
11
+import VideoMenuButton from './VideoMenuButton';
12
+
13
+/**
14
+ * The type of the React {@code Component} props of {@link HideSelfViewVideoButton}.
15
+ */
16
+type Props = {
17
+
18
+    /**
19
+     * Whether or not to hide the self view.
20
+     */
21
+    disableSelfView: boolean,
22
+
23
+    /**
24
+     * The redux dispatch function.
25
+     */
26
+    dispatch: Function,
27
+
28
+    /**
29
+     * Click handler executed aside from the main action.
30
+     */
31
+    onClick?: Function,
32
+
33
+    /**
34
+     * Invoked to obtain translated strings.
35
+     */
36
+    t: Function
37
+};
38
+
39
+/**
40
+ * Implements a React {@link Component} which displays a button for hiding the local video.
41
+ *
42
+ * @augments Component
43
+ */
44
+class HideSelfViewVideoButton extends PureComponent<Props> {
45
+    /**
46
+     * Initializes a new {@code HideSelfViewVideoButton} instance.
47
+     *
48
+     * @param {Object} props - The read-only React Component props with which
49
+     * the new instance is to be initialized.
50
+     */
51
+    constructor(props: Props) {
52
+        super(props);
53
+
54
+        // Bind event handlers so they are only bound once for every instance.
55
+        this._onClick = this._onClick.bind(this);
56
+    }
57
+
58
+    /**
59
+     * Implements React's {@link Component#render()}.
60
+     *
61
+     * @inheritdoc
62
+     * @returns {null|ReactElement}
63
+     */
64
+    render() {
65
+        const {
66
+            t
67
+        } = this.props;
68
+
69
+        return (
70
+            <VideoMenuButton
71
+                buttonText = { t('videothumbnail.hideSelfView') }
72
+                displayClass = 'hideselflink'
73
+                id = 'hideselfviewbutton'
74
+                onClick = { this._onClick } />
75
+        );
76
+    }
77
+
78
+    _onClick: () => void;
79
+
80
+    /**
81
+     * Hides the local video.
82
+     *
83
+     * @private
84
+     * @returns {void}
85
+     */
86
+    _onClick() {
87
+        const { disableSelfView, dispatch, onClick } = this.props;
88
+
89
+        onClick && onClick();
90
+        dispatch(updateSettings({
91
+            disableSelfView: !disableSelfView
92
+        }));
93
+        if (!disableSelfView) {
94
+            dispatch(showNotification({
95
+                titleKey: 'notify.selfViewTitle',
96
+                customActionNameKey: [ 'settings.title' ],
97
+                customActionHandler: [ () =>
98
+                    dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
99
+                ]
100
+            }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
101
+        }
102
+    }
103
+}
104
+
105
+/**
106
+ * Maps (parts of) the Redux state to the associated {@code FlipLocalVideoButton}'s props.
107
+ *
108
+ * @param {Object} state - The Redux state.
109
+ * @private
110
+ * @returns {Props}
111
+ */
112
+function _mapStateToProps(state) {
113
+    const { disableSelfView } = state['features/base/config'];
114
+
115
+    return {
116
+        disableSelfView: Boolean(disableSelfView)
117
+    };
118
+}
119
+
120
+export default translate(connect(_mapStateToProps)(HideSelfViewVideoButton));

+ 2
- 0
react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js View File

@@ -19,6 +19,7 @@ import { renderConnectionStatus } from '../../actions.web';
19 19
 
20 20
 import ConnectionStatusButton from './ConnectionStatusButton';
21 21
 import FlipLocalVideoButton from './FlipLocalVideoButton';
22
+import HideSelfViewVideoButton from './HideSelfViewVideoButton';
22 23
 import VideoMenu from './VideoMenu';
23 24
 
24 25
 
@@ -131,6 +132,7 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
131 132
             : (
132 133
                 <VideoMenu id = 'localVideoMenu'>
133 134
                     <FlipLocalVideoButton onClick = { hidePopover } />
135
+                    <HideSelfViewVideoButton onClick = { hidePopover } />
134 136
                     { isMobileBrowser()
135 137
                             && <ConnectionStatusButton participantId = { _localParticipantId } />
136 138
                     }

Loading…
Cancel
Save