Przeglądaj źródła

Merge pull request #1587 from virtuacoplenny/lenny/vertical-filmstrip

vertical filmstrip and 1-on-1 mode
master
yanas 8 lat temu
rodzic
commit
1ff89c5a1c

+ 23
- 0
conference.js Wyświetl plik

1286
 
1286
 
1287
             // check the roles for the new user and reflect them
1287
             // check the roles for the new user and reflect them
1288
             APP.UI.updateUserRole(user);
1288
             APP.UI.updateUserRole(user);
1289
+
1290
+            updateRemoteThumbnailsVisibility();
1289
         });
1291
         });
1290
         room.on(ConferenceEvents.USER_LEFT, (id, user) => {
1292
         room.on(ConferenceEvents.USER_LEFT, (id, user) => {
1291
             APP.store.dispatch(participantLeft(id, user));
1293
             APP.store.dispatch(participantLeft(id, user));
1293
             APP.API.notifyUserLeft(id);
1295
             APP.API.notifyUserLeft(id);
1294
             APP.UI.removeUser(id, user.getDisplayName());
1296
             APP.UI.removeUser(id, user.getDisplayName());
1295
             APP.UI.onSharedVideoStop(id);
1297
             APP.UI.onSharedVideoStop(id);
1298
+
1299
+            updateRemoteThumbnailsVisibility();
1296
         });
1300
         });
1297
 
1301
 
1298
         room.on(ConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
1302
         room.on(ConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
1475
                         reportError(e);
1479
                         reportError(e);
1476
                     }
1480
                     }
1477
                 }
1481
                 }
1482
+
1483
+                updateRemoteThumbnailsVisibility();
1478
             });
1484
             });
1479
         }
1485
         }
1480
 
1486
 
1811
                     }
1817
                     }
1812
                 });
1818
                 });
1813
             }
1819
             }
1820
+
1821
+            updateRemoteThumbnailsVisibility();
1814
         });
1822
         });
1815
         room.addCommandListener(
1823
         room.addCommandListener(
1816
             this.commands.defaults.SHARED_VIDEO, ({value, attributes}, id) => {
1824
             this.commands.defaults.SHARED_VIDEO, ({value, attributes}, id) => {
1826
                     APP.UI.onSharedVideoUpdate(id, value, attributes);
1834
                     APP.UI.onSharedVideoUpdate(id, value, attributes);
1827
                 }
1835
                 }
1828
             });
1836
             });
1837
+
1838
+        function updateRemoteThumbnailsVisibility() {
1839
+            const localUserId = APP.conference.getMyUserId();
1840
+            const remoteParticipantsCount = room.getParticipantCount() - 1;
1841
+
1842
+            // Get the remote thumbnail count for cases where there are
1843
+            // non-participants displaying video, such as with video sharing.
1844
+            const remoteVideosCount = APP.UI.getRemoteVideosCount();
1845
+
1846
+            const shouldShowRemoteThumbnails = APP.UI.isPinned(localUserId)
1847
+                || remoteVideosCount > 1
1848
+                || remoteParticipantsCount !== remoteVideosCount;
1849
+
1850
+            APP.UI.setRemoteThumbnailsVisibility(shouldShowRemoteThumbnails);
1851
+        }
1829
     },
1852
     },
1830
     /**
1853
     /**
1831
     * Adds any room listener.
1854
     * Adds any room listener.

+ 3
- 0
config.js Wyświetl plik

57
     webrtcIceTcpDisable: false,
57
     webrtcIceTcpDisable: false,
58
 
58
 
59
     openSctp: true, // Toggle to enable/disable SCTP channels
59
     openSctp: true, // Toggle to enable/disable SCTP channels
60
+
61
+    // Disable hiding of remote thumbnails when in a 1-on-1 conference call.
62
+    disable1On1Mode: false,
60
     disableStats: false,
63
     disableStats: false,
61
     disableAudioLevels: false,
64
     disableAudioLevels: false,
62
     channelLastN: -1, // The default value of the channel attribute last-n.
65
     channelLastN: -1, // The default value of the channel attribute last-n.

+ 17
- 0
css/_filmstrip.scss Wyświetl plik

62
         videos. */
62
         videos. */
63
         font-size: 0pt;
63
         font-size: 0pt;
64
 
64
 
65
+        #filmstripLocalVideo {
66
+            padding-left: 0;
67
+        }
68
+
65
         &.hidden {
69
         &.hidden {
66
             bottom: -196px;
70
             bottom: -196px;
67
         }
71
         }
68
 
72
 
73
+        .remote-videos-container {
74
+            display: flex;
75
+        }
76
+
69
         .videocontainer {
77
         .videocontainer {
70
             display: none;
78
             display: none;
71
             position: relative;
79
             position: relative;
130
         margin-bottom: auto;
138
         margin-bottom: auto;
131
         padding-right: $defaultToolbarSize;
139
         padding-right: $defaultToolbarSize;
132
     }
140
     }
141
+
142
+    .remote-videos-container {
143
+        transition: opacity 1s;
144
+
145
+        &.hide-videos {
146
+            opacity: 0;
147
+            pointer-events: none;
148
+        }
149
+    }
133
 }
150
 }

+ 24
- 0
css/_jitsi_popover.scss Wyświetl plik

44
         border-width: 5px;
44
         border-width: 5px;
45
         border-bottom-width: 0;
45
         border-bottom-width: 0;
46
     }
46
     }
47
+
48
+    /**
49
+     * Override default "top" styles to support popovers appearing from the
50
+     * left of the popover trigger element.
51
+     */
52
+    &.left {
53
+        margin-left: -$popoverMenuPadding;
54
+        margin-top: 0;
55
+
56
+        .arrow {
57
+            border-color: transparent transparent transparent $popoverBg;
58
+            border-width: 5px 0px 5px 5px;
59
+            margin-left: 0;
60
+            margin-top: -5px;
61
+        }
62
+
63
+        .jitsipopover {
64
+            &__menu-padding {
65
+                bottom: 0;
66
+                height: 100%;
67
+                width: $popoverMenuPadding;
68
+            }
69
+        }
70
+    }
47
 }
