Pārlūkot izejas kodu

ref(Filmstrip): Optimize resizes. (#4992)

* ref(Filmstrip): Optimize resizes.

* fix(thumbnails): resize.

* fix(thumbnails): Issue with height 0, width 0.

* doc(Filmstrip): Improve JSDoc.
j8
Hristo Terezov 5 gadus atpakaļ
vecāks
revīzija
31d9fb12c8
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam
37 mainītis faili ar 743 papildinājumiem un 697 dzēšanām
  1. 0
    7
      css/_utils.scss
  2. 10
    2
      css/_videolayout_default.scss
  3. 2
    0
      css/filmstrip/_horizontal_filmstrip.scss
  4. 1
    1
      css/filmstrip/_small_video.scss
  5. 38
    9
      css/filmstrip/_vertical_filmstrip.scss
  6. 4
    0
      css/filmstrip/_vertical_filmstrip_overrides.scss
  7. 1
    10
      modules/UI/UI.js
  8. 2
    3
      modules/UI/shared_video/SharedVideoThumb.js
  9. 1
    246
      modules/UI/util/UIUtil.js
  10. 98
    291
      modules/UI/videolayout/Filmstrip.js
  11. 8
    2
      modules/UI/videolayout/LargeVideoManager.js
  12. 3
    3
      modules/UI/videolayout/LocalVideo.js
  13. 2
    4
      modules/UI/videolayout/RemoteVideo.js
  14. 68
    8
      modules/UI/videolayout/SmallVideo.js
  15. 15
    72
      modules/UI/videolayout/VideoLayout.js
  16. 4
    8
      react/features/base/conference/middleware.js
  17. 6
    3
      react/features/base/react/components/web/BaseIndicator.js
  18. 9
    0
      react/features/base/responsive-ui/actionTypes.js
  19. 18
    3
      react/features/base/responsive-ui/actions.js
  20. 69
    0
      react/features/base/responsive-ui/middleware.web.js
  21. 16
    1
      react/features/base/responsive-ui/reducer.js
  22. 20
    0
      react/features/filmstrip/actionTypes.js
  23. 0
    0
      react/features/filmstrip/actions.native.js
  24. 54
    0
      react/features/filmstrip/actions.web.js
  25. 1
    0
      react/features/filmstrip/components/web/AudioMutedIndicator.js
  26. 61
    14
      react/features/filmstrip/components/web/Filmstrip.js
  27. 1
    0
      react/features/filmstrip/components/web/ModeratorIndicator.js
  28. 1
    0
      react/features/filmstrip/components/web/VideoMutedIndicator.js
  29. 5
    0
      react/features/filmstrip/constants.js
  30. 57
    0
      react/features/filmstrip/functions.web.js
  31. 2
    0
      react/features/filmstrip/index.js
  32. 0
    0
      react/features/filmstrip/middleware.native.js
  33. 74
    0
      react/features/filmstrip/middleware.web.js
  34. 30
    1
      react/features/filmstrip/reducer.js
  35. 0
    0
      react/features/filmstrip/subscriber.native.js
  36. 58
    0
      react/features/filmstrip/subscriber.web.js
  37. 4
    9
      react/features/video-layout/functions.js

+ 0
- 7
css/_utils.scss Parādīt failu

@@ -31,13 +31,6 @@
31 31
     display: inline-block !important;
32 32
 }
33 33
 
34
-/**
35
-* Shows as a list item
36
-**/
37
-.show-list-item {
38
-    display: list-item !important;
39
-}
40
-
41 34
 /**
42 35
  * Shows a flex element.
43 36
  */

+ 10
- 2
css/_videolayout_default.scss Parādīt failu

@@ -173,7 +173,9 @@
173 173
     &__hoverOverlay {
174 174
         background: rgba(0,0,0,.6);
175 175
         border-radius: $borderRadius;
176
-        position: relative;
176
+        position: absolute;
177
+        top: 0px;
178
+        left: 0px;
177 179
         width: 100%;
178 180
         height: 100%;
179 181
         visibility: hidden;
@@ -526,13 +528,16 @@
526 528
     display: flex;
527 529
     justify-content: center;
528 530
     height: 50%;
529
-    overflow: hidden;
530 531
     width: auto;
532
+    overflow: hidden;
531 533
 
532 534
     .userAvatar {
533 535
         height: 100%;
534 536
         object-fit: cover;
535 537
         width: 100%;
538
+        top: 0px;
539
+        left: 0px;
540
+        position: absolute;
536 541
     }
537 542
 }
538 543
 
@@ -555,6 +560,9 @@
555 560
 }
556 561
 
557 562
 .sharedVideoAvatar {
563
+    position: absolute;
564
+    left: 0px;
565
+    top: 0px;
558 566
     height: 100%;
559 567
     width: 100%;
560 568
     object-fit: cover;

+ 2
- 0
css/filmstrip/_horizontal_filmstrip.scss Parādīt failu

@@ -55,6 +55,7 @@
55 55
         &#filmstripLocalVideo {
56 56
             align-self: flex-end;
57 57
             display: block;
58
+            margin-bottom: 8px;
58 59
         }
59 60
 
60 61
         &.hidden {
@@ -108,6 +109,7 @@
108 109
              */
109 110
             padding: 1px 0;
110 111
             overflow-y: hidden;
112
+            overflow-x: scroll;
111 113
         }
112 114
     }
113 115
 

+ 1
- 1
css/filmstrip/_small_video.scss Parādīt failu

@@ -1,5 +1,5 @@
1 1
 .filmstrip__videos .videocontainer {
2
-    display: none;
2
+    display: inline-block;
3 3
     position: relative;
4 4
     background-size: contain;
5 5
     border: $thumbnailVideoBorder solid transparent;

+ 38
- 9
css/filmstrip/_vertical_filmstrip.scss Parādīt failu

@@ -25,6 +25,7 @@
25 25
     display: flex;
26 26
     flex-direction: column-reverse;
27 27
     height: 100%;
28
+    width: 100%;
28 29
     padding: ($desktopAppDragBarHeight - 5px) 5px 10px;
29 30
     /**
30 31
      * fixed positioning is necessary for remote menus and tooltips to pop
@@ -48,7 +49,6 @@
48 49
     .filmstrip__videos {
49 50
         @extend %align-right;
50 51
         bottom: 0;
51
-        overflow: visible !important;
52 52
         padding: 0;
53 53
         position:relative;
54 54
         right: 0;
@@ -67,6 +67,7 @@
67 67
             border: $thumbnailsBorder solid transparent;
68 68
             padding-left: 0;
69 69
             transition: right 2s;
70
+            width: 100%;
70 71
         }
71 72
     }
72 73
 
@@ -80,6 +81,16 @@
80 81
         flex-direction: column-reverse;
81 82
         height: auto;
82 83
         justify-content: flex-start;
84
+
85
+        #filmstripLocalVideoThumbnail {
86
+            width: calc(100% - 15px);
87
+
88
+            .videocontainer {
89
+                height: 0px;
90
+                width: 100%;
91
+    }
92
+        }
93
+
83 94
     }
84 95
 
85 96
     /**
@@ -96,18 +107,21 @@
96 107
 
97 108
         display: flex;
98 109
         flex: 1;
99
-        flex-direction: column;
110
+        flex-direction: column-reverse;
100 111
         height: auto;
101
-        justify-content: flex-end;
112
+        overflow-x: hidden;
113
+        overflow-y: scroll;
102 114
 
103 115
         #filmstripRemoteVideosContainer {
116
+            @include minHWAutoFix();
104 117
             flex-direction: column-reverse;
105
-            /**
106
-             * Add padding as a hack for Firefox not to show scrollbars when
107
-             * unnecessary.
108
-             */
109
-            padding: 1px 0;
110
-            overflow-x: hidden;
118
+            overflow: visible;
119
+            width: calc(100% - 8px); // 8px for margin + border of the thumbnails
120
+
121
+            .videocontainer {
122
+                height: 0px;
123
+                width: 100%;
124
+            }
111 125
         }
112 126
     }
113 127
 
@@ -160,9 +174,24 @@
160 174
     }
161 175
 }
162 176
 
177
+/**
178
+ * FF does not include the scroll width when calculating the size of the content. That's why we need to include
179
+ * ourselves the width of the scroll so that the remote videos are aligned with the local one.
180
+ */
181
+@mixin filmstripSizeWithoutScroll {
182
+    .vertical-filmstrip {
183
+        #remoteVideos #filmstripRemoteVideos {
184
+            #filmstripRemoteVideosContainer {
185
+                width: calc(100% - 15px) // 8 px - margins + border of the thumbnails; 7px - for the scroll
186
+            }
187
+        }
188
+    }
189
+}
190
+
163 191
 /** Firefox detection hack **/
164 192
 @-moz-document url-prefix() {
165 193
     @include undoColumnReverseVideos();
194
+    @include filmstripSizeWithoutScroll();
166 195
 }
167 196
 
168 197
 /** Edge detection hack **/

+ 4
- 0
css/filmstrip/_vertical_filmstrip_overrides.scss Parādīt failu

@@ -56,6 +56,10 @@
56 56
         transform: translate3d(0, 0, 0);
57 57
     }
58 58
 
59
+    .indicator-icon-container {
60
+        display: inline-block;
61
+    }
62
+
59 63
     .indicator-container {
60 64
         float: none;
61 65
     }

+ 1
- 10
modules/UI/UI.js Parādīt failu

@@ -11,7 +11,6 @@ import EtherpadManager from './etherpad/Etherpad';
11 11
 import SharedVideoManager from './shared_video/SharedVideo';
12 12
 
13 13
 import VideoLayout from './videolayout/VideoLayout';
14
-import Filmstrip from './videolayout/Filmstrip';
15 14
 
16 15
 import { getLocalParticipant } from '../../react/features/base/participants';
17 16
 import { toggleChat } from '../../react/features/chat';
