Просмотр исходного кода

feat: Add screenshare filmstrip (#11714)

Add new screen share layout with resizable top panel
Only enable new layout in large meetings (min 50 participants - configurable)
factor2
Robert Pintilii 3 лет назад
Родитель
Сommit
21cf7f23c2
Аккаунт пользователя с таким Email не найден

+ 10
- 2
config.js Просмотреть файл

@@ -1205,7 +1205,8 @@ var config = {
1205 1205
     //         'transcribing',
1206 1206
     //         'video-quality',
1207 1207
     //         'insecure-room',
1208
-    //         'highlight-moment'
1208
+    //         'highlight-moment',
1209
+    //         'top-panel-toggle'
1209 1210
     //     ]
1210 1211
     // },
1211 1212
 
@@ -1399,7 +1400,14 @@ var config = {
1399 1400
 
1400 1401
     //     // Disables the stage filmstrip
1401 1402
     //     // (displaying multiple participants on stage besides the vertical filmstrip)
1402
-    //     disableStageFilmstrip: false
1403
+    //     disableStageFilmstrip: false,
1404
+
1405
+    //     // Disables the top panel (only shown when a user is sharing their screen).
1406
+    //     disableTopPanel: false,
1407
+
1408
+    //     // The minimum number of participants that must be in the call for
1409
+    //     // the top panel layout to be used.
1410
+    //     minParticipantCountForTopPanel: 50
1403 1411
     // },
1404 1412
 
1405 1413
     // Tile view related config options.

+ 1
- 0
lang/main.json Просмотреть файл

@@ -1033,6 +1033,7 @@
1033 1033
     "termsView": {
1034 1034
         "header": "Terms"
1035 1035
     },
1036
+    "toggleTopPanelLabel": "Toggle top panel",
1036 1037
     "toolbar": {
1037 1038
         "Settings": "Settings",
1038 1039
         "accessibilityLabel": {

+ 6
- 6
package-lock.json Просмотреть файл

@@ -7888,9 +7888,9 @@
7888 7888
       "integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
7889 7889
     },
7890 7890
     "node_modules/debug": {
7891
-      "version": "4.3.3",
7892
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
7893
-      "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
7891
+      "version": "4.3.4",
7892
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
7893
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
7894 7894
       "dependencies": {
7895 7895
         "ms": "2.1.2"
7896 7896
       },
@@ -26086,9 +26086,9 @@
26086 26086
       "integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
26087 26087
     },
26088 26088
     "debug": {
26089
-      "version": "4.3.3",
26090
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
26091
-      "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
26089
+      "version": "4.3.4",
26090
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
26091
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
26092 26092
       "requires": {
26093 26093
         "ms": "2.1.2"
26094 26094
       }

+ 2
- 1
react/features/conference/components/constants.js Просмотреть файл

@@ -8,6 +8,7 @@ export const CONFERENCE_INFO = {
8 8
         'e2ee',
9 9
         'transcribing',
10 10
         'video-quality',
11
-        'insecure-room'
11
+        'insecure-room',
12
+        'top-panel-toggle'
12 13
     ]
13 14
 };

+ 2
- 1
react/features/conference/components/web/Conference.js Просмотреть файл

@@ -11,7 +11,7 @@ import { translate } from '../../../base/i18n';
11 11
 import { connect as reactReduxConnect } from '../../../base/redux';
12 12
 import { setColorAlpha } from '../../../base/util';
13 13
 import { Chat } from '../../../chat';
14
-import { MainFilmstrip, StageFilmstrip } from '../../../filmstrip';
14
+import { MainFilmstrip, StageFilmstrip, ScreenshareFilmstrip } from '../../../filmstrip';
15 15
 import { CalleeInfoContainer } from '../../../invite';
16 16
 import { LargeVideo } from '../../../large-video';
17 17
 import { LobbyScreen } from '../../../lobby';
@@ -239,6 +239,7 @@ class Conference extends AbstractConference<Props, *> {
239 239
                         {
240 240
                             _showPrejoin || _showLobby || (<>
241 241
                                 <StageFilmstrip />
242
+                                <ScreenshareFilmstrip />
242 243
                                 <MainFilmstrip />
243 244
                             </>)
244 245
                         }

+ 5
- 0
react/features/conference/components/web/ConferenceInfo.js Просмотреть файл

@@ -20,6 +20,7 @@ import InsecureRoomNameLabel from './InsecureRoomNameLabel';
20 20
 import ParticipantsCount from './ParticipantsCount';
21 21
 import RaisedHandsCountLabel from './RaisedHandsCountLabel';
22 22
 import SubjectText from './SubjectText';
23
+import ToggleTopPanelLabel from './ToggleTopPanelLabel';
23 24
 
24 25
 /**
25 26
  * The type of the React {@code Component} props of {@link Subject}.
@@ -82,6 +83,10 @@ const COMPONENTS = [
82 83
     {
83 84
         Component: InsecureRoomNameLabel,
84 85
         id: 'insecure-room'
86
+    },
87
+    {
88
+        Component: ToggleTopPanelLabel,
89
+        id: 'top-panel-toggle'
85 90
     }
86 91
 ];
87 92
 

+ 31
- 0
react/features/conference/components/web/ToggleTopPanelLabel.tsx Просмотреть файл

@@ -0,0 +1,31 @@
1
+import React, { useCallback } from 'react';
2
+import { useTranslation } from 'react-i18next';
3
+import { useDispatch, useSelector } from 'react-redux';
4
+
5
+// @ts-ignore
6
+import { IconMenuDown } from '../../../base/icons';
7
+// @ts-ignore
8
+import { Label } from '../../../base/label';
9
+// @ts-ignore
10
+import { Tooltip } from '../../../base/tooltip';
11
+// @ts-ignore
12
+import { setTopPanelVisible } from '../../../filmstrip/actions.web';
13
+
14
+const ToggleTopPanelLabel = () => {
15
+    const dispatch = useDispatch();
16
+    const { t } = useTranslation();
17
+    const topPanelHidden = !useSelector((state: any) => state['features/filmstrip'].topPanelVisible);
18
+    const onClick = useCallback(() => {
19
+        dispatch(setTopPanelVisible(true));
20
+    }, []);
21
+
22
+    return topPanelHidden && (<Tooltip
23
+        content={t('toggleTopPanelLabel') }
24
+        position = { 'bottom' }>
25
+        <Label
26
+            icon={IconMenuDown}
27
+            onClick = { onClick }/>
28
+    </Tooltip>);
29
+};
30
+
31
+export default ToggleTopPanelLabel;

+ 34
- 0
react/features/filmstrip/actionTypes.ts Просмотреть файл

@@ -92,6 +92,15 @@ export const SET_VOLUME = 'SET_VOLUME';
92 92
  */
93 93
 export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS';
94 94
 
95
+/**
96
+ * The type of action which sets the height for the top panel filmstrip.
97
+ * {
98
+ *      type: SET_FILMSTRIP_HEIGHT,
99
+ *      height: number
100
+ * }
101
+ */
102
+export const SET_FILMSTRIP_HEIGHT = 'SET_FILMSTRIP_HEIGHT';
103
+
95 104
 /**
96 105
  * The type of action which sets the width for the vertical filmstrip.
97 106
  * {
@@ -101,6 +110,15 @@ export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS'
101 110
  */
102 111
 export const SET_FILMSTRIP_WIDTH = 'SET_FILMSTRIP_WIDTH';
103 112
 
113
+/**
114
+ * The type of action which sets the height for the top panel filmstrip (user resized).
115
+ * {
116
+ *      type: SET_USER_FILMSTRIP_HEIGHT,
117
+ *      height: number
118
+ * }
119
+ */
120
+export const SET_USER_FILMSTRIP_HEIGHT = 'SET_USER_FILMSTRIP_HEIGHT';
121
+
104 122
 /**
105 123
  * The type of action which sets the width for the vertical filmstrip (user resized).
106 124
  * {
@@ -187,3 +205,19 @@ export const TOGGLE_PIN_STAGE_PARTICIPANT = 'TOGGLE_PIN_STAGE_PARTICIPANT';
187 205
  * }
188 206
  */
189 207
 export const CLEAR_STAGE_PARTICIPANTS = 'CLEAR_STAGE_PARTICIPANTS';
208
+
209
+/**
210
+ * The type of Redux action which sets the dimensions of the screenshare tile.
211
+ * {
212
+ *     type: SET_SCREENSHARING_TILE_DIMENSIONS
213
+ * }
214
+ */
215
+export const SET_SCREENSHARING_TILE_DIMENSIONS = 'SET_SCREENSHARING_TILE_DIMENSIONS';
216
+
217
+/**
218
+ * The type of Redux action which sets the visibility of the top panel.
219
+ * {
220
+ *     type: SET_TOP_PANEL_VISIBILITY
221
+ * }
222
+ */
223
+export const SET_TOP_PANEL_VISIBILITY = 'SET_TOP_PANEL_VISIBILITY';

+ 87
- 4
react/features/filmstrip/actions.web.js Просмотреть файл

@@ -25,7 +25,11 @@ import {
25 25
     SET_VOLUME,
26 26
     SET_MAX_STAGE_PARTICIPANTS,
27 27
     TOGGLE_PIN_STAGE_PARTICIPANT,
28
-    CLEAR_STAGE_PARTICIPANTS
28
+    CLEAR_STAGE_PARTICIPANTS,
29
+    SET_SCREENSHARING_TILE_DIMENSIONS,
30
+    SET_USER_FILMSTRIP_HEIGHT,
31
+    SET_FILMSTRIP_HEIGHT,
32
+    SET_TOP_PANEL_VISIBILITY
29 33
 } from './actionTypes';
30 34
 import {
31 35
     HORIZONTAL_FILMSTRIP_MARGIN,
@@ -33,11 +37,13 @@ import {
33 37
     SCROLL_SIZE,
34 38
     STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER,
35 39
     TILE_HORIZONTAL_MARGIN,
40
+    TILE_MIN_HEIGHT_SMALL,
36 41
     TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN,
37 42
     TILE_VERTICAL_MARGIN,
38 43
     TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
39 44
     TILE_VIEW_GRID_HORIZONTAL_MARGIN,
40 45
     TILE_VIEW_GRID_VERTICAL_MARGIN,
46
+    TOP_FILMSTRIP_HEIGHT,
41 47
     VERTICAL_FILMSTRIP_VERTICAL_MARGIN
42 48
 } from './constants';
43 49
 import {
@@ -48,6 +54,7 @@ import {
48 54
     getNumberOfPartipantsForTileView,
49 55
     getVerticalViewMaxWidth,
50 56
     isFilmstripResizable,
57
+    isStageFilmstripTopPanel,
51 58
     showGridInVerticalView
52 59
 } from './functions';
53 60
 import { isStageFilmstripAvailable } from './functions.web';
@@ -270,7 +277,7 @@ export function setStageFilmstripViewDimensions() {
270 277
         const {
271 278
             tileView = {}
272 279
         } = state['features/base/config'];
273
-        const { visible } = state['features/filmstrip'];
280
+        const { visible, topPanelHeight } = state['features/filmstrip'];
274 281
         const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
275 282
         const { numberOfVisibleTiles = MAX_ACTIVE_PARTICIPANTS } = tileView;
276 283
         const numberOfParticipants = state['features/filmstrip'].activeParticipants.length;
@@ -280,6 +287,7 @@ export function setStageFilmstripViewDimensions() {
280 287
             disableResponsiveTiles: false,
281 288
             disableTileEnlargement: false
282 289
         });
290
+        const topPanel = isStageFilmstripTopPanel(state);
283 291
 
284 292
         const {
285 293
             height,
@@ -288,12 +296,13 @@ export function setStageFilmstripViewDimensions() {
288 296
             rows
289 297
         } = calculateResponsiveTileViewDimensions({
290 298
             clientWidth: availableWidth,
291
-            clientHeight,
299
+            clientHeight: topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : clientHeight,
292 300
             disableTileEnlargement: false,
293 301
             maxColumns,
294 302
             noHorizontalContainerMargin: verticalWidth > 0,
295 303
             numberOfParticipants,
296
-            numberOfVisibleTiles
304
+            numberOfVisibleTiles,
305
+            minTileHeight: topPanel ? TILE_MIN_HEIGHT_SMALL : null
297 306
         });
298 307
         const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
299 308
         const hasScroll = clientHeight < thumbnailsTotalHeight;
@@ -368,6 +377,22 @@ export function setVolume(participantId: string, volume: number) {
368 377
     };
369 378
 }
370 379
 
380
+/**
381
+ * Sets the top filmstrip's height.
382
+ *
383
+ * @param {number} height - The new height of the filmstrip.
384
+ * @returns {{
385
+ *      type: SET_FILMSTRIP_HEIGHT,
386
+ *      height: number
387
+ * }}
388
+ */
389
+export function setFilmstripHeight(height: number) {
390
+    return {
391
+        type: SET_FILMSTRIP_HEIGHT,
392
+        height
393
+    };
394
+}
395
+
371 396
 /**
372 397
  * Sets the filmstrip's width.
373 398
  *
@@ -384,6 +409,22 @@ export function setFilmstripWidth(width: number) {
384 409
     };
385 410
 }
386 411
 
412
+/**
413
+ * Sets the filmstrip's height and the user preferred height.
414
+ *
415
+ * @param {number} height - The new height of the filmstrip.
416
+ * @returns {{
417
+ *      type: SET_USER_FILMSTRIP_WIDTH,
418
+ *      height: number
419
+ * }}
420
+ */
421
+export function setUserFilmstripHeight(height: number) {
422
+    return {
423
+        type: SET_USER_FILMSTRIP_HEIGHT,
424
+        height
425
+    };
426
+}
427
+
387 428
 /**
388 429
  * Sets the filmstrip's width and the user preferred width.
389 430
  *
@@ -490,3 +531,45 @@ export function clearStageParticipants() {
490 531
         type: CLEAR_STAGE_PARTICIPANTS
491 532
     };
492 533
 }
534
+
535
+/**
536
+ * Set the screensharing tile dimensions.
537
+ *
538
+ * @returns {Object}
539
+ */
540
+export function setScreensharingTileDimensions() {
541
+    return (dispatch: Dispatch<any>, getState: Function) => {
542
+        const state = getState();
543
+        const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
544
+        const { visible, topPanelHeight, topPanelVisible } = state['features/filmstrip'];
545
+        const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
546
+        const availableWidth = clientWidth - verticalWidth;
547
+        const topPanel = isStageFilmstripTopPanel(state) && topPanelVisible;
548
+        const availableHeight = clientHeight - (topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : 0);
549
+
550
+        dispatch({
551
+            type: SET_SCREENSHARING_TILE_DIMENSIONS,
552
+            dimensions: {
553
+                filmstripHeight: availableHeight,
554
+                filmstripWidth: availableWidth,
555
+                thumbnailSize: {
556
+                    width: availableWidth - TILE_HORIZONTAL_MARGIN,
557
+                    height: availableHeight - TILE_VERTICAL_MARGIN
558
+                }
559
+            }
560
+        });
561
+    };
562
+}
563
+
564
+/**
565
+ * Sets the visibility of the top panel.
566
+ *
567
+ * @param {boolean} visible - Whether it should be visible or not.
568
+ * @returns {Object}
569
+ */
570
+export function setTopPanelVisible(visible) {
571
+    return {
572
+        type: SET_TOP_PANEL_VISIBILITY,
573
+        visible
574
+    };
575
+}

+ 169
- 58
react/features/filmstrip/components/web/Filmstrip.js Просмотреть файл

@@ -23,20 +23,26 @@ import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.we
23 23
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
24 24
 import {
25 25
     setFilmstripVisible,
26
-    setVisibleRemoteParticipants,
26
+    setUserFilmstripHeight,
27 27
     setUserFilmstripWidth,
28
-    setUserIsResizing
28
+    setUserIsResizing,
29
+    setTopPanelVisible,
30
+    setVisibleRemoteParticipants
29 31
 } from '../../actions';
30 32
 import {
31 33
     ASPECT_RATIO_BREAKPOINT,
32 34
     DEFAULT_FILMSTRIP_WIDTH,
35
+    FILMSTRIP_TYPE,
36
+    MIN_STAGE_VIEW_HEIGHT,
33 37
     MIN_STAGE_VIEW_WIDTH,
34 38
     TILE_HORIZONTAL_MARGIN,
35
-    TILE_VERTICAL_MARGIN
39
+    TILE_VERTICAL_MARGIN,
40
+    TOP_FILMSTRIP_HEIGHT
36 41
 } from '../../constants';
37 42
 import {
38 43
     getVerticalViewMaxWidth,
39
-    shouldRemoteVideosBeVisible
44
+    shouldRemoteVideosBeVisible,
45
+    isStageFilmstripTopPanel
40 46
 } from '../../functions';
41 47
 
42 48
 import AudioTracksContainer from './AudioTracksContainer';
@@ -112,11 +118,21 @@ type Props = {
112 118
      */
113 119
     _localScreenShare: Object,
114 120
 
121
+    /**
122
+     * Whether or not the filmstrip videos should currently be displayed.
123
+     */
124
+    _mainFilmstripVisible: boolean,
125
+
115 126
     /**
116 127
      * The maximum width of the vertical filmstrip.
117 128
      */
118 129
     _maxFilmstripWidth: number,
119 130
 
131
+    /**
132
+     * The maximum height of the top panel.
133
+     */
134
+    _maxTopPanelHeight: number,
135
+
120 136
     /**
121 137
      * The participants in the call.
122 138
      */
@@ -137,11 +153,6 @@ type Props = {
137 153
      */
138 154
     _rows: number,
139 155
 
140
-    /**
141
-     * Whether or not this is the stage filmstrip.
142
-     */
143
-    _stageFilmstrip: boolean,
144
-
145 156
     /**
146 157
      * The height of the thumbnail.
147 158
      */
@@ -157,6 +168,26 @@ type Props = {
157 168
      */
158 169
     _thumbnailsReordered: Boolean,
159 170
 
171
+    /**
172
+     * Whether or not the filmstrip is top panel.
173
+     */
174
+    _topPanelFilmstrip: boolean,
175
+
176
+    /**
177
+     * The max height of the top panel.
178
+     */
179
+    _topPanelMaxHeight: number,
180
+
181
+    /**
182
+     * The height of the top panel (user resized).
183
+     */
184
+    _topPanelHeight: ?number,
185
+
186
+    /**
187
+     * Whether or not the top panel is visible.
188
+     */
189
+    _topPanelVisible: boolean,
190
+
160 191
     /**
161 192
      * The width of the vertical filmstrip (user resized).
162 193
      */
@@ -182,11 +213,6 @@ type Props = {
182 213
      */
183 214
     _videosClassName: string,
184 215
 
185
-    /**
186
-     * Whether or not the filmstrip videos should currently be displayed.
187
-     */
188
-    _visible: boolean,
189
-
190 216
     /**
191 217
      * An object containing the CSS classes.
192 218
      */
@@ -197,6 +223,11 @@ type Props = {
197 223
      */
198 224
     dispatch: Dispatch<any>,
199 225
 
226
+    /**
227
+     * The type of filmstrip to be displayed.
228
+     */
229
+    filmstripType: string,
230
+
200 231
     /**
201 232
      * Invoked to obtain translated strings.
202 233
      */
@@ -218,7 +249,12 @@ type State = {
218 249
     /**
219 250
      * Initial filmstrip width on drag handle mouse down.
220 251
      */
221
-    dragFilmstripWidth: ?number
252
+    dragFilmstripWidth: ?number,
253
+
254
+    /**
255
+     * Initial top panel height on drag handle mouse down.
256
+     */
257
+    dragFilmstripHeight: ?number
222 258
 }
223 259
 
224 260
 /**
@@ -307,25 +343,45 @@ class Filmstrip extends PureComponent <Props, State> {
307 343
             _currentLayout,
308 344
             _disableSelfView,
309 345
             _localScreenShare,
346
+            _mainFilmstripVisible,
310 347
             _resizableFilmstrip,
311
-            _stageFilmstrip,
312
-            _visible,
348
+            _topPanelFilmstrip,
349
+            _topPanelMaxHeight,
350
+            _topPanelVisible,
313 351
             _verticalViewBackground,
314 352
             _verticalViewGrid,
315 353
             _verticalViewMaxWidth,
316
-            classes
354
+            classes,
355
+            filmstripType
317 356
         } = this.props;
318 357
         const { isMouseDown } = this.state;
319 358
         const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
320 359
 
321
-        if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && _stageFilmstrip) {
322
-            if (_visible) {
360
+        if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.STAGE) {
361
+            if (_topPanelFilmstrip) {
362
+                filmstripStyle.maxHeight = `${_topPanelMaxHeight}px`;
363
+                filmstripStyle.zIndex = 1;
364
+
365
+                if (!_topPanelVisible) {
366
+                    filmstripStyle.top = `-${_topPanelMaxHeight}px`;
367
+                }
368
+            }
369
+            if (_mainFilmstripVisible) {
370
+                filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
371
+            }
372
+        } else if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
373
+            if (_mainFilmstripVisible) {
323 374
                 filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
324 375
             }
376
+            if (_topPanelVisible) {
377
+                filmstripStyle.maxHeight = `calc(100% - ${_topPanelMaxHeight}px)`;
378
+            }
379
+            filmstripStyle.bottom = 0;
380
+            filmstripStyle.top = 'auto';
325 381
         } else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
326
-            || (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && !_stageFilmstrip)) {
382
+            || (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.MAIN)) {
327 383
             filmstripStyle.maxWidth = _verticalViewMaxWidth;
328
-            if (!_visible) {
384
+            if (!_mainFilmstripVisible) {
329 385
                 filmstripStyle.right = `-${filmstripStyle.maxWidth}px`;
330 386
             }
331 387
         }
@@ -333,14 +389,17 @@ class Filmstrip extends PureComponent <Props, State> {
333 389
         let toolbar = null;
334 390
 
335 391
         if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
336
-            && _currentLayout !== LAYOUTS.TILE_VIEW && !_stageFilmstrip) {
392
+            && _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
393
+                || (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
337 394
             toolbar = this._renderToggleButton();
338 395
         }
339 396
 
340 397
         const filmstrip = (<>
341 398
             <div
342 399
                 className = { clsx(this.props._videosClassName,
343
-                    !tileViewActive && !_stageFilmstrip && !_resizableFilmstrip && 'filmstrip-hover',
400
+                    !tileViewActive && (filmstripType === FILMSTRIP_TYPE.MAIN
401
+                    || (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))
402
+                    && !_resizableFilmstrip && 'filmstrip-hover',
344 403
                     _verticalViewGrid && 'vertical-view-grid') }
345 404
                 id = 'remoteVideos'>
346 405
                 {!_disableSelfView && !_verticalViewGrid && (
@@ -348,8 +407,10 @@ class Filmstrip extends PureComponent <Props, State> {
348 407
                         className = 'filmstrip__videos'
349 408
                         id = 'filmstripLocalVideo'>
350 409
                         {
351
-                            !tileViewActive && !_stageFilmstrip && <div id = 'filmstripLocalVideoThumbnail'>
410
+                            !tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN
411
+                            && <div id = 'filmstripLocalVideoThumbnail'>
352 412
                                 <Thumbnail
413
+                                    filmstripType = { FILMSTRIP_TYPE.MAIN }
353 414
                                     key = 'local' />
354 415
                             </div>
355 416
                         }
@@ -361,10 +422,9 @@ class Filmstrip extends PureComponent <Props, State> {
361 422
                         id = 'filmstripLocalScreenShare'>
362 423
                         <div id = 'filmstripLocalScreenShareThumbnail'>
363 424
                             {
364
-                                !tileViewActive && !_stageFilmstrip && <Thumbnail
425
+                                !tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN && <Thumbnail
365 426
                                     key = 'localScreenShare'
366 427
                                     participantID = { _localScreenShare.id } />
367
-
368 428
                             }
369 429
                         </div>
370 430
                     </div>
@@ -385,11 +445,14 @@ class Filmstrip extends PureComponent <Props, State> {
385 445
                 style = { filmstripStyle }>
386 446
                 { toolbar }
387 447
                 {_resizableFilmstrip
388
-                    ? <div className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer) }>
448
+                    ? <div
449
+                        className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer,
450
+                            _topPanelFilmstrip && 'top-panel-filmstrip') }>
389 451
                         <div
390 452
                             className = { clsx('dragHandleContainer',
391 453
                                 classes.dragHandleContainer,
392
-                                isMouseDown && 'visible')
454
+                                isMouseDown && 'visible',
455
+                                _topPanelFilmstrip && 'top-panel')
393 456
                             }
394 457
                             onMouseDown = { this._onDragHandleMouseDown }>
395 458
                             <div className = { clsx(classes.dragHandle, 'dragHandle') } />
@@ -412,10 +475,13 @@ class Filmstrip extends PureComponent <Props, State> {
412 475
      * @returns {void}
413 476
      */
414 477
     _onDragHandleMouseDown(e) {
478
+        const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props;
479
+
415 480
         this.setState({
416 481
             isMouseDown: true,
417
-            mousePosition: e.clientX,
418
-            dragFilmstripWidth: this.props._verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH
482
+            mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX,
483
+            dragFilmstripWidth: _verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH,
484
+            dragFilmstripHeight: _topPanelHeight || TOP_FILMSTRIP_HEIGHT
419 485
         });
420 486
         this.props.dispatch(setUserIsResizing(true));
421 487
     }
@@ -446,16 +512,36 @@ class Filmstrip extends PureComponent <Props, State> {
446 512
      */
447 513
     _onFilmstripResize(e) {
448 514
         if (this.state.isMouseDown) {
449
-            const { dispatch, _verticalFilmstripWidth, _maxFilmstripWidth } = this.props;
450
-            const { dragFilmstripWidth, mousePosition } = this.state;
451
-            const diff = mousePosition - e.clientX;
452
-            const width = Math.max(
453
-                Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth),
454
-                DEFAULT_FILMSTRIP_WIDTH
455
-            );
456
-
457
-            if (width !== _verticalFilmstripWidth) {
458
-                dispatch(setUserFilmstripWidth(width));
515
+            const {
516
+                dispatch,
517
+                _verticalFilmstripWidth,
518
+                _maxFilmstripWidth,
519
+                _topPanelHeight,
520
+                _maxTopPanelHeight,
521
+                _topPanelFilmstrip
522
+            } = this.props;
523
+            const { dragFilmstripWidth, dragFilmstripHeight, mousePosition } = this.state;
524
+
525
+            if (_topPanelFilmstrip) {
526
+                const diff = e.clientY - mousePosition;
527
+                const height = Math.max(
528
+                    Math.min(dragFilmstripHeight + diff, _maxTopPanelHeight),
529
+                    TOP_FILMSTRIP_HEIGHT
530
+                );
531
+
532
+                if (height !== _topPanelHeight) {
533
+                    dispatch(setUserFilmstripHeight(height));
534
+                }
535
+            } else {
536
+                const diff = mousePosition - e.clientX;
537
+                const width = Math.max(
538
+                    Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth),
539
+                    DEFAULT_FILMSTRIP_WIDTH
540
+                );
541
+
542
+                if (width !== _verticalFilmstripWidth) {
543
+                    dispatch(setUserFilmstripWidth(width));
544
+                }
459 545
             }
460 546
         }
461 547
     }
@@ -495,7 +581,7 @@ class Filmstrip extends PureComponent <Props, State> {
495 581
      * @returns {void}
496 582
      */
497 583
     _onTabIn() {
498
-        if (!this.props._isToolboxVisible && this.props._visible) {
584
+        if (!this.props._isToolboxVisible && this.props._mainFilmstripVisible) {
499 585
             this.props.dispatch(showToolbox());
500 586
         }
501 587
     }
@@ -605,10 +691,10 @@ class Filmstrip extends PureComponent <Props, State> {
605 691
             _remoteParticipantsLength,
606 692
             _resizableFilmstrip,
607 693
             _rows,
608
-            _stageFilmstrip,
609 694
             _thumbnailHeight,
610 695
             _thumbnailWidth,
611
-            _verticalViewGrid
696
+            _verticalViewGrid,
697
+            filmstripType
612 698
         } = this.props;
613 699
 
614 700
         if (!_thumbnailWidth || isNaN(_thumbnailWidth) || !_thumbnailHeight
@@ -617,7 +703,7 @@ class Filmstrip extends PureComponent <Props, State> {
617 703
             return null;
618 704
         }
619 705
 
620
-        if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || _stageFilmstrip) {
706
+        if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || filmstripType !== FILMSTRIP_TYPE.MAIN) {
621 707
             return (
622 708
                 <FixedSizeGrid
623 709
                     className = 'filmstrip__videos remote-videos'
@@ -626,7 +712,7 @@ class Filmstrip extends PureComponent <Props, State> {
626 712
                     height = { _filmstripHeight }
627 713
                     initialScrollLeft = { 0 }
628 714
                     initialScrollTop = { 0 }
629
-                    itemData = {{ stageFilmstrip: _stageFilmstrip }}
715
+                    itemData = {{ filmstripType }}
630 716
                     itemKey = { this._gridItemKey }
631 717
                     onItemsRendered = { this._onGridItemsRendered }
632 718
                     overscanRowCount = { 1 }
@@ -694,7 +780,11 @@ class Filmstrip extends PureComponent <Props, State> {
694 780
      * @returns {void}
695 781
      */
696 782
     _doToggleFilmstrip() {
697
-        this.props.dispatch(setFilmstripVisible(!this.props._visible));
783
+        const { dispatch, _mainFilmstripVisible, _topPanelFilmstrip, _topPanelVisible } = this.props;
784
+
785
+        _topPanelFilmstrip
786
+            ? dispatch(setTopPanelVisible(!_topPanelVisible))
787
+            : dispatch(setFilmstripVisible(!_mainFilmstripVisible));
698 788
     }
699 789
 
700 790
     _onShortcutToggleFilmstrip: () => void;
@@ -710,7 +800,7 @@ class Filmstrip extends PureComponent <Props, State> {
710 800
         sendAnalytics(createShortcutEvent(
711 801
             'toggle.filmstrip',
712 802
             {
713
-                enable: this.props._visible
803
+                enable: this.props._mainFilmstripVisible
714 804
             }));
715 805
 
716 806
         this._doToggleFilmstrip();
@@ -729,7 +819,7 @@ class Filmstrip extends PureComponent <Props, State> {
729 819
         sendAnalytics(createToolbarEvent(
730 820
             'toggle.filmstrip.button',
731 821
             {
732
-                enable: this.props._visible
822
+                enable: this.props._mainFilmstripVisible
733 823
             }));
734 824
 
735 825
         this._doToggleFilmstrip();
@@ -758,8 +848,15 @@ class Filmstrip extends PureComponent <Props, State> {
758 848
      * @returns {ReactElement}
759 849
      */
760 850
     _renderToggleButton() {
761
-        const icon = this.props._visible ? IconMenuDown : IconMenuUp;
762
-        const { t, classes, _isVerticalFilmstrip } = this.props;
851
+        const {
852
+            t,
853
+            classes,
854
+            _isVerticalFilmstrip,
855
+            _mainFilmstripVisible,
856
+            _topPanelFilmstrip,
857
+            _topPanelVisible
858
+        } = this.props;
859
+        const icon = (_topPanelFilmstrip ? _topPanelVisible : _mainFilmstripVisible) ? IconMenuDown : IconMenuUp;
763 860
         const actions = isMobileBrowser()
764 861
             ? { onTouchStart: this._onToggleButtonTouch }
765 862
             : { onClick: this._onToolbarToggleFilmstrip };
@@ -768,9 +865,11 @@ class Filmstrip extends PureComponent <Props, State> {
768 865
             <div
769 866
                 className = { clsx(classes.toggleFilmstripContainer,
770 867
                     _isVerticalFilmstrip && classes.toggleVerticalFilmstripContainer,
868
+                    _topPanelFilmstrip && classes.toggleTopPanelContainer,
869
+                    _topPanelFilmstrip && !_topPanelVisible && classes.toggleTopPanelContainerHidden,
771 870
                     'toggleFilmstripContainer') }>
772 871
                 <button
773
-                    aria-expanded = { this.props._visible }
872
+                    aria-expanded = { this.props._mainFilmstripVisible }
774 873
                     aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') }
775 874
                     className = { classes.toggleFilmstripButton }
776 875
                     id = 'toggleFilmstripButton'
@@ -795,32 +894,38 @@ class Filmstrip extends PureComponent <Props, State> {
795 894
  * @returns {Props}
796 895
  */
797 896
 function _mapStateToProps(state, ownProps) {
798
-    const { _hasScroll = false } = ownProps;
897
+    const { _hasScroll = false, filmstripType, _topPanelFilmstrip, _remoteParticipants } = ownProps;
799 898
     const toolbarButtons = getToolbarButtons(state);
800 899
     const { testing = {}, iAmRecorder } = state['features/base/config'];
801 900
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
802
-    const { visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
901
+    const { topPanelHeight, topPanelVisible, visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
803 902
     const { localScreenShare } = state['features/base/participants'];
804 903
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
805 904
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
806 905
     const { isOpen: shiftRight } = state['features/chat'];
807 906
     const disableSelfView = shouldHideSelfView(state);
808
-    const { clientWidth } = state['features/base/responsive-ui'];
907
+    const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
809 908
 
810 909
     const collapseTileView = reduceHeight
811 910
         && isMobileBrowser()
812 911
         && clientWidth <= ASPECT_RATIO_BREAKPOINT;
813 912
 
814 913
     const shouldReduceHeight = reduceHeight && isMobileBrowser();
914
+    const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
915
+
916
+    let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
815 917
 
816
-    const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}${_hasScroll ? ' has-scroll' : ''}`;
918
+    if (_topPanelFilmstrip) {
919
+        isVisible = _topPanelVisible;
920
+    }
921
+    const videosClassName = `filmstrip__videos${isVisible ? '' : ' hidden'}${_hasScroll ? ' has-scroll' : ''}`;
817 922
     const className = `${remoteVideosVisible || ownProps._verticalViewGrid ? '' : 'hide-videos'} ${
818 923
         shouldReduceHeight ? 'reduce-height' : ''
819
-    } ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim();
924
+    } ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${isVisible ? '' : 'hidden'}`.trim();
820 925
 
821 926
     const _currentLayout = getCurrentLayout(state);
822 927
     const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
823
-        || (!ownProps._stageFilmstrip && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
928
+        || (filmstripType === FILMSTRIP_TYPE.MAIN && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
824 929
 
825 930
     return {
826 931
         _className: className,
@@ -833,8 +938,14 @@ function _mapStateToProps(state, ownProps) {
833 938
         _isToolboxVisible: isToolboxVisible(state),
834 939
         _isVerticalFilmstrip,
835 940
         _localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare,
941
+        _mainFilmstripVisible: visible,
836 942
         _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
943
+        _maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
944
+        _remoteParticipantsLength: _remoteParticipants.length,
837 945
         _thumbnailsReordered: enableThumbnailReordering,
946
+        _topPanelHeight: topPanelHeight.current,
947
+        _topPanelMaxHeight: topPanelHeight.current || TOP_FILMSTRIP_HEIGHT,
948
+        _topPanelVisible,
838 949
         _verticalFilmstripWidth: verticalFilmstripWidth.current,
839 950
         _verticalViewMaxWidth: getVerticalViewMaxWidth(state),
840 951
         _videosClassName: videosClassName

+ 10
- 10
react/features/filmstrip/components/web/MainFilmstrip.js Просмотреть файл

@@ -9,6 +9,7 @@ import {
9 9
     ASPECT_RATIO_BREAKPOINT,
10 10
     FILMSTRIP_BREAKPOINT,
11 11
     FILMSTRIP_BREAKPOINT_OFFSET,
12
+    FILMSTRIP_TYPE,
12 13
     TOOLBAR_HEIGHT,
13 14
     TOOLBAR_HEIGHT_MOBILE } from '../../constants';
14 15
 import { isFilmstripResizable, showGridInVerticalView } from '../../functions.web';
@@ -85,15 +86,16 @@ type Props = {
85 86
     /**
86 87
      * Additional CSS class names to add to the container of all the thumbnails.
87 88
      */
88
-    _videosClassName: string,
89
-
90
-    /**
91
-     * Whether or not the filmstrip videos should currently be displayed.
92
-     */
93
-    _visible: boolean
89
+    _videosClassName: string
94 90
 };
95 91
 
96
-const MainFilmstrip = (props: Props) => <span><Filmstrip { ...props } /></span>;
92
+const MainFilmstrip = (props: Props) => (
93
+    <span>
94
+        <Filmstrip
95
+            { ...props }
96
+            filmstripType = { FILMSTRIP_TYPE.MAIN } />
97
+    </span>
98
+);
97 99
 
98 100
 /**
99 101
  * Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
@@ -104,7 +106,7 @@ const MainFilmstrip = (props: Props) => <span><Filmstrip { ...props } /></span>;
104 106
  */
105 107
 function _mapStateToProps(state) {
106 108
     const toolbarButtons = getToolbarButtons(state);
107
-    const { visible, remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
109
+    const { remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
108 110
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
109 111
     const {
110 112
         gridDimensions: dimensions = {},
@@ -189,13 +191,11 @@ function _mapStateToProps(state) {
189 191
         _filmstripHeight: remoteFilmstripHeight,
190 192
         _filmstripWidth: remoteFilmstripWidth,
191 193
         _hasScroll,
192
-        _remoteParticipantsLength: remoteParticipants.length,
193 194
         _remoteParticipants: remoteParticipants,
194 195
         _resizableFilmstrip,
195 196
         _rows: gridDimensions.rows,
196 197
         _thumbnailWidth: _thumbnailSize?.width,
197 198
         _thumbnailHeight: _thumbnailSize?.height,
198
-        _visible: visible,
199 199
         _verticalViewGrid,
200 200
         _verticalViewBackground: verticalFilmstripWidth.current + FILMSTRIP_BREAKPOINT_OFFSET >= FILMSTRIP_BREAKPOINT
201 201
     };

+ 125
- 0
react/features/filmstrip/components/web/ScreenshareFilmstrip.js Просмотреть файл

@@ -0,0 +1,125 @@
1
+// @flow
2
+import React from 'react';
3
+
4
+import { connect } from '../../../base/redux';
5
+import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference';
6
+import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
7
+import {
8
+    FILMSTRIP_TYPE
9
+} from '../../constants';
10
+
11
+import Filmstrip from './Filmstrip';
12
+
13
+type Props = {
14
+
15
+    /**
16
+     * The current layout of the filmstrip.
17
+     */
18
+    _currentLayout: string,
19
+
20
+    /**
21
+     * The number of columns in tile view.
22
+     */
23
+    _columns: number,
24
+
25
+    /**
26
+     * The width of the filmstrip.
27
+     */
28
+    _filmstripWidth: number,
29
+
30
+    /**
31
+     * The height of the filmstrip.
32
+     */
33
+    _filmstripHeight: number,
34
+
35
+    /**
36
+     * Whether or not the current layout is vertical filmstrip.
37
+     */
38
+    _isVerticalFilmstrip: boolean,
39
+
40
+    /**
41
+     * The participants in the call.
42
+     */
43
+    _remoteParticipants: Array<Object>,
44
+
45
+    /**
46
+     * The length of the remote participants array.
47
+     */
48
+    _remoteParticipantsLength: number,
49
+
50
+    /**
51
+     * Whether or not the filmstrip should be user-resizable.
52
+     */
53
+    _resizableFilmstrip: boolean,
54
+
55
+    /**
56
+     * The number of rows in tile view.
57
+     */
58
+    _rows: number,
59
+
60
+    /**
61
+     * The height of the thumbnail.
62
+     */
63
+    _thumbnailHeight: number,
64
+
65
+    /**
66
+     * The width of the thumbnail.
67
+     */
68
+    _thumbnailWidth: number,
69
+
70
+    /**
71
+     * Whether or not the vertical filmstrip should have a background color.
72
+     */
73
+    _verticalViewBackground: boolean,
74
+
75
+    /**
76
+     * Whether or not the vertical filmstrip should be displayed as grid.
77
+     */
78
+    _verticalViewGrid: boolean,
79
+
80
+    /**
81
+     * Additional CSS class names to add to the container of all the thumbnails.
82
+     */
83
+    _videosClassName: string
84
+};
85
+
86
+const ScreenshareFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW
87
+    && props._remoteParticipantsLength === 1 && (
88
+    <span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
89
+        <Filmstrip
90
+            { ...props }
91
+            filmstripType = { FILMSTRIP_TYPE.SCREENSHARE } />
92
+    </span>
93
+);
94
+
95
+/**
96
+ * Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
97
+ *
98
+ * @param {Object} state - The Redux state.
99
+ * @private
100
+ * @returns {Props}
101
+ */
102
+function _mapStateToProps(state) {
103
+    const {
104
+        filmstripHeight,
105
+        filmstripWidth,
106
+        thumbnailSize
107
+    } = state['features/filmstrip'].screenshareFilmstripDimensions;
108
+    const screenshares = state['features/video-layout'].remoteScreenShares;
109
+
110
+    return {
111
+        _columns: 1,
112
+        _currentLayout: getCurrentLayout(state),
113
+        _filmstripHeight: filmstripHeight,
114
+        _filmstripWidth: filmstripWidth,
115
+        _remoteParticipants: screenshares.length ? [ screenshares[0] ] : [],
116
+        _resizableFilmstrip: false,
117
+        _rows: 1,
118
+        _thumbnailWidth: thumbnailSize?.width,
119
+        _thumbnailHeight: thumbnailSize?.height,
120
+        _verticalViewGrid: false,
121
+        _verticalViewBackground: false
122
+    };
123
+}
124
+
125
+export default connect(_mapStateToProps)(ScreenshareFilmstrip);

+ 7
- 11
react/features/filmstrip/components/web/StageFilmstrip.js Просмотреть файл

@@ -8,9 +8,11 @@ import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference
8 8
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
9 9
 import {
10 10
     ASPECT_RATIO_BREAKPOINT,
11
+    FILMSTRIP_TYPE,
11 12
     TOOLBAR_HEIGHT_MOBILE
12 13
 } from '../../constants';
13 14
 import { getActiveParticipantsIds } from '../../functions';
15
+import { isFilmstripResizable, isStageFilmstripTopPanel } from '../../functions.web';
14 16
 
15 17
 import Filmstrip from './Filmstrip';
16 18
 
@@ -84,19 +86,14 @@ type Props = {
84 86
     /**
85 87
      * Additional CSS class names to add to the container of all the thumbnails.
86 88
      */
87
-    _videosClassName: string,
88
-
89
-    /**
90
-     * Whether or not the filmstrip videos should currently be displayed.
91
-     */
92
-    _visible: boolean
89
+    _videosClassName: string
93 90
 };
94 91
 
95 92
 const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && (
96 93
     <span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
97 94
         <Filmstrip
98 95
             { ...props }
99
-            _stageFilmstrip = { true } />
96
+            filmstripType = { FILMSTRIP_TYPE.STAGE } />
100 97
     </span>
101 98
 );
102 99
 
@@ -109,7 +106,6 @@ const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_
109 106
  */
110 107
 function _mapStateToProps(state) {
111 108
     const toolbarButtons = getToolbarButtons(state);
112
-    const { visible } = state['features/filmstrip'];
113 109
     const activeParticipants = getActiveParticipantsIds(state);
114 110
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
115 111
     const {
@@ -139,19 +135,19 @@ function _mapStateToProps(state) {
139 135
         && clientWidth <= ASPECT_RATIO_BREAKPOINT;
140 136
 
141 137
     const remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);
138
+    const _topPanelFilmstrip = isStageFilmstripTopPanel(state);
142 139
 
143 140
     return {
144 141
         _columns: gridDimensions.columns,
145 142
         _currentLayout: getCurrentLayout(state),
146 143
         _filmstripHeight: remoteFilmstripHeight,
147 144
         _filmstripWidth: filmstripWidth,
148
-        _remoteParticipantsLength: activeParticipants.length,
149 145
         _remoteParticipants: activeParticipants,
150
-        _resizableFilmstrip: false,
146
+        _resizableFilmstrip: isFilmstripResizable(state) && _topPanelFilmstrip,
151 147
         _rows: gridDimensions.rows,
152 148
         _thumbnailWidth: thumbnailSize?.width,
153 149
         _thumbnailHeight: thumbnailSize?.height,
154
-        _visible: visible,
150
+        _topPanelFilmstrip,
155 151
         _verticalViewGrid: false,
156 152
         _verticalViewBackground: false
157 153
     };

+ 21
- 12
react/features/filmstrip/components/web/Thumbnail.js Просмотреть файл

@@ -37,6 +37,7 @@ import { togglePinStageParticipant } from '../../actions';
37 37
 import {
38 38
     DISPLAY_MODE_TO_CLASS_NAME,
39 39
     DISPLAY_VIDEO,
40
+    FILMSTRIP_TYPE,
40 41
     SHOW_TOOLBAR_CONTEXT_MENU_AFTER,
41 42
     THUMBNAIL_TYPE,
42 43
     VIDEO_TEST_EVENTS
@@ -234,6 +235,11 @@ export type Props = {|
234 235
      */
235 236
     dispatch: Function,
236 237
 
238
+    /**
239
+     * The type of filmstrip the tile is displayed in.
240
+     */
241
+    filmstripType: string,
242
+
237 243
     /**
238 244
      * The horizontal offset in px for the thumbnail. Used to center the thumbnails from the last row in tile view.
239 245
      */
@@ -244,11 +250,6 @@ export type Props = {|
244 250
      */
245 251
     participantID: ?string,
246 252
 
247
-    /**
248
-     * Whether the tile is displayed in the stage filmstrip or not.
249
-     */
250
-    stageFilmstrip: boolean,
251
-
252 253
     /**
253 254
      * Styles that will be set to the Thumbnail's main span element.
254 255
      */
@@ -993,7 +994,7 @@ class Thumbnail extends Component<Props, State> {
993 994
             _thumbnailType,
994 995
             _videoTrack,
995 996
             classes,
996
-            stageFilmstrip
997
+            filmstripType
997 998
         } = this.props;
998 999
         const { id } = _participant || {};
999 1000
         const { isHovered, popoverVisible } = this.state;
@@ -1031,8 +1032,8 @@ class Thumbnail extends Component<Props, State> {
1031 1032
             <span
1032 1033
                 className = { containerClassName }
1033 1034
                 id = { local
1034
-                    ? `localVideoContainer${stageFilmstrip ? '_stage' : ''}`
1035
-                    : `participant_${id}${stageFilmstrip ? '_stage' : ''}`
1035
+                    ? `localVideoContainer${filmstripType === FILMSTRIP_TYPE.MAIN ? '' : `_${filmstripType}`}`
1036
+                    : `participant_${id}${filmstripType === FILMSTRIP_TYPE.MAIN ? '' : `_${filmstripType}`}`
1036 1037
                 }
1037 1038
                 { ...(_isMobile
1038 1039
                     ? {
@@ -1168,7 +1169,7 @@ class Thumbnail extends Component<Props, State> {
1168 1169
  * @returns {Props}
1169 1170
  */
1170 1171
 function _mapStateToProps(state, ownProps): Object {
1171
-    const { participantID, stageFilmstrip } = ownProps;
1172
+    const { participantID, filmstripType = FILMSTRIP_TYPE.MAIN } = ownProps;
1172 1173
 
1173 1174
     const participant = getParticipantByIdOrUndefined(state, participantID);
1174 1175
     const id = participant?.id;
@@ -1199,7 +1200,7 @@ function _mapStateToProps(state, ownProps): Object {
1199 1200
     const { localFlipX } = state['features/base/settings'];
1200 1201
     const _isMobile = isMobileBrowser();
1201 1202
     const activeParticipants = getActiveParticipantsIds(state);
1202
-    const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip);
1203
+    const tileType = getThumbnailTypeFromLayout(_currentLayout, filmstripType);
1203 1204
 
1204 1205
     switch (tileType) {
1205 1206
     case THUMBNAIL_TYPE.VERTICAL:
@@ -1244,7 +1245,8 @@ function _mapStateToProps(state, ownProps): Object {
1244 1245
         const {
1245 1246
             stageFilmstripDimensions = {
1246 1247
                 thumbnailSize: {}
1247
-            }
1248
+            },
1249
+            screenshareFilmstripDimensions
1248 1250
         } = state['features/filmstrip'];
1249 1251
 
1250 1252
         size = {
@@ -1252,9 +1254,16 @@ function _mapStateToProps(state, ownProps): Object {
1252 1254
             _height: thumbnailSize?.height
1253 1255
         };
1254 1256
 
1255
-        if (stageFilmstrip) {
1257
+        if (filmstripType === FILMSTRIP_TYPE.STAGE) {
1256 1258
             const { width: _width, height: _height } = stageFilmstripDimensions.thumbnailSize;
1257 1259
 
1260
+            size = {
1261
+                _width,
1262
+                _height
1263
+            };
1264
+        } else if (filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
1265
+            const { width: _width, height: _height } = screenshareFilmstripDimensions.thumbnailSize;
1266
+
1258 1267
             size = {
1259 1268
                 _width,
1260 1269
                 _height

+ 25
- 12
react/features/filmstrip/components/web/ThumbnailWrapper.js Просмотреть файл

@@ -7,7 +7,7 @@ import { getLocalParticipant } from '../../../base/participants';
7 7
 import { connect } from '../../../base/redux';
8 8
 import { shouldHideSelfView } from '../../../base/settings/functions.any';
9 9
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
10
-import { TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN } from '../../constants';
10
+import { TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN, FILMSTRIP_TYPE } from '../../constants';
11 11
 import { showGridInVerticalView, getActiveParticipantsIds } from '../../functions';
12 12
 
13 13
 import Thumbnail from './Thumbnail';
@@ -22,6 +22,11 @@ type Props = {
22 22
      */
23 23
     _disableSelfView: boolean,
24 24
 
25
+    /**
26
+     * The type of filmstrip this thumbnail is displayed in.
27
+     */
28
+    _filmstripType: string,
29
+
25 30
     /**
26 31
      * The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view.
27 32
      */
@@ -37,11 +42,6 @@ type Props = {
37 42
      */
38 43
     _isLocalScreenShare: boolean,
39 44
 
40
-    /**
41
-     * Whether or not the filmstrip is used a stage filmstrip.
42
-     */
43
-    _stageFilmstrip: boolean,
44
-
45 45
     /**
46 46
      * The width of the thumbnail. Used for expanding the width of the thumbnails on last row in case
47 47
      * there is empty space.
@@ -97,10 +97,10 @@ class ThumbnailWrapper extends Component<Props> {
97 97
     render() {
98 98
         const {
99 99
             _disableSelfView,
100
+            _filmstripType = FILMSTRIP_TYPE.MAIN,
100 101
             _isLocalScreenShare = false,
101 102
             _horizontalOffset = 0,
102 103
             _participantID,
103
-            _stageFilmstrip,
104 104
             _thumbnailWidth,
105 105
             style
106 106
         } = this.props;
@@ -112,9 +112,9 @@ class ThumbnailWrapper extends Component<Props> {
112 112
         if (_participantID === 'local') {
113 113
             return _disableSelfView ? null : (
114 114
                 <Thumbnail
115
+                    filmstripType = { _filmstripType }
115 116
                     horizontalOffset = { _horizontalOffset }
116 117
                     key = 'local'
117
-                    stageFilmstrip = { _stageFilmstrip }
118 118
                     style = { style }
119 119
                     width = { _thumbnailWidth } />);
120 120
         }
@@ -122,20 +122,20 @@ class ThumbnailWrapper extends Component<Props> {
122 122
         if (_isLocalScreenShare) {
123 123
             return _disableSelfView ? null : (
124 124
                 <Thumbnail
125
+                    filmstripType = { _filmstripType }
125 126
                     horizontalOffset = { _horizontalOffset }
126 127
                     key = 'localScreenShare'
127 128
                     participantID = { _participantID }
128
-                    stageFilmstrip = { _stageFilmstrip }
129 129
                     style = { style }
130 130
                     width = { _thumbnailWidth } />);
131 131
         }
132 132
 
133 133
         return (
134 134
             <Thumbnail
135
+                filmstripType = { _filmstripType }
135 136
                 horizontalOffset = { _horizontalOffset }
136 137
                 key = { `remote_${_participantID}` }
137 138
                 participantID = { _participantID }
138
-                stageFilmstrip = { _stageFilmstrip }
139 139
                 style = { style }
140 140
                 width = { _thumbnailWidth } />);
141 141
     }
@@ -158,7 +158,8 @@ function _mapStateToProps(state, ownProps) {
158 158
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
159 159
     const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
160 160
     const _verticalViewGrid = showGridInVerticalView(state);
161
-    const stageFilmstrip = ownProps.data?.stageFilmstrip;
161
+    const filmstripType = ownProps.data?.filmstripType;
162
+    const stageFilmstrip = filmstripType === FILMSTRIP_TYPE.STAGE;
162 163
     const sortedActiveParticipants = activeParticipants.sort();
163 164
     const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote;
164 165
     const remoteParticipantsLength = remoteParticipants.length;
@@ -235,9 +236,9 @@ function _mapStateToProps(state, ownProps) {
235 236
         if (stageFilmstrip) {
236 237
             return {
237 238
                 _disableSelfView: disableSelfView,
239
+                _filmstripType: filmstripType,
238 240
                 _participantID: remoteParticipants[index] === localId ? 'local' : remoteParticipants[index],
239 241
                 _horizontalOffset: horizontalOffset,
240
-                _stageFilmstrip: stageFilmstrip,
241 242
                 _thumbnailWidth: thumbnailWidth
242 243
             };
243 244
         }
@@ -260,6 +261,7 @@ function _mapStateToProps(state, ownProps) {
260 261
         if (!iAmRecorder && index === localIndex) {
261 262
             return {
262 263
                 _disableSelfView: disableSelfView,
264
+                _filmstripType: filmstripType,
263 265
                 _participantID: 'local',
264 266
                 _horizontalOffset: horizontalOffset,
265 267
                 _thumbnailWidth: thumbnailWidth
@@ -269,6 +271,7 @@ function _mapStateToProps(state, ownProps) {
269 271
         if (sourceNameSignalingEnabled && !iAmRecorder && localScreenShare && index === localScreenShareIndex) {
270 272
             return {
271 273
                 _disableSelfView: disableSelfView,
274
+                _filmstripType: filmstripType,
272 275
                 _isLocalScreenShare: true,
273 276
                 _participantID: localScreenShare?.id,
274 277
                 _horizontalOffset: horizontalOffset,
@@ -277,12 +280,22 @@ function _mapStateToProps(state, ownProps) {
277 280
         }
278 281
 
279 282
         return {
283
+            _filmstripType: filmstripType,
280 284
             _participantID: remoteParticipants[remoteIndex],
281 285
             _horizontalOffset: horizontalOffset,
282 286
             _thumbnailWidth: thumbnailWidth
283 287
         };
284 288
     }
285 289
 
290
+    if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
291
+        const { remoteScreenShares } = state['features/video-layout'];
292
+
293
+        return {
294
+            _filmstripType: filmstripType,
295
+            _participantID: remoteScreenShares[remoteScreenShares.length - 1]
296
+        };
297
+    }
298
+
286 299
     const { index } = ownProps;
287 300
 
288 301
     if (typeof index !== 'number' || remoteParticipantsLength <= index) {

+ 1
- 0
react/features/filmstrip/components/web/index.js Просмотреть файл

@@ -5,6 +5,7 @@ export { default as Filmstrip } from './Filmstrip';
5 5
 export { default as MainFilmstrip } from './MainFilmstrip';
6 6
 export { default as ModeratorIndicator } from './ModeratorIndicator';
7 7
 export { default as RaisedHandIndicator } from './RaisedHandIndicator';
8
+export { default as ScreenshareFilmstrip } from './ScreenshareFilmstrip';
8 9
 export { default as StageFilmstrip } from './StageFilmstrip';
9 10
 export { default as StatusIndicators } from './StatusIndicators';
10 11
 export { default as Thumbnail } from './Thumbnail';

+ 28
- 1
react/features/filmstrip/components/web/styles.js Просмотреть файл

@@ -23,6 +23,7 @@ export const styles = theme => {
23 23
             left: 'calc(50% - 16px)',
24 24
             opacity: 0,
25 25
             transition: 'opacity .3s',
26
+            zIndex: 1,
26 27
 
27 28
             '&:hover': {
28 29
                 backgroundColor: theme.palette.ui02
@@ -53,8 +54,18 @@ export const styles = theme => {
53 54
             top: 'calc(50% - 12px)'
54 55
         },
55 56
 
57
+        toggleTopPanelContainer: {
58
+            transform: 'rotate(180deg)',
59
+            bottom: 'calc(-24px - 6px)',
60
+            top: 'auto'
61
+        },
62
+
63
+        toggleTopPanelContainerHidden: {
64
+            visibility: 'hidden'
65
+        },
66
+
56 67
         filmstrip: {
57
-            transition: 'background .2s ease-in-out, right 1s, bottom 1s, height .3s ease-in',
68
+            transition: 'background .2s ease-in-out, right 1s, bottom 1s, top 1s, height .3s ease-in',
58 69
             right: 0,
59 70
             bottom: 0,
60 71
 
@@ -111,6 +122,10 @@ export const styles = theme => {
111 122
             '& .avatar-container': {
112 123
                 maxWidth: 'initial',
113 124
                 maxHeight: 'initial'
125
+            },
126
+
127
+            '&.top-panel-filmstrip': {
128
+                flexDirection: 'column'
114 129
             }
115 130
         },
116 131
 
@@ -137,6 +152,18 @@ export const styles = theme => {
137 152
                 '& .dragHandle': {
138 153
                     backgroundColor: theme.palette.icon01
139 154
                 }
155
+            },
156
+
157
+            '&.top-panel': {
158
+                order: 2,
159
+                width: '100%',
160
+                height: '9px',
161
+                cursor: 'row-resize',
162
+
163
+                '& .dragHandle': {
164
+                    height: '3px',
165
+                    width: '100px'
166
+                }
140 167
             }
141 168
         },
142 169
 

+ 20
- 0
react/features/filmstrip/constants.js Просмотреть файл

@@ -281,6 +281,12 @@ export const FILMSTRIP_GRID_BREAKPOINT = 300;
281 281
  */
282 282
 export const FILMSTRIP_BREAKPOINT_OFFSET = 5;
283 283
 
284
+/**
285
+ * The minimum height for the stage view
286
+ * (used to determine the maximum height of the user-resizable top panel).
287
+ */
288
+export const MIN_STAGE_VIEW_HEIGHT = 700;
289
+
284 290
 /**
285 291
  * The minimum width for the stage view
286 292
  * (used to determine the maximum width of the user-resizable vertical filmstrip).
@@ -298,7 +304,21 @@ export const VERTICAL_VIEW_HORIZONTAL_MARGIN = VERTICAL_FILMSTRIP_MIN_HORIZONTAL
298 304
  */
299 305
 export const ACTIVE_PARTICIPANT_TIMEOUT = 1000 * 60;
300 306
 
307
+/**
308
+ * The types of filmstrip.
309
+ */
310
+export const FILMSTRIP_TYPE = {
311
+    MAIN: 'main',
312
+    STAGE: 'stage',
313
+    SCREENSHARE: 'screenshare'
314
+};
315
+
301 316
 /**
302 317
  * The max number of participants to be displayed on the stage filmstrip.
303 318
  */
304 319
 export const MAX_ACTIVE_PARTICIPANTS = 6;
320
+
321
+/**
322
+ * Top filmstrip default height.
323
+ */
324
+export const TOP_FILMSTRIP_HEIGHT = 180;

+ 49
- 14
react/features/filmstrip/functions.web.js Просмотреть файл

@@ -6,6 +6,7 @@ import { MEDIA_TYPE } from '../base/media';
6 6
 import {
7 7
     getLocalParticipant,
8 8
     getParticipantById,
9
+    getParticipantCount,
9 10
     getParticipantCountWithFake,
10 11
     getPinnedParticipant
11 12
 } from '../base/participants';
@@ -32,6 +33,7 @@ import {
32 33
     DISPLAY_AVATAR,
33 34
     DISPLAY_VIDEO,
34 35
     FILMSTRIP_GRID_BREAKPOINT,
36
+    FILMSTRIP_TYPE,
35 37
     INDICATORS_TOOLTIP_POSITION,
36 38
     SCROLL_SIZE,
37 39
     SQUARE_TILE_ASPECT_RATIO,
@@ -296,7 +298,8 @@ export function calculateResponsiveTileViewDimensions({
296 298
     noHorizontalContainerMargin = false,
297 299
     maxColumns,
298 300
     numberOfParticipants,
299
-    desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES
301
+    desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
302
+    minTileHeight
300 303
 }) {
301 304
     let height, width;
302 305
     let columns, rows;
@@ -324,7 +327,8 @@ export function calculateResponsiveTileViewDimensions({
324 327
             clientHeight,
325 328
             disableTileEnlargement,
326 329
             disableResponsiveTiles: false,
327
-            noHorizontalContainerMargin
330
+            noHorizontalContainerMargin,
331
+            minTileHeight
328 332
         });
329 333
 
330 334
         if (size) {
@@ -413,10 +417,11 @@ export function calculateThumbnailSizeForTileView({
413 417
     clientHeight,
414 418
     disableResponsiveTiles = false,
415 419
     disableTileEnlargement = false,
416
-    noHorizontalContainerMargin = false
420
+    noHorizontalContainerMargin = false,
421
+    minTileHeight
417 422
 }: Object) {
418 423
     const aspectRatio = getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth);
419
-    const minHeight = getThumbnailMinHeight(clientWidth);
424
+    const minHeight = minTileHeight || getThumbnailMinHeight(clientWidth);
420 425
     const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
421 426
         - (noHorizontalContainerMargin ? SCROLL_SIZE : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
422 427
     const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
@@ -506,6 +511,7 @@ export function getVerticalFilmstripVisibleAreaWidth() {
506 511
 */
507 512
 export function computeDisplayModeFromInput(input: Object) {
508 513
     const {
514
+        filmstripType,
509 515
         isActiveParticipant,
510 516
         isAudioOnly,
511 517
         isCurrentlyOnLargeVideo,
@@ -515,7 +521,6 @@ export function computeDisplayModeFromInput(input: Object) {
515 521
         isRemoteParticipant,
516 522
         multipleVideoSupport,
517 523
         stageParticipantsVisible,
518
-        stageFilmstrip,
519 524
         tileViewActive
520 525
     } = input;
521 526
     const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
@@ -534,8 +539,8 @@ export function computeDisplayModeFromInput(input: Object) {
534 539
         }
535 540
     }
536 541
 
537
-    if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
538
-        || (stageParticipantsVisible && isActiveParticipant && !stageFilmstrip))) {
542
+    if (!tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN && ((isScreenSharing && isRemoteParticipant)
543
+        || (stageParticipantsVisible && isActiveParticipant))) {
539 544
         return DISPLAY_AVATAR;
540 545
     } else if (isCurrentlyOnLargeVideo && !tileViewActive) {
541 546
         // Display name is always and only displayed when user is on the stage
@@ -569,12 +574,13 @@ export function getDisplayModeInput(props: Object, state: Object) {
569 574
         _participant,
570 575
         _stageParticipantsVisible,
571 576
         _videoTrack,
572
-        stageFilmstrip
577
+        filmstripType = FILMSTRIP_TYPE.MAIN
573 578
     } = props;
574 579
     const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
575 580
     const { canPlayEventReceived } = state;
576 581
 
577 582
     return {
583
+        filmstripType,
578 584
         isActiveParticipant: _isActiveParticipant,
579 585
         isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo,
580 586
         isAudioOnly: _isAudioOnly,
@@ -588,7 +594,6 @@ export function getDisplayModeInput(props: Object, state: Object) {
588 594
         isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant,
589 595
         multipleVideoSupport: _multipleVideoSupport,
590 596
         stageParticipantsVisible: _stageParticipantsVisible,
591
-        stageFilmstrip,
592 597
         videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
593 598
     };
594 599
 }
@@ -717,8 +722,24 @@ export function isStageFilmstripAvailable(state, minParticipantCount = 0) {
717 722
     const { remoteScreenShares } = state['features/video-layout'];
718 723
     const sharedVideo = isSharingStatus(state['features/shared-video']?.status);
719 724
 
720
-    return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo
721
-        && activeParticipants.length >= minParticipantCount;
725
+    return isStageFilmstripEnabled(state) && !sharedVideo
726
+        && activeParticipants.length >= minParticipantCount
727
+        && (isTopPanelEnabled(state) || remoteScreenShares.length === 0);
728
+}
729
+
730
+/**
731
+ * Whether the stage filmstrip should be displayed on the top.
732
+ *
733
+ * @param {Object} state - Redux state.
734
+ * @param {number} minParticipantCount - The min number of participants for the stage filmstrip
735
+ * to be displayed.
736
+ * @returns {boolean}
737
+ */
738
+export function isStageFilmstripTopPanel(state, minParticipantCount = 0) {
739
+    const { remoteScreenShares } = state['features/video-layout'];
740
+
741
+    return isTopPanelEnabled(state)
742
+        && isStageFilmstripAvailable(state, minParticipantCount) && remoteScreenShares.length > 0;
722 743
 }
723 744
 
724 745
 /**
@@ -737,10 +758,10 @@ export function isStageFilmstripEnabled(state) {
737 758
  * Gets the thumbnail type by filmstrip type.
738 759
  *
739 760
  * @param {string} currentLayout - Current app layout.
740
- * @param {boolean} isStageFilmstrip - Whether the filmstrip is stage filmstrip or not.
761
+ * @param {string} filmstripType - The current filmstrip type.
741 762
  * @returns {string}
742 763
  */
743
-export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = false) {
764
+export function getThumbnailTypeFromLayout(currentLayout, filmstripType) {
744 765
     switch (currentLayout) {
745 766
     case LAYOUTS.TILE_VIEW:
746 767
         return THUMBNAIL_TYPE.TILE;
@@ -749,10 +770,24 @@ export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = fal
749 770
     case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
750 771
         return THUMBNAIL_TYPE.HORIZONTAL;
751 772
     case LAYOUTS.STAGE_FILMSTRIP_VIEW:
752
-        if (isStageFilmstrip) {
773
+        if (filmstripType !== FILMSTRIP_TYPE.MAIN) {
753 774
             return THUMBNAIL_TYPE.TILE;
754 775
         }
755 776
 
756 777
         return THUMBNAIL_TYPE.VERTICAL;
757 778
     }
758 779
 }
780
+
781
+/**
782
+ * Whether or not the top panel is enabled.
783
+ *
784
+ * @param {Object} state - Redux state.
785
+ * @returns {boolean}
786
+ */
787
+export function isTopPanelEnabled(state) {
788
+    const { filmstrip } = state['features/base/config'];
789
+    const participantsCount = getParticipantCount(state);
790
+
791
+    return !filmstrip?.disableTopPanel && participantsCount >= (filmstrip?.minParticipantCountForTopPanel ?? 50);
792
+
793
+}

+ 16
- 5
react/features/filmstrip/middleware.web.js Просмотреть файл

@@ -31,6 +31,7 @@ import {
31 31
 import {
32 32
     addStageParticipant,
33 33
     removeStageParticipant,
34
+    setFilmstripHeight,
34 35
     setFilmstripWidth,
35 36
     setStageParticipants
36 37
 } from './actions';
@@ -38,7 +39,9 @@ import {
38 39
     ACTIVE_PARTICIPANT_TIMEOUT,
39 40
     DEFAULT_FILMSTRIP_WIDTH,
40 41
     MAX_ACTIVE_PARTICIPANTS,
41
-    MIN_STAGE_VIEW_WIDTH
42
+    MIN_STAGE_VIEW_HEIGHT,
43
+    MIN_STAGE_VIEW_WIDTH,
44
+    TOP_FILMSTRIP_HEIGHT
42 45
 } from './constants';
43 46
 import {
44 47
     isFilmstripResizable,
@@ -77,19 +80,27 @@ MiddlewareRegistry.register(store => next => action => {
77 80
         const state = store.getState();
78 81
 
79 82
         if (isFilmstripResizable(state)) {
80
-            const { width: filmstripWidth } = state['features/filmstrip'];
81
-            const { clientWidth } = action;
82
-            let width;
83
+            const { width: filmstripWidth, topPanelHeight } = state['features/filmstrip'];
84
+            const { clientWidth, clientHeight } = action;
85
+            let height, width;
83 86
 
84 87
             if (filmstripWidth.current > clientWidth - MIN_STAGE_VIEW_WIDTH) {
85 88
                 width = Math.max(clientWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH);
86 89
             } else {
87 90
                 width = Math.min(clientWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet);
88 91
             }
89
-
90 92
             if (width !== filmstripWidth.current) {
91 93
                 store.dispatch(setFilmstripWidth(width));
92 94
             }
95
+
96
+            if (topPanelHeight.current > clientHeight - MIN_STAGE_VIEW_HEIGHT) {
97
+                height = Math.max(clientHeight - MIN_STAGE_VIEW_HEIGHT, TOP_FILMSTRIP_HEIGHT);
98
+            } else {
99
+                height = Math.min(clientHeight - MIN_STAGE_VIEW_HEIGHT, topPanelHeight.userSet);
100
+            }
101
+            if (height !== topPanelHeight.current) {
102
+                store.dispatch(setFilmstripHeight(height));
103
+            }
93 104
         }
94 105
         break;
95 106
     }

+ 63
- 1
react/features/filmstrip/reducer.js Просмотреть файл

@@ -19,7 +19,11 @@ import {
19 19
     SET_VISIBLE_REMOTE_PARTICIPANTS,
20 20
     SET_VOLUME,
21 21
     SET_MAX_STAGE_PARTICIPANTS,
22
-    CLEAR_STAGE_PARTICIPANTS
22
+    CLEAR_STAGE_PARTICIPANTS,
23
+    SET_SCREENSHARING_TILE_DIMENSIONS,
24
+    SET_USER_FILMSTRIP_HEIGHT,
25
+    SET_FILMSTRIP_HEIGHT,
26
+    SET_TOP_PANEL_VISIBILITY
23 27
 } from './actionTypes';
24 28
 
25 29
 const DEFAULT_STATE = {
@@ -76,6 +80,11 @@ const DEFAULT_STATE = {
76 80
      */
77 81
     remoteParticipants: [],
78 82
 
83
+    /**
84
+     * The dimensions of the screenshare filmstrip.
85
+     */
86
+    screenshareFilmstripDimensions: {},
87
+
79 88
     /**
80 89
      * The stage filmstrip view dimensions.
81 90
      *
@@ -92,6 +101,27 @@ const DEFAULT_STATE = {
92 101
      */
93 102
     tileViewDimensions: {},
94 103
 
104
+    /**
105
+     * The height of the resizable top panel.
106
+     */
107
+    topPanelHeight: {
108
+        /**
109
+         * Current height. Affected by: user top panel resize,
110
+         * window resize.
111
+         */
112
+        current: null,
113
+
114
+        /**
115
+         * Height set by user resize. Used as the preferred height.
116
+         */
117
+        userSet: null
118
+    },
119
+
120
+    /**
121
+     * The indicator determines if the top panel is visible.
122
+     */
123
+    topPanelVisible: true,
124
+
95 125
     /**
96 126
      * The vertical view dimensions.
97 127
      *
@@ -227,6 +257,15 @@ ReducerRegistry.register(
227 257
                 ...state
228 258
             };
229 259
         }
260
+        case SET_FILMSTRIP_HEIGHT:{
261
+            return {
262
+                ...state,
263
+                topPanelHeight: {
264
+                    ...state.topPanelHeight,
265
+                    current: action.height
266
+                }
267
+            };
268
+        }
230 269
         case SET_FILMSTRIP_WIDTH: {
231 270
             return {
232 271
                 ...state,
@@ -236,6 +275,17 @@ ReducerRegistry.register(
236 275
                 }
237 276
             };
238 277
         }
278
+        case SET_USER_FILMSTRIP_HEIGHT: {
279
+            const { height } = action;
280
+
281
+            return {
282
+                ...state,
283
+                topPanelHeight: {
284
+                    current: height,
285
+                    userSet: height
286
+                }
287
+            };
288
+        }
239 289
         case SET_USER_FILMSTRIP_WIDTH: {
240 290
             const { width } = action;
241 291
 
@@ -283,6 +333,18 @@ ReducerRegistry.register(
283 333
                 activeParticipants: []
284 334
             };
285 335
         }
336
+        case SET_SCREENSHARING_TILE_DIMENSIONS: {
337
+            return {
338
+                ...state,
339
+                screenshareFilmstripDimensions: action.dimensions
340
+            };
341
+        }
342
+        case SET_TOP_PANEL_VISIBILITY: {
343
+            return {
344
+                ...state,
345
+                topPanelVisible: action.visible
346
+            };
347
+        }
286 348
         }
287 349
 
288 350
         return state;

+ 29
- 2
react/features/filmstrip/subscriber.web.js Просмотреть файл

@@ -14,6 +14,7 @@ import { getCurrentLayout, shouldDisplayTileView, LAYOUTS } from '../video-layou
14 14
 import {
15 15
     clearStageParticipants,
16 16
     setHorizontalViewDimensions,
17
+    setScreensharingTileDimensions,
17 18
     setStageFilmstripViewDimensions,
18 19
     setTileViewDimensions,
19 20
     setVerticalViewDimensions
@@ -23,7 +24,8 @@ import {
23 24
     DISPLAY_DRAWER_THRESHOLD
24 25
 } from './constants';
25 26
 import {
26
-    isFilmstripResizable
27
+    isFilmstripResizable,
28
+    isTopPanelEnabled
27 29
 } from './functions';
28 30
 
29 31
 import './subscriber.any';
@@ -176,7 +178,8 @@ StateListenerRegistry.register(
176 178
             visible: state['features/filmstrip'].visible,
177 179
             clientWidth: state['features/base/responsive-ui'].clientWidth,
178 180
             clientHeight: state['features/base/responsive-ui'].clientHeight,
179
-            tileView: state['features/video-layout'].tileViewEnabled
181
+            tileView: state['features/video-layout'].tileViewEnabled,
182
+            height: state['features/filmstrip'].topPanelHeight?.current
180 183
         };
181 184
     },
182 185
     /* listener */(_, store) => {
@@ -198,3 +201,27 @@ StateListenerRegistry.register(
198 201
             store.dispatch(selectParticipantInLargeVideo());
199 202
         }
200 203
     });
204
+
205
+/**
206
+ * Listens for changes to determine the size of the screenshare filmstrip.
207
+ */
208
+StateListenerRegistry.register(
209
+    /* selector */ state => {
210
+        return {
211
+            length: state['features/video-layout'].remoteScreenShares.length,
212
+            clientWidth: state['features/base/responsive-ui'].clientWidth,
213
+            clientHeight: state['features/base/responsive-ui'].clientHeight,
214
+            height: state['features/filmstrip'].topPanelHeight?.current,
215
+            width: state['features/filmstrip'].width?.current,
216
+            visible: state['features/filmstrip'].visible,
217
+            topPanelVisible: state['features/filmstrip'].topPanelVisible
218
+        };
219
+    },
220
+    /* listener */({ length }, store) => {
221
+        if (length >= 1 && isTopPanelEnabled(store.getState())) {
222
+            store.dispatch(setScreensharingTileDimensions());
223
+        }
224
+    }, {
225
+        deepEquals: true
226
+    });
227
+

Загрузка…
Отмена
Сохранить