71
 }

+ 118
- 0
css/_vertical_filmstrip_overrides.scss Wyświetl plik

1
+/**
2
+ * Override other styles to support vertical filmstrip mode.
3
+ */
4
+.vertical-filmstrip {
5
+    .filmstrip {
6
+        align-items: flex-end;
7
+        box-sizing: border-box;
8
+        display: flex;
9
+        flex-direction: column-reverse;
10
+        height: 100%;
11
+
12
+        /**
13
+         * Hide videos by making them slight to the right.
14
+         */
15
+        .filmstrip__videos {
16
+            right: 0;
17
+            transition: right 2s;
18
+
19
+            &.hidden {
20
+                bottom: auto;
21
+                right: -196px;
22
+            }
23
+        }
24
+
25
+        #filmstripLocalVideo {
26
+            height: auto;
27
+            justify-content: flex-end;
28
+        }
29
+
30
+        /**
31
+         * Remove unnecssary padding that is normally used to prevent horizontal
32
+         * filmstrip from overlapping the left edge of the screen.
33
+         */
34
+        #filmstripLocalVideo,
35
+        #filmstripRemoteVideos {
36
+            padding: 0;
37
+        }
38
+
39
+        #filmstripRemoteVideos {
40
+            display: flex;
41
+            flex: 1;
42
+            flex-direction: column;
43
+            height: auto;
44
+            overflow-x: hidden !important;
45
+
46
+            .remote-videos-container {
47
+                flex-direction: column;
48
+            }
49
+        }
50
+
51
+        /**
52
+         * Rotate the hide filmstrip icon so it points towards the right edge
53
+         * of the screen.
54
+         */
55
+        &__toolbar {
56
+            transform: rotate(-90deg);
57
+        }
58
+
59
+        /**
60
+         * Move the remote video menu trigger to the bottom left of the
61
+         * video thumbnail.
62
+         */
63
+        .remotevideomenu {
64
+            bottom: 0;
65
+            left: 0;
66
+            top: auto;
67
+            right: auto;
68
+            transform: rotate(-90deg);
69
+        }
70
+
71
+        #remoteVideos {
72
+            flex-direction: column-reverse;
73
+            height: 100%;
74
+        }
75
+
76
+        .videocontainer {
77
+            /**
78
+             * Move status icons to the bottom right of the thumbnail.
79
+             */
80
+            &__toolbar {
81
+                text-align: right;
82
+
83
+                .toolbar-icon {
84
+                    float: none;
85
+                }
86
+            }
87
+        }
88
+    }
89
+
90
+    /**
91
+     * For video labels that display on the top right to adjust its position as
92
+     * the filmstrip itself or filmstrip remote videos appear and disappear.
93
+     */
94
+    .video-state-indicator {
95
+        transition: right 2s;
96
+
97
+        &.with-filmstrip {
98
+            &#recordingLabel {
99
+                right: 200px;
100
+            }
101
+
102
+            &#videoResolutionLabel {
103
+                right: 150px;
104
+            }
105
+        }
106
+    }
107
+
108
+    /**
109
+     * Move toastr closer to the bottom of the screen and move left to avoid
110
+     * overlapping of videos when they are configured at default height.
111
+     */
112
+    #toast-container {
113
+        &.notification-bottom-right {
114
+            bottom: 25px;
115
+            right: 130 + 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
116
+        }
117
+    }
118
+}

+ 1
- 0
css/main.scss Wyświetl plik

73
 @import 'policy';
73
 @import 'policy';
74
 @import 'filmstrip';
74
 @import 'filmstrip';
75
 @import 'unsupported-browser/main';
75
 @import 'unsupported-browser/main';
76
+@import 'vertical_filmstrip_overrides';
76
 
77
 
77
 /* Modules END */
78
 /* Modules END */

+ 6
- 0
interface_config.js Wyświetl plik

55
      * Whether to only show the filmstrip (and hide the toolbar).
55
      * Whether to only show the filmstrip (and hide the toolbar).
56
      */
56
      */
57
     filmStripOnly: false,
57
     filmStripOnly: false,
58
+
59
+    /**
60
+     * Whether to show thumbnails in filmstrip as a column instead of as a row.
61
+     */
62
+    VERTICAL_FILMSTRIP: false,
63
+
58
     //A html text to be shown to guests on the close page, false disables it
64
     //A html text to be shown to guests on the close page, false disables it
59
     CLOSE_PAGE_GUEST_HINT: false,
65
     CLOSE_PAGE_GUEST_HINT: false,
60
     RANDOM_AVATAR_URL_PREFIX: false,
66
     RANDOM_AVATAR_URL_PREFIX: false,

+ 34
- 0
modules/UI/UI.js Wyświetl plik

309
     SideContainerToggler.init(eventEmitter);
309
     SideContainerToggler.init(eventEmitter);
310
     Filmstrip.init(eventEmitter);
310
     Filmstrip.init(eventEmitter);
311
 
311
 
312
+    // By default start with remote videos hidden and rely on other logic to
313
+    // make them visible.
314
+    UI.setRemoteThumbnailsVisibility(false);
315
+
312
     VideoLayout.init(eventEmitter);
316
     VideoLayout.init(eventEmitter);
313
     if (!interfaceConfig.filmStripOnly) {
317
     if (!interfaceConfig.filmStripOnly) {
314
         VideoLayout.initLargeVideo();
318
         VideoLayout.initLargeVideo();
339
         JitsiPopover.enabled = false;
343
         JitsiPopover.enabled = false;
340
     }
344
     }
341
 
345
 