@@ -158,8 +157,6 @@ UI.start = function() {
158 157
     // Set the defaults for prompt dialogs.
159 158
     $.prompt.setDefaults({ persistent: false });
160 159
 
161
-    Filmstrip.init(eventEmitter);
162
-
163 160
     VideoLayout.init(eventEmitter);
164 161
     if (!interfaceConfig.filmStripOnly) {
165 162
         VideoLayout.initLargeVideo();
@@ -202,7 +199,7 @@ UI.bindEvents = () => {
202 199
      *
203 200
      */
204 201
     function onResize() {
205
-        VideoLayout.resizeVideoArea();
202
+        VideoLayout.onResize();
206 203
     }
207 204
 
208 205
     // Resize and reposition videos in full screen mode.
@@ -353,12 +350,6 @@ UI.toggleFilmstrip = function() {
353 350
     APP.store.dispatch(setFilmstripVisible(!visible));
354 351
 };
355 352
 
356
-/**
357
- * Checks if the filmstrip is currently visible or not.
358
- * @returns {true} if the filmstrip is currently visible, and false otherwise.
359
- */
360
-UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
361
-
362 353
 /**
363 354
  * Toggles the visibility of the chat panel.
364 355
  */

+ 2
- 3
modules/UI/shared_video/SharedVideoThumb.js Parādīt failu

@@ -17,17 +17,16 @@ export default class SharedVideoThumb extends SmallVideo {
17 17
     constructor(participant, videoType, VideoLayout) {
18 18
         super(VideoLayout);
19 19
         this.id = participant.id;
20
-
20
+        this.isLocal = false;
21 21
         this.url = participant.id;
22 22
         this.setVideoType(videoType);
23 23
         this.videoSpanId = 'sharedVideoContainer';
24 24
         this.container = this.createContainer(this.videoSpanId);
25 25
         this.$container = $(this.container);
26
-
26
+        this._setThumbnailSize();
27 27
         this.bindHoverHandler();
28 28
         this.isVideoMuted = true;
29 29
         this.updateDisplayName();
30
-
31 30
         this.container.onclick = this._onContainerClick;
32 31
     }
33 32
 

+ 1
- 246
modules/UI/util/UIUtil.js Parādīt failu

@@ -1,22 +1,4 @@
1
-/* global $, interfaceConfig */
2
-
3
-/**
4
- * Associates the default display type with corresponding CSS class
5
- */
6
-const SHOW_CLASSES = {
7
-    'block': 'show',
8
-    'inline': 'show-inline',
9
-    'list-item': 'show-list-item'
10
-};
11
-
12
-/**
13
- * Contains sizes of thumbnails
14
- * @type {{SMALL: number, MEDIUM: number}}
15
- */
16
-const ThumbnailSizes = {
17
-    SMALL: 60,
18
-    MEDIUM: 80
19
-};
1
+/* global $ */
20 2
 
21 3
 /**
22 4
  * Created by hristo on 12/22/14.
@@ -30,32 +12,6 @@ const UIUtil = {
30 12
         return window.innerWidth;
31 13
     },
32 14
 
33
-    /**
34
-     * Changes the style class of the element given by id.
35
-     */
36
-    buttonClick(id, classname) {
37
-        // add the class to the clicked element
38
-        $(`#${id}`).toggleClass(classname);
39
-    },
40
-
41
-    /**
42
-     * Returns the text width for the given element.
43
-     *
44
-     * @param el the element
45
-     */
46
-    getTextWidth(el) {
47
-        return el.clientWidth + 1;
48
-    },
49
-
50
-    /**
51
-     * Returns the text height for the given element.
52
-     *
53
-     * @param el the element
54
-     */
55
-    getTextHeight(el) {
56
-        return el.clientHeight + 1;
57
-    },
58
-
59 15
     /**
60 16
      * Escapes the given text.
61 17
      */
@@ -64,27 +20,6 @@ const UIUtil = {
64 20
             .html();
65 21
     },
66 22
 
67
-    imageToGrayScale(canvas) {
68
-        const context = canvas.getContext('2d');
69
-        const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
70
-        const pixels = imgData.data;
71
-
72
-        for (let i = 0, n = pixels.length; i < n; i += 4) {
73
-            const grayscale
74
-                = (pixels[i] * 0.3)
75
-                    + (pixels[i + 1] * 0.59)
76
-                    + (pixels[i + 2] * 0.11);
77
-
78
-            pixels[i] = grayscale; // red
79
-            pixels[i + 1] = grayscale; // green
80
-            pixels[i + 2] = grayscale; // blue
81
-            // pixels[i+3]              is alpha
82
-        }
83
-
84
-        // redraw the image in black & white
85
-        context.putImageData(imgData, 0, 0);
86
-    },
87
-
88 23
     /**
89 24
      * Inserts given child element as the first one into the container.
90 25
      * @param container the container to which new child element will be added
@@ -100,81 +35,6 @@ const UIUtil = {
100 35
         }
101 36
     },
102 37
 
103
-    /**
104
-     * Indicates if Authentication Section should be shown
105
-     *
106
-     * @returns {boolean}
107
-     */
108
-    isAuthenticationEnabled() {
109
-        return interfaceConfig.AUTHENTICATION_ENABLE;
110
-    },
111
-
112
-    /**
113
-     * Shows / hides the element given by id.
114
-     *
115
-     * @param {string|HTMLElement} idOrElement the identifier or the element
116
-     *        to show/hide
117
-     * @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
118
-     */
119
-    setVisible(id, visible) {
120
-        let element;
121
-
122
-        if (id instanceof HTMLElement) {
123
-            element = id;
124
-        } else {
125
-            element = document.getElementById(id);
126
-        }
127
-
128
-        if (!element) {
129
-            return;
130
-        }
131
-
132
-        if (!visible) {
133
-            element.classList.add('hide');
134
-        } else if (element.classList.contains('hide')) {
135
-            element.classList.remove('hide');
136
-        }
137
-
138
-        const type = this._getElementDefaultDisplay(element.tagName);
139
-        const className = SHOW_CLASSES[type];
140
-
141
-        if (visible) {
142
-            element.classList.add(className);
143
-        } else if (element.classList.contains(className)) {
144
-            element.classList.remove(className);
145
-        }
146
-    },
147
-
148
-    /**
149
-     * Returns default display style for the tag
150
-     * @param tag
151
-     * @returns {*}
152
-     * @private
153
-     */
154
-    _getElementDefaultDisplay(tag) {
155
-        const tempElement = document.createElement(tag);
156
-
157
-        document.body.appendChild(tempElement);
158
-        const style = window.getComputedStyle(tempElement).display;
159
-
160
-        document.body.removeChild(tempElement);
161
-
162
-        return style;
163
-    },
164
-
165
-    /**
166
-     * Shows / hides the element with the given jQuery selector.
167
-     *
168
-     * @param {jQuery} jquerySelector the jQuery selector of the element to
169
-     * show / shide
170
-     * @param {boolean} isVisible
171
-     */
172
-    setVisibleBySelector(jquerySelector, isVisible) {
173
-        if (jquerySelector && jquerySelector.length > 0) {
174
-            jquerySelector.css('visibility', isVisible ? 'visible' : 'hidden');
175
-        }
176
-    },
177
-
178 38
     /**
179 39
      * Redirects to a given URL.
180 40
      *
@@ -200,17 +60,6 @@ const UIUtil = {
200 60
             || document.msFullscreenElement);
201 61
     },
202 62
 
203
-    /**
204
-      * Create html attributes string out of object properties.
205
-      * @param {Object} attrs object with properties
206
-      * @returns {String} string of html element attributes
207
-      */
208
-    attrsToString(attrs) {
209
-        return (
210
-            Object.keys(attrs).map(key => ` ${key}="${attrs[key]}"`)
211
-.join(' '));
212
-    },
213
-
214 63
     /**
215 64
      * Checks if the given DOM element is currently visible. The offsetParent
216 65
      * will be null if the "display" property of the element or any of its
@@ -220,100 +69,6 @@ const UIUtil = {
220 69
      */
221 70
     isVisible(el) {
222 71
         return el.offsetParent !== null;
223
-    },
224
-
225
-    /**
226
-     * Shows / hides the element given by {selector} and sets a timeout if the
227
-     * {hideDelay} is set to a value > 0.
228
-     * @param selector the jquery selector of the element to show/hide.
229
-     * @param show a {boolean} that indicates if the element should be shown or
230
-     * hidden
231
-     * @param hideDelay the value in milliseconds to wait before hiding the
232
-     * element
233
-     */
234
-    animateShowElement(selector, show, hideDelay) {
235
-        if (show) {
236
-            if (!selector.is(':visible')) {
237
-                selector.css('display', 'inline-block');
238
-            }
239
-
240
-            selector.fadeIn(300,
241
-                () => {
242
-                    selector.css({ opacity: 1 });
243
-                }
244
-            );
245
-
246
-            if (hideDelay && hideDelay > 0) {
247
-                setTimeout(
248
-                    () => {
249
-                        selector.fadeOut(
250
-                            300,
251
-                            () => {
252
-                                selector.css({ opacity: 0 });
253
-                            });
254
-                    },
255
-                    hideDelay);
256
-            }
257
-        } else {
258
-            selector.fadeOut(300,
259
-                () => {
260
-                    selector.css({ opacity: 0 });
261
-                }
262
-            );
263
-        }
264
-    },
265
-
266
-    /**
267
-     * Parses the given cssValue as an Integer. If the value is not a number
268
-     * we return 0 instead of NaN.
269
-     * @param cssValue the string value we obtain when querying css properties
270
-     */
271
-    parseCssInt(cssValue) {
272
-        return parseInt(cssValue, 10) || 0;
273
-    },
274
-
275
-    /**
276
-     * Adds href value to 'a' link jquery object. If link value is null,
277
-     * undefined or empty string, disables the link.
278
-     * @param {object} aLinkElement the jquery object
279
-     * @param {string} link the link value
280
-     */
281
-    setLinkHref(aLinkElement, link) {
282
-        if (link) {
283
-            aLinkElement.attr('href', link);
284
-        } else {
285
-            aLinkElement.css({
286
-                'pointer-events': 'none',
287
-                'cursor': 'default'
288
-            });
289
-        }
290
-    },
291
-
292
-    /**
293
-     * Returns font size for indicators according to current
294
-     * height of thumbnail
295
-     * @param {Number} [thumbnailHeight] - current height of thumbnail
296
-     * @returns {Number} - font size for current height
297
-     */
298
-    getIndicatorFontSize(thumbnailHeight) {
299
-        const height = typeof thumbnailHeight === 'undefined'
300
-            ? $('#localVideoContainer').height() : thumbnailHeight;
301
-
302
-        const { SMALL, MEDIUM } = ThumbnailSizes;
303
-        const IndicatorFontSizes = interfaceConfig.INDICATOR_FONT_SIZES || {
304
-            SMALL: 5,
305
-            MEDIUM: 6,
306
-            NORMAL: 8
307
-        };
308
-        let fontSize = IndicatorFontSizes.NORMAL;
309
-
310
-        if (height <= SMALL) {
311
-            fontSize = IndicatorFontSizes.SMALL;
312
-        } else if (height > SMALL && height <= MEDIUM) {
313
-            fontSize = IndicatorFontSizes.MEDIUM;
314
-        }
315
-
316
-        return fontSize;
317 72
     }
318 73
 };
319 74
 

+ 98
- 291
modules/UI/videolayout/Filmstrip.js Parādīt failu

@@ -1,33 +1,8 @@
1 1
 /* global $, APP, interfaceConfig */
2 2
 
3
-import {
4
-    LAYOUTS,
5
-    getCurrentLayout,
6
-    getMaxColumnCount,
7
-    getTileViewGridDimensions,
8
-    shouldDisplayTileView
9
-} from '../../../react/features/video-layout';
10
-
11
-import UIUtil from '../util/UIUtil';
3
+import { isFilmstripVisible } from '../../../react/features/filmstrip';
12 4
 
13 5
 const Filmstrip = {
14
-    /**
15
-     * Caches jquery lookups of the filmstrip for future use.
16
-     */
17
-    init() {
18
-        this.filmstripContainerClassName = 'filmstrip';
19
-        this.filmstrip = $('#remoteVideos');
20
-        this.filmstripRemoteVideos = $('#filmstripRemoteVideosContainer');
21
-    },
22
-
23
-    /**
24
-     * Shows if filmstrip is visible
25
-     * @returns {boolean}
26
-     */
27
-    isFilmstripVisible() {
28
-        return APP.store.getState()['features/filmstrip'].visible;
29
-    },
30
-
31 6
     /**
32 7
      * Returns the height of filmstrip
33 8
      * @returns {number} height
@@ -36,8 +11,8 @@ const Filmstrip = {
36 11
         // FIXME Make it more clear the getFilmstripHeight check is used in
37 12
         // horizontal film strip mode for calculating how tall large video
38 13
         // display should be.
39
-        if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) {
40
-            return $(`.${this.filmstripContainerClassName}`).outerHeight();
14
+        if (isFilmstripVisible(APP.store) && !interfaceConfig.VERTICAL_FILMSTRIP) {
15
+            return $('.filmstrip').outerHeight();
41 16
         }
42 17
 
43 18
         return 0;
@@ -48,303 +23,135 @@ const Filmstrip = {
48 23
      * @returns {number} width
49 24
      */
50 25
     getFilmstripWidth() {
51
-        return this.isFilmstripVisible()
52
-            ? this.filmstrip.outerWidth()
53
-                - parseInt(this.filmstrip.css('paddingLeft'), 10)
54
-                - parseInt(this.filmstrip.css('paddingRight'), 10)
55
-            : 0;
56
-    },
26
+        const filmstrip = $('#remoteVideos');
57 27
 
58
-    /**
59
-     * Calculates the size for thumbnails: local and remote one
60
-     * @returns {*|{localVideo, remoteVideo}}
61
-     */
62
-    calculateThumbnailSize() {
63
-        if (shouldDisplayTileView(APP.store.getState())) {
64
-            return this._calculateThumbnailSizeForTileView();
65
-        }
66
-
67
-        const { availableWidth, availableHeight } = this.calculateAvailableSize();
68
-
69
-        return this.calculateThumbnailSizeFromAvailable(availableWidth, availableHeight);
28
+        return isFilmstripVisible(APP.store)
29
+            ? filmstrip.outerWidth()
30
+                - parseInt(filmstrip.css('paddingLeft'), 10)
31
+                - parseInt(filmstrip.css('paddingRight'), 10)
32
+            : 0;
70 33
     },
71 34
 
72 35
     /**
73
-     * Calculates available size for one thumbnail according to
74
-     * the current window size.
36
+     * Resizes thumbnails for tile view.
75 37
      *
76
-     * @returns {{availableWidth: number, availableHeight: number}}
38
+     * @param {number} width - The new width of the thumbnails.
39
+     * @param {number} height - The new height of the thumbnails.
40
+     * @param {boolean} forceUpdate
41
+     * @returns {void}
77 42
      */
78
-    calculateAvailableSize() {
79
-        /**
80
-         * If the videoAreaAvailableWidth is set we use this one to calculate
81
-         * the filmstrip width, because we're probably in a state where the
82
-         * filmstrip size hasn't been updated yet, but it will be.
83
-         */
84
-        const videoAreaAvailableWidth
85
-            = UIUtil.getAvailableVideoWidth()
86
-                - this._getFilmstripExtraPanelsWidth()
87
-                - UIUtil.parseCssInt(this.filmstrip.css('right'), 10)
88
-                - UIUtil.parseCssInt(this.filmstrip.css('paddingLeft'), 10)
89
-                - UIUtil.parseCssInt(this.filmstrip.css('paddingRight'), 10)
90
-                - UIUtil.parseCssInt(this.filmstrip.css('borderLeftWidth'), 10)
91
-                - UIUtil.parseCssInt(this.filmstrip.css('borderRightWidth'), 10)
92
-                - 5;
93
-
94
-        const availableHeight = Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120, window.innerHeight - 18);
95
-        let availableWidth = videoAreaAvailableWidth;
96
-        const localVideoContainer = $('#localVideoContainer');
43
+    resizeThumbnailsForTileView(width, height, forceUpdate = false) {
44
+        const thumbs = this._getThumbs(!forceUpdate);
45
+        const avatarSize = height / 2;
97 46
 
98
-        // If local thumb is not hidden
99
-        if (!localVideoContainer.has('hidden')) {
100
-            availableWidth = Math.floor(
101
-                videoAreaAvailableWidth - (
102
-                    UIUtil.parseCssInt(localVideoContainer.css('borderLeftWidth'), 10)
103
-                    + UIUtil.parseCssInt(localVideoContainer.css('borderRightWidth'), 10)
104
-                    + UIUtil.parseCssInt(localVideoContainer.css('paddingLeft'), 10)
105
-                    + UIUtil.parseCssInt(localVideoContainer.css('paddingRight'), 10)
106
-                    + UIUtil.parseCssInt(localVideoContainer.css('marginLeft'), 10)
107
-                    + UIUtil.parseCssInt(localVideoContainer.css('marginRight'), 10))
108
-            );
47
+        if (thumbs.localThumb) {
48
+            thumbs.localThumb.css({
49
+                'padding-top': '',
50
+                height: `${height}px`,
51
+                'min-height': `${height}px`,
52
+                'min-width': `${width}px`,
53
+                width: `${width}px`
54
+            });
109 55
         }
110 56
 
111
-        return {
112
-            availableHeight,
113
-            availableWidth
114
-        };
115
-    },
116
-
117
-    /**
118
-     * Traverse all elements inside the filmstrip
119
-     * and calculates the sum of all of them except
120
-     * remote videos element. Used for calculation of
121
-     * available width for video thumbnails.
122
-     *
123
-     * @returns {number} calculated width
124
-     * @private
125
-     */
126
-    _getFilmstripExtraPanelsWidth() {
127
-        const className = this.filmstripContainerClassName;
128
-        let width = 0;
129
-
130
-        $(`.${className}`)
131
-            .children()
132
-            .each(function() {
133
-                /* eslint-disable no-invalid-this */
134
-                if (this.id !== 'remoteVideos') {
135
-                    width += $(this).outerWidth();
136
-                }
137
-                /* eslint-enable no-invalid-this */
57
+        if (thumbs.remoteThumbs) {
58
+            thumbs.remoteThumbs.css({
59
+                'padding-top': '',
60
+                height: `${height}px`,
61
+                'min-height': `${height}px`,
62
+                'min-width': `${width}px`,
63
+                width: `${width}px`
138 64
             });
139
-
140
-        return width;
141
-    },
142
-
143
-    /**
144
-     Calculate the thumbnail size in order to fit all the thumnails in passed
145
-     * dimensions.
146
-     * NOTE: Here we assume that the remote and local thumbnails are with the
147
-     * same height.
148
-     * @param {int} availableWidth the maximum width for all thumbnails
149
-     * @param {int} availableHeight the maximum height for all thumbnails
150
-     * @returns {{localVideo, remoteVideo}}
151
-     */
152
-    calculateThumbnailSizeFromAvailable(availableWidth, availableHeight) {
153
-        /**
154
-         * Let:
155
-         * lW - width of the local thumbnail
156
-         * rW - width of the remote thumbnail
157
-         * h - the height of the thumbnails
158
-         * remoteRatio - width:height for the remote thumbnail
159
-         * localRatio - width:height for the local thumbnail
160
-         * remoteThumbsInRow - number of remote thumbnails in a row (we have
161
-         * only one local thumbnail) next to the local thumbnail. In vertical
162
-         * filmstrip mode, this will always be 0.
163
-         *
164
-         * Since the height for local thumbnail = height for remote thumbnail
165
-         * and we know the ratio (width:height) for the local and for the
166
-         * remote thumbnail we can find rW/lW:
167
-         * rW / remoteRatio = lW / localRatio then -
168
-         * remoteLocalWidthRatio = rW / lW = remoteRatio / localRatio
169
-         * and rW = lW * remoteRatio / localRatio = lW * remoteLocalWidthRatio
170
-         * And the total width for the thumbnails is:
171
-         * totalWidth = rW * remoteThumbsInRow + lW
172
-         * = lW * remoteLocalWidthRatio * remoteThumbsInRow + lW =
173
-         * lW * (remoteLocalWidthRatio * remoteThumbsInRow + 1)
174
-         * and the h = lW/localRatio
175
-         *
176
-         * In order to fit all the thumbails in the area defined by
177
-         * availableWidth * availableHeight we should check one of the
178
-         * following options:
179
-         * 1) if availableHeight == h - totalWidth should be less than
180
-         * availableWidth
181
-         * 2) if availableWidth ==  totalWidth - h should be less than
182
-         * availableHeight
183
-         *
184
-         * 1) or 2) will be true and we are going to use it to calculate all
185
-         * sizes.
186
-         *
187
-         * if 1) is true that means that
188
-         * availableHeight/h > availableWidth/totalWidth otherwise 2) is true
189
-         */
190
-
191
-        const remoteLocalWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
192
-        const lW = Math.min(availableWidth, availableHeight * interfaceConfig.LOCAL_THUMBNAIL_RATIO);
193
-        const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
194
-
195
-        const remoteVideoWidth = lW * remoteLocalWidthRatio;
196
-
197
-        let localVideo;
198
-
199
-        if (interfaceConfig.VERTICAL_FILMSTRIP) {
200
-            localVideo = {
201
-                thumbWidth: remoteVideoWidth,
202
-                thumbHeight: h * remoteLocalWidthRatio
203
-            };
204
-        } else {
205
-            localVideo = {
206
-                thumbWidth: lW,
207
-                thumbHeight: h
208
-            };
209 65
         }
210 66
 
211
-        return {
212
-            localVideo,
213
-            remoteVideo: {
214
-                thumbWidth: remoteVideoWidth,
215
-                thumbHeight: h
216
-            }
217
-        };
67
+        $('.avatar-container').css({
68
+            height: `${avatarSize}px`,
69
+            width: `${avatarSize}px`
70
+        });
218 71
     },
219 72
 
220 73
     /**
221
-     * Calculates the size for thumbnails when in tile view layout.
74
+     * Resizes thumbnails for horizontal view.
222 75
      *
223
-     * @returns {{localVideo, remoteVideo}}
76
+     * @param {Object} dimensions - The new dimensions of the thumbnails.
77
+     * @param {boolean} forceUpdate
78
+     * @returns {void}
224 79
      */
225
-    _calculateThumbnailSizeForTileView() {
226
-        const tileAspectRatio = 16 / 9;
227
-
228
-        // The distance from the top and bottom of the screen, as set by CSS, to
229
-        // avoid overlapping UI elements.
230
-        const topBottomPadding = 200;
231
-
232
-        // Minimum space to keep between the sides of the tiles and the sides
233
-        // of the window.
234
-        const sideMargins = 30 * 2;
80
+    resizeThumbnailsForHorizontalView({ local = {}, remote = {} }, forceUpdate = false) {
81
+        const thumbs = this._getThumbs(!forceUpdate);
235 82
 
236
-        const state = APP.store.getState();
237
-
238
-        const viewWidth = document.body.clientWidth - sideMargins;
239
-        const viewHeight = document.body.clientHeight - topBottomPadding;
83
+        if (thumbs.localThumb) {
84
+            const { height, width } = local;
85
+            const avatarSize = height / 2;
240 86
 
241
-        const {
242
-            columns,
243
-            visibleRows
244
-        } = getTileViewGridDimensions(state, getMaxColumnCount());
245
-        const initialWidth = viewWidth / columns;
246
-        const aspectRatioHeight = initialWidth / tileAspectRatio;
87
+            thumbs.localThumb.css({
88
+                height: `${height}px`,
89
+                'min-height': `${height}px`,
90
+                'min-width': `${width}px`,
91
+                width: `${width}px`
92
+            });
93
+            $('#localVideoContainer > .avatar-container').css({
94
+                height: `${avatarSize}px`,
95
+                width: `${avatarSize}px`
96
+            });
97
+        }
247 98
 
248
-        const heightOfEach = Math.floor(Math.min(
249
-            aspectRatioHeight,
250
-            viewHeight / visibleRows
251
-        ));
252
-        const widthOfEach = Math.floor(tileAspectRatio * heightOfEach);
99
+        if (thumbs.remoteThumbs) {
100
+            const { height, width } = remote;
101
+            const avatarSize = height / 2;
253 102
 
254
-        return {
255
-            localVideo: {
256
-                thumbWidth: widthOfEach,
257
-                thumbHeight: heightOfEach
258
-            },
259
-            remoteVideo: {
260
-                thumbWidth: widthOfEach,
261
-                thumbHeight: heightOfEach
262
-            }
263
-        };
103
+            thumbs.remoteThumbs.css({
104
+                height: `${height}px`,
105
+                'min-height': `${height}px`,
106
+                'min-width': `${width}px`,
107
+                width: `${width}px`
108
+            });
109
+            $('#filmstripRemoteVideosContainer > span > .avatar-container').css({
110
+                height: `${avatarSize}px`,
111
+                width: `${avatarSize}px`
112
+            });
113
+        }
264 114
     },
265 115
 
266 116
     /**
267
-     * Resizes thumbnails
268
-     * @param local
269
-     * @param remote
270
-     * @param forceUpdate
271
-     * @returns {Promise}
117
+     * Resizes thumbnails for vertical view.
118
+     *
119
+     * @returns {void}
272 120
      */
273
-    // eslint-disable-next-line max-params
274
-    resizeThumbnails(local, remote, forceUpdate = false) {
275
-        const state = APP.store.getState();
276
-
277
-        if (shouldDisplayTileView(state)) {
278
-            // The size of the side margins for each tile as set in CSS.
279
-            const sideMargins = 10 * 2;
280
-            const { columns, rows } = getTileViewGridDimensions(state, getMaxColumnCount());
281
-            const hasOverflow = rows > columns;
282
-
283
-            // Width is set so that the flex layout can automatically wrap
284
-            // tiles onto new rows.
285
-            this.filmstripRemoteVideos.css({ width: (local.thumbWidth * columns) + (columns * sideMargins) });
286
-            this.filmstripRemoteVideos.toggleClass('has-overflow', hasOverflow);
287
-        } else {
288
-            this.filmstripRemoteVideos.css('width', '');
289
-        }
290
-
291
-        const thumbs = this.getThumbs(!forceUpdate);
121
+    resizeThumbnailsForVerticalView() {
122
+        const thumbs = this._getThumbs(true);
292 123
 
293 124
         if (thumbs.localThumb) {
294
-            // eslint-disable-next-line no-shadow
125
+            const heightToWidthPercent = 100 / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
126
+
295 127
             thumbs.localThumb.css({
296
-                display: 'inline-block',
297
-                height: `${local.thumbHeight}px`,
298
-                'min-height': `${local.thumbHeight}px`,
299
-                'min-width': `${local.thumbWidth}px`,
300
-                width: `${local.thumbWidth}px`
128
+                'padding-top': `${heightToWidthPercent}%`,
129
+                width: '',
130
+                height: '',
131
+                'min-width': '',
132
+                'min-height': ''
301 133
             });
302
-
303
-            const avatarSize = local.thumbHeight / 2;
304
-
305
-            thumbs.localThumb.find('.avatar-container')
306
-                .height(avatarSize)
307
-                .width(avatarSize);
308
-        }
309
-
310
-        if (thumbs.remoteThumbs) {
311
-            thumbs.remoteThumbs.css({
312
-                display: 'inline-block',
313
-                height: `${remote.thumbHeight}px`,
314
-                'min-height': `${remote.thumbHeight}px`,
315
-                'min-width': `${remote.thumbWidth}px`,
316
-                width: `${remote.thumbWidth}px`
134
+            $('#localVideoContainer > .avatar-container').css({
135
+                height: '50%',
136
+                width: `${heightToWidthPercent / 2}%`
317 137
             });
318
-
319
-            const avatarSize = remote.thumbHeight / 2;
320
-
321
-            thumbs.remoteThumbs.find('.avatar-container')
322
-                .height(avatarSize)
323
-                .width(avatarSize);
324 138
         }
325 139
 
326
-        const currentLayout = getCurrentLayout(APP.store.getState());
140
+        if (thumbs.remoteThumbs) {
141
+            const heightToWidthPercent = 100 / interfaceConfig.REMOTE_THUMBNAIL_RATIO;
327 142
 
328
-        // Let CSS take care of height in vertical filmstrip mode.
329
-        if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
330
-            $('#filmstripLocalVideo').css({
331
-                // adds 4 px because of small video 2px border
332
-                width: `${local.thumbWidth + 4}px`
143
+            thumbs.remoteThumbs.css({
144
+                'padding-top': `${heightToWidthPercent}%`,
145
+                width: '',
146
+                height: '',
147
+                'min-width': '',
148
+                'min-height': ''
333 149
             });
334
-        } else if (currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW) {
335
-            this.filmstrip.css({
336
-                // adds 4 px because of small video 2px border and 10px margin for the scroll
337
-                height: `${remote.thumbHeight + 14}px`
150
+            $('#filmstripRemoteVideosContainer > span > .avatar-container').css({
151
+                height: '50%',
152
+                width: `${heightToWidthPercent / 2}%`
338 153
             });
339 154
         }
340
-
341
-        const { localThumb } = this.getThumbs();
342
-        const height = localThumb ? localThumb.height() : 0;
343
-        const fontSize = UIUtil.getIndicatorFontSize(height);
344
-
345
-        this.filmstrip.find('.indicator').css({
346
-            'font-size': `${fontSize}px`
347
-        });
348 155
     },
349 156
 
350 157
     /**
@@ -352,7 +159,7 @@ const Filmstrip = {
352 159
      * @param onlyVisible
353 160
      * @returns {object} thumbnails
354 161
      */
355
-    getThumbs(onlyVisible = false) {
162
+    _getThumbs(onlyVisible = false) {
356 163
         let selector = 'span';
357 164
 
358 165
         if (onlyVisible) {
@@ -360,7 +167,7 @@ const Filmstrip = {
360 167
         }
361 168
 
362 169
         const localThumb = $('#localVideoContainer');
363
-        const remoteThumbs = this.filmstripRemoteVideos.children(selector);
170
+        const remoteThumbs = $('#filmstripRemoteVideosContainer').children(selector);
364 171
 
365 172
         // Exclude the local video container if it has been hidden.
366 173
         if (localThumb.hasClass('hidden')) {

+ 8
- 2
modules/UI/videolayout/LargeVideoManager.js Parādīt failu

@@ -490,9 +490,15 @@ export default class LargeVideoManager {
490 490
             show = APP.conference.isConnectionInterrupted();
491 491
         }
492 492
 
493
-        const id = 'localConnectionMessage';
493
+        const element = document.getElementById('localConnectionMessage');
494 494
 
495
-        UIUtil.setVisible(id, show);
495
+        if (element) {
496
+            if (show) {
497
+                element.classList.add('show');
498
+            } else {
499
+                element.classList.remove('show');
500
+            }
501
+        }
496 502
 
497 503
         if (show) {
498 504
             // Avatar message conflicts with 'videoConnectionMessage',

+ 3
- 3
modules/UI/videolayout/LocalVideo.js Parādīt failu

@@ -33,6 +33,8 @@ export default class LocalVideo extends SmallVideo {
33 33
         this.streamEndedCallback = streamEndedCallback;
34 34
         this.container = this.createContainer();
35 35
         this.$container = $(this.container);
36
+        this.isLocal = true;
37
+        this._setThumbnailSize();
36 38
         this.updateDOMLocation();
37 39
 
38 40
         this.localVideoId = null;
@@ -40,10 +42,8 @@ export default class LocalVideo extends SmallVideo {
40 42
         if (!config.disableLocalVideoFlip) {
41 43
             this._buildContextMenu();
42 44
         }
43
-        this.isLocal = true;
44 45
         this.emitter = emitter;
45
-        this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
46
-            ? 'left top' : 'top center';
46
+        this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP ? 'left top' : 'top center';
47 47
 
48 48
         Object.defineProperty(this, 'id', {
49 49
             get() {

+ 2
- 4
modules/UI/videolayout/RemoteVideo.js Parādīt failu

@@ -20,10 +20,7 @@ import {
20 20
     REMOTE_CONTROL_MENU_STATES,
21 21
     RemoteVideoMenuTriggerButton
22 22
 } from '../../../react/features/remote-video-menu';
23
-import {
24
-    LAYOUTS,
25
-    getCurrentLayout
26
-} from '../../../react/features/video-layout';
23
+import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
27 24
 /* eslint-enable no-unused-vars */
28 25
 
29 26
 const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -129,6 +126,7 @@ export default class RemoteVideo extends SmallVideo {
129 126
     addRemoteVideoContainer() {
130 127
         this.container = createContainer(this.videoSpanId);
131 128
         this.$container = $(this.container);
129
+        this._setThumbnailSize();
132 130
         this.initBrowserSpecificProperties();
133 131
         this.updateRemoteVideoMenu();
134 132
         this.addAudioLevelIndicator();

+ 68
- 8
modules/UI/videolayout/SmallVideo.js Parādīt failu

@@ -34,7 +34,6 @@ import {
34 34
 
35 35
 const logger = require('jitsi-meet-logger').getLogger(__filename);
36 36
 
37
-import UIUtil from '../util/UIUtil';
38 37
 import UIEvents from '../../../service/UI/UIEvents';
39 38
 
40 39
 /**
@@ -629,8 +628,7 @@ export default class SmallVideo {
629 628
                 <Provider store = { APP.store }>
630 629
                     <AvatarDisplay
631 630
                         className = 'userAvatar'
632
-                        participantId = { this.id }
633
-                        size = { this.$avatar().width() } />
631
+                        participantId = { this.id } />
634 632
                 </Provider>,
635 633
                 thumbnail
636 634
             );
@@ -801,7 +799,8 @@ export default class SmallVideo {
801 799
             return;
802 800
         }
803 801
 
804
-        const iconSize = UIUtil.getIndicatorFontSize();
802
+        const { NORMAL = 8 } = interfaceConfig.INDICATOR_FONT_SIZES || {};
803
+        const iconSize = NORMAL;
805 804
         const showConnectionIndicator = this.videoIsHovered || !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED;
806 805
         const state = APP.store.getState();
807 806
         const currentLayout = getCurrentLayout(state);
@@ -830,11 +829,9 @@ export default class SmallVideo {
830 829
                                     connectionStatus = { this._connectionStatus }
831 830
                                     iconSize = { iconSize }
832 831
                                     isLocalVideo = { this.isLocal }
833
-                                    enableStatsDisplay
834
-                                        = { !interfaceConfig.filmStripOnly }
832
+                                    enableStatsDisplay = { !interfaceConfig.filmStripOnly }
835 833
                                     participantId = { this.id }
836
-                                    statsPopoverPosition
837
-                                        = { statsPopoverPosition } />
834
+                                    statsPopoverPosition = { statsPopoverPosition } />
838 835
                                 : null }
839 836
                             <RaisedHandIndicator
840 837
                                 iconSize = { iconSize }
@@ -936,4 +933,67 @@ export default class SmallVideo {
936 933
         this._popoverIsHovered = popoverIsHovered;
937 934
         this.updateView();
938 935
     }
936
+
937
+    /**
938
+     * Sets the size of the thumbnail.
939
+     */
940
+    _setThumbnailSize() {
941
+        const layout = getCurrentLayout(APP.store.getState());
942
+        const heightToWidthPercent = 100
943
+                / (this.isLocal ? interfaceConfig.LOCAL_THUMBNAIL_RATIO : interfaceConfig.REMOTE_THUMBNAIL_RATIO);
944
+
945
+        switch (layout) {
946
+        case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
947
+            this.$container.css('padding-top', `${heightToWidthPercent}%`);
948
+            this.$avatar().css({
949
+                height: '50%',
950
+                width: `${heightToWidthPercent / 2}%`
951
+            });
952
+            break;
953
+        }
954
+        case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
955
+            const state = APP.store.getState();
956
+            const { local, remote } = state['features/filmstrip'].horizontalViewDimensions;
957
+            const size = this.isLocal ? local : remote;
958
+
959
+            if (typeof size !== 'undefined') {
960
+                const { height, width } = size;
961
+                const avatarSize = height / 2;
962
+
963
+                this.$container.css({
964
+                    height: `${height}px`,
965
+                    'min-height': `${height}px`,
966
+                    'min-width': `${width}px`,
967
+                    width: `${width}px`
968
+                });
969
+                this.$avatar().css({
970
+                    height: `${avatarSize}px`,
971
+                    width: `${avatarSize}px`
972
+                });
973
+            }
974
+            break;
975
+        }
976
+        case LAYOUTS.TILE_VIEW: {
977
+            const state = APP.store.getState();
978
+            const { thumbnailSize } = state['features/filmstrip'].tileViewDimensions;
979
+
980
+            if (typeof thumbnailSize !== 'undefined') {
981
+                const { height, width } = thumbnailSize;
982
+                const avatarSize = height / 2;
983
+
984
+                this.$container.css({
985
+                    height: `${height}px`,
986
+                    'min-height': `${height}px`,
987
+                    'min-width': `${width}px`,
988
+                    width: `${width}px`
989
+                });
990
+                this.$avatar().css({
991
+                    height: `${avatarSize}px`,
992
+                    width: `${avatarSize}px`
993
+                });
994
+            }
995
+            break;
996
+        }
997
+        }
998
+    }
939 999
 }

+ 15
- 72
modules/UI/videolayout/VideoLayout.js Parādīt failu

@@ -1,10 +1,6 @@
1 1
 /* global APP, $, interfaceConfig  */
2 2
 const logger = require('jitsi-meet-logger').getLogger(__filename);
3 3
 
4
-import {
5
-    getNearestReceiverVideoQualityLevel,
6
-    setMaxReceiverVideoQuality
7
-} from '../../../react/features/base/conference';
8 4
 import {
9 5
     JitsiParticipantConnectionStatus
10 6
 } from '../../../react/features/base/lib-jitsi-meet';
@@ -14,13 +10,9 @@ import {
14 10
     getPinnedParticipant,
15 11
     pinParticipant
16 12
 } from '../../../react/features/base/participants';
17
-import {
18
-    shouldDisplayTileView
19
-} from '../../../react/features/video-layout';
20 13
 import { SHARED_VIDEO_CONTAINER_TYPE } from '../shared_video/SharedVideo';
21 14
 import SharedVideoThumb from '../shared_video/SharedVideoThumb';
22 15
 
23
-import Filmstrip from './Filmstrip';
24 16
 import UIEvents from '../../../service/UI/UIEvents';
25 17
 
26 18
 import RemoteVideo from './RemoteVideo';
@@ -87,11 +79,6 @@ const VideoLayout = {
87 79
         // sets default video type of local video
88 80
         // FIXME container type is totally different thing from the video type
89 81
         localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
90
-
91
-        // if we do not resize the thumbs here, if there is no video device
92
-        // the local video thumb maybe one pixel
93
-        this.resizeThumbnails(true);
94
-
95 82
         this.registerListeners();
96 83
     },
97 84
 
@@ -331,8 +318,6 @@ const VideoLayout = {
331 318
             remoteVideo.setVideoType(VIDEO_CONTAINER_TYPE);
332 319
         }
333 320
 
334
-        VideoLayout.resizeThumbnails(true);
335
-
336 321
         // Initialize the view
337 322
         remoteVideo.updateView();
338 323
     },
@@ -340,12 +325,9 @@ const VideoLayout = {
340 325
     // FIXME: what does this do???
341 326
     remoteVideoActive(videoElement, resourceJid) {
342 327
         logger.info(`${resourceJid} video is now active`, videoElement);
343
-        VideoLayout.resizeThumbnails(
344
-            false, () => {
345
-                if (videoElement) {
346
-                    $(videoElement).show();
347
-                }
348
-            });
328
+        if (videoElement) {
329
+            $(videoElement).show();
330
+        }
349 331
         this._updateLargeVideoIfDisplayed(resourceJid, true);
350 332
     },
351 333
 
@@ -378,37 +360,6 @@ const VideoLayout = {
378 360
         });
379 361
     },
380 362
 
381
-    /*
382
-     * Shows or hides the audio muted indicator over the local thumbnail video.
383
-     * @param {boolean} isMuted
384
-     */
385
-    showLocalAudioIndicator(isMuted) {
386
-        localVideoThumbnail.showAudioIndicator(isMuted);
387
-    },
388
-
389
-    /**
390
-     * Resizes thumbnails.
391
-     */
392
-    resizeThumbnails(forceUpdate = false, onComplete = null) {
393
-        const { localVideo, remoteVideo } = Filmstrip.calculateThumbnailSize();
394
-
395
-        Filmstrip.resizeThumbnails(localVideo, remoteVideo, forceUpdate);
396
-
397
-        if (shouldDisplayTileView(APP.store.getState())) {
398
-            const height = (localVideo && localVideo.thumbHeight) || (remoteVideo && remoteVideo.thumbnHeight) || 0;
399
-            const qualityLevel = getNearestReceiverVideoQualityLevel(height);
400
-
401
-            APP.store.dispatch(setMaxReceiverVideoQuality(qualityLevel));
402
-        }
403
-
404
-        localVideoThumbnail && localVideoThumbnail.rerender();
405
-        Object.values(remoteVideos).forEach(remoteVideoThumbnail => remoteVideoThumbnail.rerender());
406
-
407
-        if (onComplete && typeof onComplete === 'function') {
408
-            onComplete();
409
-        }
410
-    },
411
-
412 363
     /**
413 364
      * On audio muted event.
414 365
      */
@@ -543,18 +494,6 @@ const VideoLayout = {
543 494
         }
544 495
     },
545 496
 
546
-    /**
547
-     * Hides the connection indicator
548
-     * @param id
549
-     */
550
-    hideConnectionIndicator(id) {
551
-        const remoteVideo = remoteVideos[id];
552
-
553
-        if (remoteVideo) {
554
-            remoteVideo.removeConnectionIndicator();
555
-        }
556
-    },
557
-
558 497
     /**
559 498
      * Hides all the indicators
560 499
      */
@@ -586,8 +525,6 @@ const VideoLayout = {
586 525
         } else {
587 526
             logger.warn(`No remote video for ${id}`);
588 527
         }
589
-
590
-        VideoLayout.resizeThumbnails();
591 528
     },
592 529
 
593 530
     onVideoTypeChanged(id, newVideoType) {
@@ -623,12 +560,7 @@ const VideoLayout = {
623 560
      *
624 561
      * @param forceUpdate indicates that hidden thumbnails will be shown
625 562
      */
626
-    resizeVideoArea(
627
-            forceUpdate = false,
628
-            animate = false) {
629
-        // Resize the thumbnails first.
630
-        this.resizeThumbnails(forceUpdate);
631
-
563
+    resizeVideoArea(animate = false) {
632 564
         if (largeVideo) {
633 565
             largeVideo.updateContainerSize();
634 566
             largeVideo.resize(animate);
@@ -917,6 +849,10 @@ const VideoLayout = {
917 849
     refreshLayout() {
918 850
         localVideoThumbnail && localVideoThumbnail.updateDOMLocation();
919 851
         VideoLayout.resizeVideoArea();
852
+
853
+        // Rerender the thumbnails since they are dependant on the layout because of the tooltip positioning.
854
+        localVideoThumbnail && localVideoThumbnail.rerender();
855
+        Object.values(remoteVideos).forEach(remoteVideoThumbnail => remoteVideoThumbnail.rerender());
920 856
     },
921 857
 
922 858
     /**
@@ -967,6 +903,13 @@ const VideoLayout = {
967 903
         if (this.isCurrentlyOnLarge(participantId)) {
968 904
             this.updateLargeVideo(participantId, force);
969 905
         }
906
+    },
907
+
908
+    /**
909
+     * Handles window resizes.
910
+     */
911
+    onResize() {
912
+        VideoLayout.resizeVideoArea();
970 913
     }
971 914
 };
972 915
 

+ 4
- 8
react/features/base/conference/middleware.js Parādīt failu

@@ -116,16 +116,12 @@ StateListenerRegistry.register(
116 116
             maxReceiverVideoQuality,
117 117
             preferredReceiverVideoQuality
118 118
         } = currentState;
119
-        const changedPreferredVideoQuality = preferredReceiverVideoQuality
120
-            !== previousState.preferredReceiverVideoQuality;
121
-        const changedMaxVideoQuality = maxReceiverVideoQuality
122
-            !== previousState.maxReceiverVideoQuality;
119
+        const changedPreferredVideoQuality
120
+            = preferredReceiverVideoQuality !== previousState.preferredReceiverVideoQuality;
121
+        const changedMaxVideoQuality = maxReceiverVideoQuality !== previousState.maxReceiverVideoQuality;
123 122
 
124 123
         if (changedPreferredVideoQuality || changedMaxVideoQuality) {
125
-            _setReceiverVideoConstraint(
126
-                conference,
127
-                preferredReceiverVideoQuality,
128
-                maxReceiverVideoQuality);
124
+            _setReceiverVideoConstraint(conference, preferredReceiverVideoQuality, maxReceiverVideoQuality);
129 125
         }
130 126
     });
131 127
 

+ 6
- 3
react/features/base/react/components/web/BaseIndicator.js Parādīt failu

@@ -72,7 +72,6 @@ class BaseIndicator extends Component<Props> {
72 72
      */
73 73
     static defaultProps = {
74 74
         className: '',
75
-        iconSize: 13,
76 75
         id: '',
77 76
         tooltipPosition: 'top'
78 77
     };
@@ -95,8 +94,12 @@ class BaseIndicator extends Component<Props> {
95 94
             tooltipKey,
96 95
             tooltipPosition
97 96
         } = this.props;
98
-
99 97
         const iconContainerClassName = `indicator-icon-container ${className}`;
98
+        const style = {};
99
+
100
+        if (iconSize) {
101
+            style.fontSize = iconSize;
102
+        }
100 103
 
101 104
         return (
102 105
             <div className = 'indicator-container'>
@@ -110,7 +113,7 @@ class BaseIndicator extends Component<Props> {
110 113
                             className = { iconClassName }
111 114
                             id = { iconId }
112 115
                             src = { icon }
113
-                            style = {{ fontSize: iconSize }} />
116
+                            style = { style } />
114 117
                     </span>
115 118
                 </Tooltip>
116 119
             </div>

+ 9
- 0
react/features/base/responsive-ui/actionTypes.js Parādīt failu

@@ -1,3 +1,12 @@
1
+/**
2
+ * The type of (redux) action which indicates that the client window has been resized.
3
+ *
4
+ * {
5
+ *     type: CLIENT_RESIZED
6
+ * }
7
+ */
8
+export const CLIENT_RESIZED = 'CLIENT_RESIZED';
9
+
1 10
 /**
2 11
  * The type of (redux) action which sets the aspect ratio of the app's user
3 12
  * interface.

+ 18
- 3
react/features/base/responsive-ui/actions.js Parādīt failu

@@ -1,10 +1,10 @@
1 1
 // @flow
2 2
 
3
-import { SET_ASPECT_RATIO, SET_REDUCED_UI } from './actionTypes';
4
-import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
5
-
6 3
 import type { Dispatch } from 'redux';
7 4
 
5
+import { CLIENT_RESIZED, SET_ASPECT_RATIO, SET_REDUCED_UI } from './actionTypes';
6
+import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
7
+
8 8
 /**
9 9
  * Size threshold for determining if we are in reduced UI mode or not.
10 10
  *
@@ -16,6 +16,21 @@ import type { Dispatch } from 'redux';
16 16
  */
17 17
 const REDUCED_UI_THRESHOLD = 300;
18 18
 
19
+/**
20
+ * Indicates a resize of the window.
21
+ *
22
+ * @param {number} clientWidth - The width of the window.
23
+ * @param {number} clientHeight - The height of the window.
24
+ * @returns {Object}
25
+ */
26
+export function clientResized(clientWidth: number, clientHeight: number) {
27
+    return {
28
+        type: CLIENT_RESIZED,
29
+        clientHeight,
30
+        clientWidth
31
+    };
32
+}
33
+
19 34
 /**
20 35
  * Sets the aspect ratio of the app's user interface based on specific width and
21 36
  * height.

+ 69
- 0
react/features/base/responsive-ui/middleware.web.js Parādīt failu

@@ -0,0 +1,69 @@
1
+// @flow
2
+
3
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
4
+import { MiddlewareRegistry } from '../../base/redux';
5
+
6
+import { clientResized } from './actions';
7
+
8
+/**
9
+ * Dimensions change handler.
10
+ */
11
+let handler;
12
+
13
+/**
14
+ * Middleware that handles window dimension changes.
15
+ *
16
+ * @param {Store} store - The redux store.
17
+ * @returns {Function}
18
+ */
19
+MiddlewareRegistry.register(store => next => action => {
20
+    const result = next(action);
21
+
22
+    switch (action.type) {
23
+    case APP_WILL_UNMOUNT: {
24
+        _appWillUnmount();
25
+        break;
26
+    }
27
+    case APP_WILL_MOUNT:
28
+        _appWillMount(store);
29
+        break;
30
+
31
+    }
32
+
33
+    return result;
34
+});
35
+
36
+/**
37
+ * Notifies this feature that the action {@link APP_WILL_MOUNT} is being
38
+ * dispatched within a specific redux {@code store}.
39
+ *
40
+ * @param {Store} store - The redux store in which the specified {@code action}
41
+ * is being dispatched.
42
+ * @private
43
+ * @returns {void}
44
+ */
45
+function _appWillMount(store) {
46
+    handler = () => {
47
+        const {
48
+            innerHeight,
49
+            innerWidth
50
+        } = window;
51
+
52
+        store.dispatch(clientResized(innerWidth, innerHeight));
53
+    };
54
+
55
+    window.addEventListener('resize', handler);
56
+}
57
+
58
+/**
59
+ * Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
60
+ * dispatched within a specific redux {@code store}.
61
+ *
62
+ * @private
63
+ * @returns {void}
64
+ */
65
+function _appWillUnmount() {
66
+    window.removeEventListener('resize', handler);
67
+
68
+    handler = undefined;
69
+}

+ 16
- 1
react/features/base/responsive-ui/reducer.js Parādīt failu

@@ -2,19 +2,34 @@
2 2
 
3 3
 import { ReducerRegistry, set } from '../redux';
4 4
 
5
-import { SET_ASPECT_RATIO, SET_REDUCED_UI } from './actionTypes';
5
+import { CLIENT_RESIZED, SET_ASPECT_RATIO, SET_REDUCED_UI } from './actionTypes';
6 6
 import { ASPECT_RATIO_NARROW } from './constants';
7 7
 
8
+const {
9
+    innerHeight = 0,
10
+    innerWidth = 0
11
+} = window;
12
+
8 13
 /**
9 14
  * The default/initial redux state of the feature base/responsive-ui.
10 15
  */
11 16
 const DEFAULT_STATE = {
12 17
     aspectRatio: ASPECT_RATIO_NARROW,
18
+    clientHeight: innerHeight,
19
+    clientWidth: innerWidth,
13 20
     reducedUI: false
14 21
 };
15 22
 
16 23
 ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
17 24
     switch (action.type) {
25
+    case CLIENT_RESIZED: {
26
+
27
+        return {
28
+            ...state,
29
+            clientWidth: action.clientWidth,
30
+            clientHeight: action.clientHeight
31
+        };
32
+    }
18 33
     case SET_ASPECT_RATIO:
19 34
         return set(state, 'aspectRatio', action.aspectRatio);
20 35
 

+ 20
- 0
react/features/filmstrip/actionTypes.js Parādīt failu

@@ -28,3 +28,23 @@ export const SET_FILMSTRIP_HOVERED = 'SET_FILMSTRIP_HOVERED';
28 28
  * }
29 29
  */
30 30
 export const SET_FILMSTRIP_VISIBLE = 'SET_FILMSTRIP_VISIBLE';
31
+
32
+/**
33
+ * The type of (redux) action which sets the dimensions of the tile view grid.
34
+ *
35
+ * {
36
+ *     type: SET_TILE_VIEW_DIMENSIONS,
37
+ *     dimensions: Object
38
+ * }
39
+ */
40
+export const SET_TILE_VIEW_DIMENSIONS = 'SET_TILE_VIEW_DIMENSIONS';
41
+
42
+/**
43
+ * The type of (redux) action which sets the dimensions of the thumbnails in horizontal view.
44
+ *
45
+ * {
46
+ *     type: SET_HORIZONTAL_VIEW_DIMENSIONS,
47
+ *     dimensions: Object
48
+ * }
49
+ */
50
+export const SET_HORIZONTAL_VIEW_DIMENSIONS = 'SET_HORIZONTAL_VIEW_DIMENSIONS';

react/features/filmstrip/actions.js → react/features/filmstrip/actions.native.js Parādīt failu


+ 54
- 0
react/features/filmstrip/actions.web.js Parādīt failu

@@ -0,0 +1,54 @@
1
+// @flow
2
+
3
+import { SET_HORIZONTAL_VIEW_DIMENSIONS, SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
4
+import { calculateThumbnailSizeForHorizontalView, calculateThumbnailSizeForTileView } from './functions';
5
+
6
+/**
7
+ * The size of the side margins for each tile as set in CSS.
8
+ */
9
+const TILE_VIEW_SIDE_MARGINS = 10 * 2;
10
+
11
+/**
12
+ * Sets the dimensions of the tile view grid.
13
+ *
14
+ * @param {Object} dimensions - Whether the filmstrip is visible.
15
+ * @param {Object} windowSize - The size of the window.
16
+ * @returns {{
17
+ *     type: SET_TILE_VIEW_DIMENSIONS,
18
+ *     dimensions: Object
19
+ * }}
20
+ */
21
+export function setTileViewDimensions(dimensions: Object, windowSize: Object) {
22
+    const thumbnailSize = calculateThumbnailSizeForTileView({
23
+        ...dimensions,
24
+        ...windowSize
25
+    });
26
+    const filmstripWidth = dimensions.columns * (TILE_VIEW_SIDE_MARGINS + thumbnailSize.width);
27
+
28
+    return {
29
+        type: SET_TILE_VIEW_DIMENSIONS,
30
+        dimensions: {
31
+            gridDimensions: dimensions,
32
+            thumbnailSize,
33
+            filmstripWidth
34
+        }
35
+    };
36
+}
37
+
38
+/**
39
+ * Sets the dimensions of the thumbnails in horizontal view.
40
+ *
41
+ * @param {number} clientHeight - The height of the window.
42
+ * @returns {{
43
+ *     type: SET_HORIZONTAL_VIEW_DIMENSIONS,
44
+ *     dimensions: Object
45
+ * }}
46
+ */
47
+export function setHorizontalViewDimensions(clientHeight: number = 0) {
48
+    return {
49
+        type: SET_HORIZONTAL_VIEW_DIMENSIONS,
50
+        dimensions: calculateThumbnailSizeForHorizontalView(clientHeight)
51
+    };
52
+}
53
+
54
+export * from './actions.native';

+ 1
- 0
react/features/filmstrip/components/web/AudioMutedIndicator.js Parādīt failu

@@ -34,6 +34,7 @@ class AudioMutedIndicator extends Component<Props> {
34 34
                 className = 'audioMuted toolbar-icon'
35 35
                 icon = { IconMicDisabled }
36 36
                 iconId = 'mic-disabled'
37
+                iconSize = { 13 }
37 38
                 tooltipKey = 'videothumbnail.mute'
38 39
                 tooltipPosition = { this.props.tooltipPosition } />
39 40
         );

+ 61
- 14
react/features/filmstrip/components/web/Filmstrip.js Parādīt failu

@@ -16,6 +16,8 @@ import { dockToolbox } from '../../../toolbox';
16 16
 import { setFilmstripHovered, setFilmstripVisible } from '../../actions';
17 17
 import { shouldRemoteVideosBeVisible } from '../../functions';
18 18
 
19
+import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
20
+
19 21
 import Toolbar from './Toolbar';
20 22
 
21 23
 declare var APP: Object;
@@ -31,17 +33,37 @@ type Props = {
31 33
      */
32 34
     _className: string,
33 35
 
36
+    /**
37
+     * The current layout of the filmstrip.
38
+     */
39
+    _currentLayout: string,
40
+
41
+    /**
42
+     * The number of columns in tile view.
43
+     */
44
+    _columns: number,
45
+
34 46
     /**
35 47
      * Whether the UI/UX is filmstrip-only.
36 48
      */
37 49
     _filmstripOnly: boolean,
38 50
 
51
+    /**
52
+     * The width of the filmstrip.
53
+     */
54
+    _filmstripWidth: number,
55
+
39 56
     /**
40 57
      * Whether or not remote videos are currently being hovered over. Hover
41 58
      * handling is currently being handled detected outside of react.
42 59
      */
43 60
     _hovered: boolean,
44 61
 
62
+    /**
63
+     * The number of rows in tile view.
64
+     */
65
+    _rows: number,
66
+
45 67
     /**
46 68
      * Additional CSS class names to add to the container of all the thumbnails.
47 69
      */
@@ -87,8 +109,7 @@ class Filmstrip extends Component <Props> {
87 109
         // also works around an issue where mouseout and then a mouseover event
88 110
         // is fired when hovering over remote thumbnails, which are not yet in
89 111
         // react.
90
-        this._notifyOfHoveredStateUpdate
91
-            = _.debounce(this._notifyOfHoveredStateUpdate, 100);
112
+        this._notifyOfHoveredStateUpdate = _.debounce(this._notifyOfHoveredStateUpdate, 100);
92 113
 
93 114
         // Cache the current hovered state for _updateHoveredState to always
94 115
         // send the last known hovered state.
@@ -97,10 +118,8 @@ class Filmstrip extends Component <Props> {
97 118
         // Bind event handlers so they are only bound once for every instance.
98 119
         this._onMouseOut = this._onMouseOut.bind(this);
99 120
         this._onMouseOver = this._onMouseOver.bind(this);
100
-        this._onShortcutToggleFilmstrip
101
-            = this._onShortcutToggleFilmstrip.bind(this);
102
-        this._onToolbarToggleFilmstrip
103
-            = this._onToolbarToggleFilmstrip.bind(this);
121
+        this._onShortcutToggleFilmstrip = this._onShortcutToggleFilmstrip.bind(this);
122
+        this._onToolbarToggleFilmstrip = this._onToolbarToggleFilmstrip.bind(this);
104 123
     }
105 124
 
106 125
     /**
@@ -142,13 +161,36 @@ class Filmstrip extends Component <Props> {
142 161
         // will get updated without replacing the DOM. If the known DOM gets
143 162
         // modified, then the views will get blown away.
144 163
 
164
+        const remoteVideosStyle = { };
165
+        const filmstripRemoteVideosContainerStyle = {};
166
+        let remoteVideoContainerClassName = 'remote-videos-container';
167
+
168
+        switch (this.props._currentLayout) {
169
+        case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
170
+            // Adding 8px for the 2px margins and 2px borders on the left and right. Also adding 7px for the scrollbar.
171
+            remoteVideosStyle.maxWidth = (interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120) + 15;
172
+            break;
173
+        case LAYOUTS.TILE_VIEW: {
174
+            // The size of the side margins for each tile as set in CSS.
175
+            const { _columns, _rows, _filmstripWidth } = this.props;
176
+
177
+            if (_rows > _columns) {
178
+                remoteVideoContainerClassName += ' has-overflow';
179
+            }
180
+
181
+            filmstripRemoteVideosContainerStyle.width = _filmstripWidth;
182
+            break;
183
+        }
184
+        }
185
+
145 186
         return (
146 187
             <div className = { `filmstrip ${this.props._className}` }>
147 188
                 { this.props._filmstripOnly
148 189
                     ? <Toolbar /> : this._renderToggleButton() }
149 190
                 <div
150 191
                     className = { this.props._videosClassName }
151
-                    id = 'remoteVideos'>
192
+                    id = 'remoteVideos'
193
+                    style = { remoteVideosStyle }>
152 194
                     <div
153 195
                         className = 'filmstrip__videos'
154 196
                         id = 'filmstripLocalVideo'
@@ -165,10 +207,11 @@ class Filmstrip extends Component <Props> {
165 207
                           * thumbnails resize instead of causing overflow.
166 208
                           */}
167 209
                         <div
168
-                            className = 'remote-videos-container'
210
+                            className = { remoteVideoContainerClassName }
169 211
                             id = 'filmstripRemoteVideosContainer'
170 212
                             onMouseOut = { this._onMouseOut }
171
-                            onMouseOver = { this._onMouseOver }>
213
+                            onMouseOver = { this._onMouseOver }
214
+                            style = { filmstripRemoteVideosContainerStyle }>
172 215
                             <div id = 'localVideoTileViewContainer' />
173 216
                         </div>
174 217
                     </div>
@@ -301,20 +344,24 @@ class Filmstrip extends Component <Props> {
301 344
 function _mapStateToProps(state) {
302 345
     const { hovered, visible } = state['features/filmstrip'];
303 346
     const isFilmstripOnly = Boolean(interfaceConfig.filmStripOnly);
304
-    const reduceHeight = !isFilmstripOnly
305
-        && state['features/toolbox'].visible
306
-        && interfaceConfig.TOOLBAR_BUTTONS.length;
347
+    const reduceHeight
348
+        = !isFilmstripOnly && state['features/toolbox'].visible && interfaceConfig.TOOLBAR_BUTTONS.length;
307 349
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
308
-    const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
309
-        reduceHeight ? 'reduce-height' : ''}`.trim();
350
+    const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`.trim();
310 351
     const videosClassName = `filmstrip__videos${
311 352
         isFilmstripOnly ? ' filmstrip__videos-filmstripOnly' : ''}${
312 353
         visible ? '' : ' hidden'}`;
354
+    const { gridDimensions = {}, filmstripWidth } = state['features/filmstrip'].tileViewDimensions;
355
+
313 356
 
314 357
     return {
315 358
         _className: className,
359
+        _columns: gridDimensions.columns,
360
+        _currentLayout: getCurrentLayout(state),
316 361
         _filmstripOnly: isFilmstripOnly,
362
+        _filmstripWidth: filmstripWidth,
317 363
         _hovered: hovered,
364
+        _rows: gridDimensions.rows,
318 365
         _videosClassName: videosClassName,
319 366
         _visible: visible
320 367
     };

+ 1
- 0
react/features/filmstrip/components/web/ModeratorIndicator.js Parādīt failu

@@ -34,6 +34,7 @@ class ModeratorIndicator extends Component<Props> {
34 34
                 <BaseIndicator
35 35
                     className = 'focusindicator toolbar-icon'
36 36
                     icon = { IconModerator }
37
+                    iconSize = { 13 }
37 38
                     tooltipKey = 'videothumbnail.moderator'
38 39
                     tooltipPosition = { this.props.tooltipPosition } />
39 40
             </div>

+ 1
- 0
react/features/filmstrip/components/web/VideoMutedIndicator.js Parādīt failu

@@ -33,6 +33,7 @@ class VideoMutedIndicator extends Component<Props> {
33 33
                 className = 'videoMuted toolbar-icon'
34 34
                 icon = { IconCameraDisabled }
35 35
                 iconId = 'camera-disabled'
36
+                iconSize = { 13 }
36 37
                 tooltipKey = 'videothumbnail.videomute'
37 38
                 tooltipPosition = { this.props.tooltipPosition } />
38 39
         );

+ 5
- 0
react/features/filmstrip/constants.js Parādīt failu

@@ -4,3 +4,8 @@
4 4
  * The height of the filmstrip in narrow aspect ratio, or width in wide.
5 5
  */
6 6
 export const FILMSTRIP_SIZE = 90;
7
+
8
+/**
9
+ * The aspect ratio of a tile in tile view.
10
+ */
11
+export const TILE_ASPECT_RATIO = 16 / 9;

+ 57
- 0
react/features/filmstrip/functions.web.js Parādīt failu

@@ -6,6 +6,8 @@ import {
6 6
 } from '../base/participants';
7 7
 import { toState } from '../base/redux';
8 8
 
9
+import { TILE_ASPECT_RATIO } from './constants';
10
+
9 11
 declare var interfaceConfig: Object;
10 12
 
11 13
 /**
@@ -59,3 +61,58 @@ export function shouldRemoteVideosBeVisible(state: Object) {
59 61
 
60 62
             || state['features/base/config'].disable1On1Mode);
61 63
 }
64
+
65
+/**
66
+ * Calculates the size for thumbnails when in horizontal view layout.
67
+ *
68
+ * @param {number} clientHeight - The height of the app window.
69
+ * @returns {{local: {height, width}, remote: {height, width}}}
70
+ */
71
+export function calculateThumbnailSizeForHorizontalView(clientHeight: number = 0) {
72
+    const topBottomMargin = 15;
73
+    const availableHeight = Math.min(clientHeight, (interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120) + topBottomMargin);
74
+    const height = availableHeight - topBottomMargin;
75
+
76
+    return {
77
+        local: {
78
+            height,
79
+            width: Math.floor(interfaceConfig.LOCAL_THUMBNAIL_RATIO * height)
80
+        },
81
+        remote: {
82
+            height,
83
+            width: Math.floor(interfaceConfig.REMOTE_THUMBNAIL_RATIO * height)
84
+        }
85
+    };
86
+}
87
+
88
+/**
89
+ * Calculates the size for thumbnails when in tile view layout.
90
+ *
91
+ * @param {Object} dimensions - The desired dimensions of the tile view grid.
92
+ * @returns {{height, width}}
93
+ */
94
+export function calculateThumbnailSizeForTileView({
95
+    columns,
96
+    visibleRows,
97
+    clientWidth,
98
+    clientHeight
99
+}: Object) {
100
+    // The distance from the top and bottom of the screen, as set by CSS, to
101
+    // avoid overlapping UI elements.
102
+    const topBottomPadding = 200;
103
+
104
+    // Minimum space to keep between the sides of the tiles and the sides
105
+    // of the window.
106
+    const sideMargins = 30 * 2;
107
+    const viewWidth = clientWidth - sideMargins;
108
+    const viewHeight = clientHeight - topBottomPadding;
109
+    const initialWidth = viewWidth / columns;
110
+    const aspectRatioHeight = initialWidth / TILE_ASPECT_RATIO;
111
+    const height = Math.floor(Math.min(aspectRatioHeight, viewHeight / visibleRows));
112
+    const width = Math.floor(TILE_ASPECT_RATIO * height);
113
+
114
+    return {
115
+        height,
116
+        width
117
+    };
118
+}

+ 2
- 0
react/features/filmstrip/index.js Parādīt failu

@@ -5,3 +5,5 @@ export * from './constants';
5 5
 export * from './functions';
6 6
 
7 7
 import './reducer';
8
+import './subscriber';
9
+import './middleware';

+ 0
- 0
react/features/filmstrip/middleware.native.js Parādīt failu


+ 74
- 0
react/features/filmstrip/middleware.web.js Parādīt failu

@@ -0,0 +1,74 @@
1
+// @flow
2
+
3
+import { getNearestReceiverVideoQualityLevel, setMaxReceiverVideoQuality } from '../base/conference';
4
+import { MiddlewareRegistry } from '../base/redux';
5
+import { CLIENT_RESIZED } from '../base/responsive-ui';
6
+import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
7
+import {
8
+    getCurrentLayout,
9
+    LAYOUTS,
10
+    shouldDisplayTileView
11
+} from '../video-layout';
12
+
13
+import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
14
+import { SET_HORIZONTAL_VIEW_DIMENSIONS, SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
15
+
16
+/**
17
+ * The middleware of the feature Filmstrip.
18
+ */
19
+MiddlewareRegistry.register(store => next => action => {
20
+    const result = next(action);
21
+
22
+    switch (action.type) {
23
+    case CLIENT_RESIZED: {
24
+        const state = store.getState();
25
+        const layout = getCurrentLayout(state);
26
+
27
+        switch (layout) {
28
+        case LAYOUTS.TILE_VIEW: {
29
+            const { gridDimensions } = state['features/filmstrip'].tileViewDimensions;
30
+            const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
31
+
32
+            store.dispatch(setTileViewDimensions(gridDimensions, {
33
+                clientHeight,
34
+                clientWidth
35
+            }));
36
+            break;
37
+        }
38
+        case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
39
+            store.dispatch(setHorizontalViewDimensions(state['features/base/responsive-ui'].clientHeight));
40
+            break;
41
+        }
42
+        break;
43
+    }
44
+    case SET_TILE_VIEW_DIMENSIONS: {
45
+        const state = store.getState();
46
+
47
+        if (shouldDisplayTileView(state)) {
48
+            const { width, height } = state['features/filmstrip'].tileViewDimensions.thumbnailSize;
49
+            const qualityLevel = getNearestReceiverVideoQualityLevel(height);
50
+
51
+            store.dispatch(setMaxReceiverVideoQuality(qualityLevel));
52
+
53
+            // Once the thumbnails are reactified this should be moved there too.
54
+            Filmstrip.resizeThumbnailsForTileView(width, height, true);
55
+        }
56
+        break;
57
+    }
58
+    case SET_HORIZONTAL_VIEW_DIMENSIONS: {
59
+        const state = store.getState();
60
+
61
+        if (getCurrentLayout(state) === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW) {
62
+            const { horizontalViewDimensions = {} } = state['features/filmstrip'];
63
+
64
+            // Once the thumbnails are reactified this should be moved there too.
65
+            Filmstrip.resizeThumbnailsForHorizontalView(horizontalViewDimensions, true);
66
+        }
67
+
68
+        break;
69
+    }
70
+    }
71
+
72
+    return result;
73
+});
74
+

+ 30
- 1
react/features/filmstrip/reducer.js Parādīt failu

@@ -5,7 +5,9 @@ import { ReducerRegistry } from '../base/redux';
5 5
 import {
6 6
     SET_FILMSTRIP_ENABLED,
7 7
     SET_FILMSTRIP_HOVERED,
8
-    SET_FILMSTRIP_VISIBLE
8
+    SET_FILMSTRIP_VISIBLE,
9
+    SET_HORIZONTAL_VIEW_DIMENSIONS,
10
+    SET_TILE_VIEW_DIMENSIONS
9 11
 } from './actionTypes';
10 12
 
11 13
 const DEFAULT_STATE = {
@@ -17,6 +19,22 @@ const DEFAULT_STATE = {
17 19
      */
18 20
     enabled: true,
19 21
 
22
+    /**
23
+     * The horizontal view dimensions.
24
+     *
25
+     * @public
26
+     * @type {Object}
27
+     */
28
+    horizontalViewDimensions: {},
29
+
30
+    /**
31
+     * The tile view dimensions.
32
+     *
33
+     * @public
34
+     * @type {Object}
35
+     */
36
+    tileViewDimensions: {},
37
+
20 38
     /**
21 39
      * The indicator which determines whether the {@link Filmstrip} is visible.
22 40
      *
@@ -55,6 +73,17 @@ ReducerRegistry.register(
55 73
                 ...state,
56 74
                 visible: action.visible
57 75
             };
76
+
77
+        case SET_HORIZONTAL_VIEW_DIMENSIONS:
78
+            return {
79
+                ...state,
80
+                horizontalViewDimensions: action.dimensions
81
+            };
82
+        case SET_TILE_VIEW_DIMENSIONS:
83
+            return {
84
+                ...state,
85
+                tileViewDimensions: action.dimensions
86
+            };
58 87
         }
59 88
 
60 89
         return state;

+ 0
- 0
react/features/filmstrip/subscriber.native.js Parādīt failu


+ 58
- 0
react/features/filmstrip/subscriber.web.js Parādīt failu

@@ -0,0 +1,58 @@
1
+// @flow
2
+
3
+import { StateListenerRegistry, equals } from '../base/redux';
4
+import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
5
+import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
6
+
7
+import { setHorizontalViewDimensions, setTileViewDimensions } from './actions';
8
+
9
+/**
10
+ * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
11
+ */
12
+StateListenerRegistry.register(
13
+    /* selector */ state => state['features/base/participants'].length,
14
+    /* listener */ (numberOfParticipants, store) => {
15
+        const state = store.getState();
16
+
17
+        if (shouldDisplayTileView(state)) {
18
+            const gridDimensions = getTileViewGridDimensions(state['features/base/participants'].length);
19
+            const oldGridDimensions = state['features/filmstrip'].tileViewDimensions.gridDimensions;
20
+            const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
21
+
22
+            if (!equals(gridDimensions, oldGridDimensions)) {
23
+                store.dispatch(setTileViewDimensions(gridDimensions, {
24
+                    clientHeight,
25
+                    clientWidth
26
+                }));
27
+            }
28
+        }
29
+    });
30
+
31
+/**
32
+ * Listens for changes in the selected layout to calculate the dimensions of the tile view grid and horizontal view.
33
+ */
34
+StateListenerRegistry.register(
35
+    /* selector */ state => getCurrentLayout(state),
36
+    /* listener */ (layout, store) => {
37
+        const state = store.getState();
38
+
39
+        switch (layout) {
40
+        case LAYOUTS.TILE_VIEW: {
41
+            const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
42
+
43
+            store.dispatch(setTileViewDimensions(
44
+                getTileViewGridDimensions(state['features/base/participants'].length), {
45
+                    clientHeight,
46
+                    clientWidth
47
+                }));
48
+            break;
49
+        }
50
+        case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
51
+            store.dispatch(setHorizontalViewDimensions(state['features/base/responsive-ui'].clientHeight));
52
+            break;
53
+        case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
54
+            // Once the thumbnails are reactified this should be moved there too.
55
+            Filmstrip.resizeThumbnailsForVerticalView();
56
+            break;
57
+        }
58
+    });

+ 4
- 9
react/features/video-layout/functions.js Parādīt failu

@@ -39,25 +39,20 @@ export function getMaxColumnCount() {
39 39
  * equal count of tiles for height and width, until maxColumn is reached in
40 40
  * which rows will be added but no more columns.
41 41
  *
42
- * @param {Object} state - The redux state.
42
+ * @param {number} numberOfParticipants - The number of participants including the fake participants.
43 43
  * @param {number} maxColumns - The maximum number of columns that can be
44 44
  * displayed.
45 45
  * @returns {Object} An object is return with the desired number of columns,
46 46
  * rows, and visible rows (the rest should overflow) for the tile view layout.
47 47
  */
48
-export function getTileViewGridDimensions(state: Object, maxColumns: number) {
49
-    // Purposefully include all participants, which includes fake participants
50
-    // that should show a thumbnail.
51
-    const potentialThumbnails = state['features/base/participants'].length;
52
-
53
-    const columnsToMaintainASquare = Math.ceil(Math.sqrt(potentialThumbnails));
48
+export function getTileViewGridDimensions(numberOfParticipants: number, maxColumns: number = getMaxColumnCount()) {
49
+    const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants));
54 50
     const columns = Math.min(columnsToMaintainASquare, maxColumns);
55
-    const rows = Math.ceil(potentialThumbnails / columns);
51
+    const rows = Math.ceil(numberOfParticipants / columns);
56 52
     const visibleRows = Math.min(maxColumns, rows);
57 53
 
58 54
     return {
59 55
         columns,
60
-        rows,
61 56
         visibleRows
62 57
     };
63 58
 }

Notiek ielāde…
Atcelt
Saglabāt