346
+    if (interfaceConfig.VERTICAL_FILMSTRIP) {
347
+        $("body").addClass("vertical-filmstrip");
348
+    }
349
+
342
     document.title = interfaceConfig.APP_NAME;
350
     document.title = interfaceConfig.APP_NAME;
343
 
351
 
344
     if (!interfaceConfig.filmStripOnly) {
352
     if (!interfaceConfig.filmStripOnly) {
1142
     return VideoLayout.getLargeVideo();
1150
     return VideoLayout.getLargeVideo();
1143
 };
1151
 };
1144
 
1152
 
1153
+/**
1154
+ * Returns whether or not the passed in user id is currently pinned to the large
1155
+ * video.
1156
+ *
1157
+ * @param {string} userId - The id of the user to check is pinned or not.
1158
+ * @returns {boolean} True if the user is currently pinned to the large video.
1159
+ */
1160
+UI.isPinned = userId => VideoLayout.getPinnedId() === userId;
1161
+
1145
 /**
1162
 /**
1146
  * Shows dialog with a link to FF extension.
1163
  * Shows dialog with a link to FF extension.
1147
  */
1164
  */
1392
  */
1409
  */
1393
 UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
1410
 UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
1394
 
1411
 
1412
+/**
1413
+ * Returns the number of known remote videos.
1414
+ *
1415
+ * @returns {number} The number of remote videos.
1416
+ */
1417
+UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
1418
+
1419
+/**
1420
+ * Makes remote thumbnail videos visible or not visible.
1421
+ *
1422
+ * @param {boolean} shouldHide - True if remote thumbnails should be hidden,
1423
+ * false f they should be visible.
1424
+ * @returns {void}
1425
+ */
1426
+UI.setRemoteThumbnailsVisibility
1427
+    = shouldHide => Filmstrip.setRemoteVideoVisibility(shouldHide);
1428
+
1395
 const UIListeners = new Map([
1429
 const UIListeners = new Map([
1396
     [
1430
     [
1397
         UIEvents.ETHERPAD_CLICKED,
1431
         UIEvents.ETHERPAD_CLICKED,

+ 13
- 0
modules/UI/recording/Recording.js Wyświetl plik

201
  * position
201
  * position
202
  */
202
  */
203
 function moveToCorner(selector, move) {
203
 function moveToCorner(selector, move) {
204
+    const {
205
+        remoteVideosCount,
206
+        remoteVideosVisible,
207
+        visible
208
+    } = APP.store.getState()['features/filmstrip'];
209
+    selector.toggleClass(
210
+        'with-filmstrip',
211
+        Boolean(remoteVideosCount && remoteVideosVisible && visible));
212
+
204
     let moveToCornerClass = "moveToCorner";
213
     let moveToCornerClass = "moveToCorner";
205
     let containsClass = selector.hasClass(moveToCornerClass);
214
     let containsClass = selector.hasClass(moveToCornerClass);
206
 
215
 
295
             APP.UI.messageHandler.enableNotifications(false);
304
             APP.UI.messageHandler.enableNotifications(false);
296
             APP.UI.messageHandler.enablePopups(false);
305
             APP.UI.messageHandler.enablePopups(false);
297
         }
306
         }
307
+
308
+        this.eventEmitter.addListener(UIEvents.UPDATED_FILMSTRIP_DISPLAY, () =>{
309
+            this._updateStatusLabel();
310
+        });
298
     },
311
     },
299
 
312
 
300
     /**
313
     /**

+ 4
- 1
modules/UI/shared_video/SharedVideo.js Wyświetl plik

456
                 // revert to original behavior (prevents pausing
456
                 // revert to original behavior (prevents pausing
457
                 // for participants not sharing the video to pause it)
457
                 // for participants not sharing the video to pause it)
458
                 $("#sharedVideo").css("pointer-events","auto");
458
                 $("#sharedVideo").css("pointer-events","auto");
459
+
460
+                this.emitter.emit(
461
+                    UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
459
         });
462
         });
460
 
463
 
461
         this.url = null;
464
         this.url = null;
656
     avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
659
     avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
657
     container.appendChild(avatar);
660
     container.appendChild(avatar);
658
 
661
 
659
-    var remotes = document.getElementById('remoteVideos');
662
+    var remotes = document.getElementById('filmstripRemoteVideosContainer');
660
     return remotes.appendChild(container);
663
     return remotes.appendChild(container);
661
 };
664
 };
662
 
665
 

+ 84
- 18
modules/UI/util/JitsiPopover.js Wyświetl plik

1
 /* global $ */
1
 /* global $ */
2
+
3
+const positionConfigurations = {
4
+    left: {
5
+
6
+        // Align the popover's right side to the target element.
7
+        my: 'right',
8
+
9
+        // Align the popover to the left side of the target element.
10
+        at: 'left',
11
+
12
+        // Force the popover to fit within the viewport.
13
+        collision: 'fit',
14
+
15
+        /**
16
+         * Callback invoked by jQuery UI tooltip.
17
+         *
18
+         * @param {Object} position - The top and bottom position the popover
19
+         * element should be set at.
20
+         * @param {Object} element. - Additional size and position information
21
+         * about the popover element and target.
22
+         * @param {Object} elements.element - Has position and size related data
23
+         * for the popover element itself.
24
+         * @param {Object} elements.target - Has position and size related data
25
+         * for the target element the popover displays from.
26
+         */
27
+        using: function setPositionLeft(position, elements) {
28
+            const { element, target } = elements;
29
+
30
+            $('.jitsipopover').css({
31
+                display: 'table',
32
+                left: position.left,
33
+                top: position.top
34
+            });
35
+
36
+            // Move additional padding to the right edge of the popover and
37
+            // allow css to take care of width. The padding is used to maintain
38
+            // a hover state between the target and the popover.
39
+            $('.jitsipopover > .jitsipopover__menu-padding').css({
40
+                left: element.width
41
+            });
42
+
43
+            // Find the distance from the top of the popover to the center of
44
+            // the target and use that value to position the arrow to point to
45
+            // it.
46
+            const verticalCenterOfTarget = target.height / 2;
47
+            const verticalDistanceFromTops = target.top - element.top;
48
+            const verticalPositionOfTargetCenter
49
+                = verticalDistanceFromTops + verticalCenterOfTarget;
50
+
51
+            $('.jitsipopover > .arrow').css({
52
+                left: element.width,
53
+                top: verticalPositionOfTargetCenter
54
+            });
55
+        }
56
+    },
57
+    top: {
58
+        my: "bottom",
59
+        at: "top",
60
+        collision: "fit",
61
+        using: function setPositionTop(position, elements) {
62
+            var calcLeft = elements.target.left - elements.element.left +
63
+                elements.target.width/2;
64
+            $(".jitsipopover").css(
65
+                {top: position.top, left: position.left, display: "table"});
66
+            $(".jitsipopover > .arrow").css({left: calcLeft});
67
+            $(".jitsipopover > .jitsipopover__menu-padding").css(
68
+                {left: calcLeft - 50});
69
+        }
70
+    }
71
+};
2
 var JitsiPopover = (function () {
72
 var JitsiPopover = (function () {
3
     /**
73
     /**
4
      * The default options
74
      * The default options
7
         skin: 'white',
77
         skin: 'white',
8
         content: '',
78
         content: '',
9
         hasArrow: true,
79
         hasArrow: true,
10
-        onBeforePosition: undefined
80
+        onBeforePosition: undefined,
81
+        position: 'top'
11
     };
82
     };
12
 
83
 
13
     /**
84
     /**
21
     function JitsiPopover(element, options)
92
     function JitsiPopover(element, options)
22
     {
93
     {
23
         this.options = Object.assign({}, defaultOptions, options);
94
         this.options = Object.assign({}, defaultOptions, options);
24
-
25
         this.elementIsHovered = false;
95
         this.elementIsHovered = false;
26
         this.popoverIsHovered = false;
96
         this.popoverIsHovered = false;
27
         this.popoverShown = false;
97
         this.popoverShown = false;
45
      * Returns template for popover
115
      * Returns template for popover
46
      */
116
      */
47
     JitsiPopover.prototype.getTemplate = function () {
117
     JitsiPopover.prototype.getTemplate = function () {
118
+        const { hasArrow, position, skin } = this.options;
119
+
48
         let arrow = '';
120
         let arrow = '';
49
-        if (this.options.hasArrow) {
121
+        if (hasArrow) {
50
             arrow = '<div class="arrow"></div>';
122
             arrow = '<div class="arrow"></div>';
51
         }
123
         }
124
+
52
         return  (
125
         return  (
53
-            `<div class="jitsipopover ${this.options.skin}">
126
+            `<div class="jitsipopover ${skin} ${position}">
54
                 ${arrow}
127
                 ${arrow}
55
                 <div class="jitsipopover__content"></div>
128
                 <div class="jitsipopover__content"></div>
56
                 <div class="jitsipopover__menu-padding"></div>
129
                 <div class="jitsipopover__menu-padding"></div>
129
      * Refreshes the position of the popover.
202
      * Refreshes the position of the popover.
130
      */
203
      */
131
     JitsiPopover.prototype.refreshPosition = function () {
204
     JitsiPopover.prototype.refreshPosition = function () {
132
-        $(".jitsipopover").position({
133
-            my: "bottom",
134
-            at: "top",
135
-            collision: "fit",
136
-            of: this.element,
137
-            using: function (position, elements) {
138
-                var calcLeft = elements.target.left - elements.element.left +
139
-                    elements.target.width/2;
140
-                $(".jitsipopover").css(
141
-                    {top: position.top, left: position.left, display: "table"});
142
-                $(".jitsipopover > .arrow").css({left: calcLeft});
143
-                $(".jitsipopover > .jitsipopover__menu-padding").css(
144
-                    {left: calcLeft - 50});
205
+        const positionOptions = Object.assign(
206
+            {},
207
+            positionConfigurations[this.options.position],
208
+            {
209
+                of: this.element
145
             }
210
             }
146
-        });
211
+        );
212
+        $(".jitsipopover").position(positionOptions);
147
     };
213
     };
148
 
214
 
149
     /**
215
     /**

+ 3
- 2
modules/UI/videolayout/ConnectionIndicator.js Wyświetl plik

1
-/* global $, APP */
1
+/* global $, APP, interfaceConfig */
2
 /* jshint -W101 */
2
 /* jshint -W101 */
3
 
3
 
4
 import JitsiPopover from "../util/JitsiPopover";
4
 import JitsiPopover from "../util/JitsiPopover";
309
     this.popover = new JitsiPopover($(element), {
309
     this.popover = new JitsiPopover($(element), {
310
         content: popoverContent,
310
         content: popoverContent,
311
         skin: "black",
311
         skin: "black",
312
-        onBeforePosition: el => APP.translation.translateElement(el)
312
+        onBeforePosition: el => APP.translation.translateElement(el),
313
+        position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
313
     });
314
     });
314
 
315
 
315
     // override popover show method to make sure we will update the content
316
     // override popover show method to make sure we will update the content

+ 65
- 13
modules/UI/videolayout/Filmstrip.js Wyświetl plik

1
-/* global $, APP, JitsiMeetJS, interfaceConfig */
1
+/* global $, APP, config, JitsiMeetJS, interfaceConfig */
2
+
3
+import {
4
+    setFilmstripRemoteVideosVisibility,
5
+    setFilmstripVisibility
6
+} from '../../../react/features/filmstrip';
2
 
7
 
3
 import UIEvents from "../../../service/UI/UIEvents";
8
 import UIEvents from "../../../service/UI/UIEvents";
4
 import UIUtil from "../util/UIUtil";
9
 import UIUtil from "../util/UIUtil";
14
         this.iconMenuUpClassName = 'icon-menu-up';
19
         this.iconMenuUpClassName = 'icon-menu-up';
15
         this.filmstripContainerClassName = 'filmstrip';
20
         this.filmstripContainerClassName = 'filmstrip';
16
         this.filmstrip = $('#remoteVideos');
21
         this.filmstrip = $('#remoteVideos');
22
+        this.filmstripRemoteVideos = $('#filmstripRemoteVideosContainer');
17
         this.eventEmitter = eventEmitter;
23
         this.eventEmitter = eventEmitter;
18
 
24
 
19
         // Show the toggle button and add event listeners only when out of
25
         // Show the toggle button and add event listeners only when out of
24
         }
30
         }
25
     },
31
     },
26
 
32
 
33
+    /**
34
+     * Sets a class on the remote videos container for CSS to adjust visibility
35
+     * of the remote videos. Will no-op if config.debug is truthy, as should be
36
+     * the case with torture tests.
37
+     *
38
+     * @param {boolean} shouldHide - True if remote videos should be hidden,
39
+     * false if they should be visible.
40
+     * @returns {void}
41
+     */
42
+    setRemoteVideoVisibility(shouldShow) {
43
+        // FIXME Checking config.debug is a grand hack to avoid fixing the
44
+        // torture tests after the 1-on-1 UI was implemented, which hides remote
45
+        // videos on 1-on-1 calls. If this check is to be kept, at least create
46
+        // new torture tests to verify 1-on-1 mode.
47
+        if (config.debug || config.disable1On1Mode) {
48
+            return;
49
+        }
50
+
51
+        APP.store.dispatch(setFilmstripRemoteVideosVisibility(shouldShow));
52
+        this.filmstripRemoteVideos.toggleClass('hide-videos', !shouldShow);
53
+    },
54
+
27
     /**
55
     /**
28
      * Initializes the filmstrip toolbar.
56
      * Initializes the filmstrip toolbar.
29
      */
57
      */
150
 
178
 
151
         // Emit/fire UIEvents.TOGGLED_FILMSTRIP.
179
         // Emit/fire UIEvents.TOGGLED_FILMSTRIP.
152
         const eventEmitter = this.eventEmitter;
180
         const eventEmitter = this.eventEmitter;
181
+        const isFilmstripVisible = this.isFilmstripVisible();
182
+
153
         if (eventEmitter) {
183
         if (eventEmitter) {
154
             eventEmitter.emit(
184
             eventEmitter.emit(
155
                 UIEvents.TOGGLED_FILMSTRIP,
185
                 UIEvents.TOGGLED_FILMSTRIP,
156
                 this.isFilmstripVisible());
186
                 this.isFilmstripVisible());
157
         }
187
         }
188
+        APP.store.dispatch(setFilmstripVisibility(isFilmstripVisible));
158
     },
189
     },
159
 
190
 
160
     /**
191
     /**
177
      * @returns {number} height
208
      * @returns {number} height
178
      */
209
      */
179
     getFilmstripHeight() {
210
     getFilmstripHeight() {
180
-        if (this.isFilmstripVisible()) {
211
+        // FIXME Make it more clear the getFilmstripHeight check is used in
212
+        // horizontal film strip mode for calculating how tall large video
213
+        // display should be.
214
+        if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) {
181
             return $(`.${this.filmstripContainerClassName}`).outerHeight();
215
             return $(`.${this.filmstripContainerClassName}`).outerHeight();
182
         } else {
216
         } else {
183
             return 0;
217
             return 0;
364
             (remoteLocalWidthRatio * numberRemoteThumbs + 1), availableHeight *
398
             (remoteLocalWidthRatio * numberRemoteThumbs + 1), availableHeight *
365
             interfaceConfig.LOCAL_THUMBNAIL_RATIO);
399
             interfaceConfig.LOCAL_THUMBNAIL_RATIO);
366
         const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
400
         const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
401
+
402
+        const removeVideoWidth = lW * remoteLocalWidthRatio;
403
+
404
+        let localVideo;
405
+        if (interfaceConfig.VERTICAL_FILMSTRIP) {
406
+            // scale both width and height
407
+            localVideo = {
408
+                thumbWidth: removeVideoWidth,
409
+                thumbHeight: h * remoteLocalWidthRatio
410
+            };
411
+        } else {
412
+            localVideo = {
413
+                thumbWidth: lW,
414
+                thumbHeight: h
415
+            };
416
+        }
417
+
367
         return {
418
         return {
368
-                    localVideo:{
369
-                        thumbWidth: lW,
370
-                        thumbHeight: h
371
-                    },
419
+                    localVideo,
372
                     remoteVideo: {
420
                     remoteVideo: {
373
-                        thumbWidth: lW * remoteLocalWidthRatio,
421
+                        thumbWidth: removeVideoWidth,
374
                         thumbHeight: h
422
                         thumbHeight: h
375
                     }
423
                     }
376
                 };
424
                 };
406
                 }));
454
                 }));
407
             }
455
             }
408
             promises.push(new Promise((resolve) => {
456
             promises.push(new Promise((resolve) => {
409
-                this.filmstrip.animate({
410
-                    // adds 2 px because of small video 1px border
411
-                    height: remote.thumbHeight + 2
412
-                }, this._getAnimateOptions(animate, resolve));
457
+                // Let CSS take care of height in vertical filmstrip mode.
458
+                if (interfaceConfig.VERTICAL_FILMSTRIP) {
459
+                    resolve();
460
+                } else {
461
+                    this.filmstrip.animate({
462
+                        // adds 2 px because of small video 1px border
463
+                        height: remote.thumbHeight + 2
464
+                    }, this._getAnimateOptions(animate, resolve));
465
+                }
413
             }));
466
             }));
414
 
467
 
415
             promises.push(new Promise(() => {
468
             promises.push(new Promise(() => {
456
         }
509
         }
457
 
510
 
458
         let localThumb = $("#localVideoContainer");
511
         let localThumb = $("#localVideoContainer");
459
-        let remoteThumbs = this.filmstrip.children(selector)
460
-            .not("#localVideoContainer");
512
+        let remoteThumbs = this.filmstripRemoteVideos.children(selector);
461
 
513
 
462
         // Exclude the local video container if it has been hidden.
514
         // Exclude the local video container if it has been hidden.
463
         if (localThumb.hasClass("hidden")) {
515
         if (localThumb.hasClass("hidden")) {

+ 3
- 2
modules/UI/videolayout/RemoteVideo.js Wyświetl plik

91
         content: popupMenuElement.outerHTML,
91
         content: popupMenuElement.outerHTML,
92
         skin: "black",
92
         skin: "black",
93
         hasArrow: false,
93
         hasArrow: false,
94
-        onBeforePosition: el => APP.translation.translateElement(el)
94
+        onBeforePosition: el => APP.translation.translateElement(el),
95
+        position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
95
     };
96
     };
96
     let element = $("#" + this.videoSpanId + " .remotevideomenu");
97
     let element = $("#" + this.videoSpanId + " .remotevideomenu");
97
     this.popover = new JitsiPopover(element, options);
98
     this.popover = new JitsiPopover(element, options);
800
     overlay.className = "videocontainer__hoverOverlay";
801
     overlay.className = "videocontainer__hoverOverlay";
801
     container.appendChild(overlay);
802
     container.appendChild(overlay);
802
 
803
 
803
-    var remotes = document.getElementById('remoteVideos');
804
+    var remotes = document.getElementById('filmstripRemoteVideosContainer');
804
     return remotes.appendChild(container);
805
     return remotes.appendChild(container);
805
 };
806
 };
806
 
807
 

+ 16
- 0
modules/UI/videolayout/VideoLayout.js Wyświetl plik

1
 /* global APP, $, interfaceConfig */
1
 /* global APP, $, interfaceConfig */
2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3
 
3
 
4
+import {
5
+    setFilmstripRemoteVideosCount
6
+} from '../../../react/features/filmstrip';
7
+
4
 import Filmstrip from "./Filmstrip";
8
 import Filmstrip from "./Filmstrip";
5
 import UIEvents from "../../../service/UI/UIEvents";
9
 import UIEvents from "../../../service/UI/UIEvents";
6
 import UIUtil from "../util/UIUtil";
10
 import UIUtil from "../util/UIUtil";
550
                 if (onComplete && typeof onComplete === "function")
554
                 if (onComplete && typeof onComplete === "function")
551
                     onComplete();
555
                     onComplete();
552
             });
556
             });
557
+
558
+        APP.store.dispatch(
559
+            setFilmstripRemoteVideosCount(this.getRemoteVideosCount()));
553
         return { localVideo, remoteVideo };
560
         return { localVideo, remoteVideo };
554
     },
561
     },
555
 
562
 
1133
      */
1140
      */
1134
     getLargeVideoWrapper() {
1141
     getLargeVideoWrapper() {
1135
         return this.getCurrentlyOnLargeContainer().$wrapper;
1142
         return this.getCurrentlyOnLargeContainer().$wrapper;
1143
+    },
1144
+
1145
+    /**
1146
+     * Returns the number of remove video ids.
1147
+     *
1148
+     * @returns {number} The number of remote videos.
1149
+     */
1150
+    getRemoteVideosCount() {
1151
+        return Object.keys(remoteVideos).length;
1136
     }
1152
     }
1137
 };
1153
 };
1138
 
1154
 

+ 62
- 29
react/features/conference/components/Conference.web.js Wyświetl plik

10
 import { Toolbox } from '../../toolbox';
10
 import { Toolbox } from '../../toolbox';
11
 import { HideNotificationBarStyle } from '../../unsupported-browser';
11
 import { HideNotificationBarStyle } from '../../unsupported-browser';
12
 import { VideoStatusLabel } from '../../video-status-label';
12
 import { VideoStatusLabel } from '../../video-status-label';
13
+import '../../filmstrip';
13
 
14
 
14
 declare var $: Function;
15
 declare var $: Function;
15
 declare var APP: Object;
16
 declare var APP: Object;
106
                                 src = 'images/spin.svg' />
107
                                 src = 'images/spin.svg' />
107
                         </span>
108
                         </span>
108
                     </div>
109
                     </div>
109
-                    <div className = 'filmstrip'>
110
-                        <div
111
-                            className = 'filmstrip__videos'
112
-                            id = 'remoteVideos'>
113
-                            <span
114
-                                className = 'videocontainer'
115
-                                id = 'localVideoContainer'>
116
-                                <div className = 'videocontainer__background' />
117
-                                <span id = 'localVideoWrapper' />
118
-                                <audio
119
-                                    autoPlay = { true }
120
-                                    id = 'localAudio'
121
-                                    muted = { true } />
122
-                                <div className = 'videocontainer__toolbar' />
123
-                                <div className = 'videocontainer__toptoolbar' />
124
-                                <div
125
-                                    className
126
-                                        = 'videocontainer__hoverOverlay' />
127
-                            </span>
128
-                            <audio
129
-                                id = 'userJoined'
130
-                                preload = 'auto'
131
-                                src = 'sounds/joined.wav' />
132
-                            <audio
133
-                                id = 'userLeft'
134
-                                preload = 'auto'
135
-                                src = 'sounds/left.wav' />
136
-                        </div>
137
-                    </div>
110
+                    { this._renderFilmstrip() }
138
                 </div>
111
                 </div>
139
 
112
 
140
                 <DialogContainer />
113
                 <DialogContainer />
143
             </div>
116
             </div>
144
         );
117
         );
145
     }
118
     }
119
+
120
+    /**
121
+     * Creates a React Element for displaying filmstrip videos.
122
+     *
123
+     * @private
124
+     * @returns {ReactElement}
125
+     */
126
+    _renderFilmstrip() {
127
+        return (
128
+            <div className = 'filmstrip'>
129
+                <div
130
+                    className = 'filmstrip__videos'
131
+                    id = 'remoteVideos'>
132
+                    <div
133
+                        className = 'filmstrip__videos'
134
+                        id = 'filmstripLocalVideo'>
135
+                        <span
136
+                            className = 'videocontainer'
137
+                            id = 'localVideoContainer'>
138
+                            <div className = 'videocontainer__background' />
139
+                            <span id = 'localVideoWrapper' />
140
+                            <audio
141
+                                autoPlay = { true }
142
+                                id = 'localAudio'
143
+                                muted = { true } />
144
+                            <div className = 'videocontainer__toolbar' />
145
+                            <div className = 'videocontainer__toptoolbar' />
146
+                            <div
147
+                                className
148
+                                    = 'videocontainer__hoverOverlay' />
149
+                        </span>
150
+                    </div>
151
+                    <div
152
+                        className = 'filmstrip__videos'
153
+                        id = 'filmstripRemoteVideos'>
154
+                        {
155
+
156
+                            /*
157
+                                This extra video container is needed for
158
+                                scrolling thumbnails in firefox, otherwise the
159
+                                flex thumbnails resize instead of causing
160
+                                overflow.
161
+                            */
162
+                        }
163
+                        <div
164
+                            className = 'remote-videos-container'
165
+                            id = 'filmstripRemoteVideosContainer' />
166
+                    </div>
167
+                    <audio
168
+                        id = 'userJoined'
169
+                        preload = 'auto'
170
+                        src = 'sounds/joined.wav' />
171
+                    <audio
172
+                        id = 'userLeft'
173
+                        preload = 'auto'
174
+                        src = 'sounds/left.wav' />
175
+                </div>
176
+            </div>
177
+        );
178
+    }
146
 }
179
 }
147
 
180
 
148
 export default reactReduxConnect()(Conference);
181
 export default reactReduxConnect()(Conference);

+ 35
- 0
react/features/filmstrip/actionTypes.js Wyświetl plik

1
+import { Symbol } from '../base/react';
2
+
3
+/**
4
+ * The type of action which signals to change the count of known remote videos
5
+ * displayed in the filmstrip.
6
+ *
7
+ * {
8
+ *     type: SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
9
+ *     remoteVideosCount: number
10
+ * }
11
+ */
12
+export const SET_FILMSTRIP_REMOTE_VIDEOS_COUNT
13
+    = Symbol('SET_FILMSTRIP_REMOTE_VIDEOS_COUNT');
14
+
15
+/**
16
+ * The type of action which signals to change the visibility of remote videos in
17
+ * the filmstrip.
18
+ *
19
+ * {
20
+ *     type: SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
21
+ *     removeVideosVisible: boolean
22
+ * }
23
+ */
24
+export const SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY
25
+    = Symbol('SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY');
26
+
27
+/**
28
+ * The type of action sets the visibility of the entire filmstrip;
29
+ *
30
+ * {
31
+ *     type: SET_FILMSTRIP_VISIBILITY,
32
+ *     visible: boolean
33
+ * }
34
+ */
35
+export const SET_FILMSTRIP_VISIBILITY = Symbol('SET_FILMSTRIP_VISIBILITY');

+ 54
- 0
react/features/filmstrip/actions.js Wyświetl plik

1
+import {
2
+    SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
3
+    SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
4
+    SET_FILMSTRIP_VISIBILITY
5
+} from './actionTypes';
6
+
7
+/**
8
+ * Sets the visibility of remote videos in the filmstrip.
9
+ *
10
+ * @param {boolean} remoteVideosVisible - Whether or not remote videos in the
11
+ * filmstrip should be visible.
12
+ * @returns {{
13
+ *     type: SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
14
+ *     remoteVideosVisible: boolean
15
+ * }}
16
+ */
17
+export function setFilmstripRemoteVideosVisibility(remoteVideosVisible) {
18
+    return {
19
+        type: SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
20
+        remoteVideosVisible
21
+    };
22
+}
23
+
24
+/**
25
+ * Sets how many remote videos are currently in the filmstrip.
26
+ *
27
+ * @param {number} remoteVideosCount - The number of remote videos.
28
+ * @returns {{
29
+ *     type: SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
30
+ *     remoteVideosCount: number
31
+ * }}
32
+ */
33
+export function setFilmstripRemoteVideosCount(remoteVideosCount) {
34
+    return {
35
+        type: SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
36
+        remoteVideosCount
37
+    };
38
+}
39
+
40
+/**
41
+ * Sets if the entire filmstrip should be visible.
42
+ *
43
+ * @param {boolean} visible - Whether not the filmstrip is visible.
44
+ * @returns {{
45
+ *     type: SET_FILMSTRIP_VISIBILITY,
46
+ *     visible: boolean
47
+ * }}
48
+ */
49
+export function setFilmstripVisibility(visible) {
50
+    return {
51
+        type: SET_FILMSTRIP_VISIBILITY,
52
+        visible
53
+    };
54
+}

react/features/filmstrip/components/Filmstrip.js → react/features/filmstrip/components/Filmstrip.native.js Wyświetl plik


+ 0
- 0
react/features/filmstrip/components/Filmstrip.web.js Wyświetl plik


+ 5
- 0
react/features/filmstrip/index.js Wyświetl plik

1
+export * from './actions';
2
+export * from './actionTypes';
1
 export * from './components';
3
 export * from './components';
4
+
5
+import './middleware';
6
+import './reducer';

+ 30
- 0
react/features/filmstrip/middleware.js Wyświetl plik

1
+import UIEvents from '../../../service/UI/UIEvents';
2
+
3
+import { MiddlewareRegistry } from '../base/redux';
4
+
5
+import {
6
+    SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
7
+    SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
8
+    SET_FILMSTRIP_VISIBILITY
9
+} from './actionTypes';
10
+
11
+declare var APP: Object;
12
+
13
+// eslint-disable-next-line no-unused-vars
14
+MiddlewareRegistry.register(store => next => action => {
15
+    const result = next(action);
16
+
17
+    switch (action.type) {
18
+    case SET_FILMSTRIP_REMOTE_VIDEOS_COUNT:
19
+    case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
20
+    case SET_FILMSTRIP_VISIBILITY: {
21
+        if (typeof APP !== 'undefined') {
22
+            APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
23
+
24
+        }
25
+        break;
26
+    }
27
+    }
28
+
29
+    return result;
30
+});

+ 36
- 0
react/features/filmstrip/reducer.js Wyświetl plik

1
+import { ReducerRegistry } from '../base/redux';
2
+import {
3
+    SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
4
+    SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
5
+    SET_FILMSTRIP_VISIBILITY
6
+} from './actionTypes';
7
+
8
+const DEFAULT_STATE = {
9
+    remoteVideosCount: 0,
10
+    remoteVideosVisible: true,
11
+    visible: true
12
+};
13
+
14
+ReducerRegistry.register(
15
+    'features/filmstrip',
16
+    (state = DEFAULT_STATE, action) => {
17
+        switch (action.type) {
18
+        case SET_FILMSTRIP_REMOTE_VIDEOS_COUNT:
19
+            return {
20
+                ...state,
21
+                remoteVideosCount: action.remoteVideosCount
22
+            };
23
+        case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
24
+            return {
25
+                ...state,
26
+                remoteVideosVisible: action.remoteVideosVisible
27
+            };
28
+        case SET_FILMSTRIP_VISIBILITY:
29
+            return {
30
+                ...state,
31
+                visible: action.visible
32
+            };
33
+        }
34
+
35
+        return state;
36
+    });

+ 25
- 2
react/features/video-status-label/components/VideoStatusLabel.js Wyświetl plik

28
          */
28
          */
29
         _conferenceStarted: React.PropTypes.bool,
29
         _conferenceStarted: React.PropTypes.bool,
30
 
30
 
31
+        /**
32
+         * Whether or not the filmstrip is displayed with remote videos.
33
+         */
34
+        _filmstripVisible: React.PropTypes.bool,
35
+
31
         /**
36
         /**
32
          * Whether or not a high-definition large video is displayed.
37
          * Whether or not a high-definition large video is displayed.
33
          */
38
          */
64
      * @returns {ReactElement}
69
      * @returns {ReactElement}
65
      */
70
      */
66
     render() {
71
     render() {
67
-        const { _audioOnly, _conferenceStarted, _largeVideoHD, t } = this.props;
72
+        const {
73
+            _audioOnly,
74
+            _conferenceStarted,
75
+            _filmstripVisible,
76
+            _largeVideoHD,
77
+            t
78
+        } = this.props;
68
 
79
 
69
         // FIXME The _conferenceStarted check is used to be defensive against
80
         // FIXME The _conferenceStarted check is used to be defensive against
70
         // toggling audio only mode while there is no conference and hides the
81
         // toggling audio only mode while there is no conference and hides the
82
                 ? t('videoStatus.hd') : t('videoStatus.sd');
93
                 ? t('videoStatus.hd') : t('videoStatus.sd');
83
         }
94
         }
84
 
95
 
96
+        const filmstripClassName
97
+            = _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip';
98
+        const classNames
99
+            = `video-state-indicator moveToCorner ${filmstripClassName}`;
100
+
85
         return (
101
         return (
86
             <div
102
             <div
87
-                className = 'video-state-indicator moveToCorner'
103
+                className = { classNames }
88
                 id = 'videoResolutionLabel' >
104
                 id = 'videoResolutionLabel' >
89
                 { displayedLabel }
105
                 { displayedLabel }
90
                 { this._renderVideonMenu() }
106
                 { this._renderVideonMenu() }
152
         conference,
168
         conference,
153
         isLargeVideoHD
169
         isLargeVideoHD
154
     } = state['features/base/conference'];
170
     } = state['features/base/conference'];
171
+    const {
172
+        remoteVideosCount,
173
+        remoteVideosVisible,
174
+        visible
175
+    } = state['features/filmstrip'];
155
 
176
 
156
     return {
177
     return {
157
         _audioOnly: audioOnly,
178
         _audioOnly: audioOnly,
158
         _conferenceStarted: Boolean(conference),
179
         _conferenceStarted: Boolean(conference),
180
+        _filmstripVisible:
181
+            Boolean(remoteVideosCount && remoteVideosVisible && visible),
159
         _largeVideoHD: isLargeVideoHD
182
         _largeVideoHD: isLargeVideoHD
160
     };
183
     };
161
 }
184
 }

+ 6
- 0
service/UI/UIEvents.js Wyświetl plik

65
      * @see {TOGGLE_FILMSTRIP}
65
      * @see {TOGGLE_FILMSTRIP}
66
      */
66
      */
67
     TOGGLED_FILMSTRIP: "UI.toggled_filmstrip",
67
     TOGGLED_FILMSTRIP: "UI.toggled_filmstrip",
68
+
69
+    /**
70
+     * Notifies that the filmstrip has updated its appearance, such as by
71
+     * toggling or removing videos or adding videos.
72
+     */
73
+    UPDATED_FILMSTRIP_DISPLAY: "UI.updated_filmstrip_display",
68
     TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
74
     TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
69
     TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
75
     TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
70
     CONTACT_CLICKED: "UI.contact_clicked",
76
     CONTACT_CLICKED: "UI.contact_clicked",

Ładowanie…
Anuluj
Zapisz