').appendTo(container).get(0)
+
resize();
volumeMeterStop()
@@ -115,11 +120,6 @@
// avatar: undefined // avatar image
}
- // Create a container for subscribers
- if (!subscribersContainer) {
- subscribersContainer = $('
').appendTo(container).get(0)
- }
-
// TODO: Make sure all supported callbacks exist, so we don't have to check
// their existence everywhere anymore
@@ -185,6 +185,12 @@
// Subscribe to the Stream to receive it
let subscriber = session.subscribe(event.stream, metadata.element, props);
+ Object.assign(metadata, {
+ audioActive: event.stream.audioActive,
+ videoActive: event.stream.videoActive,
+ videoDimensions: event.stream.videoDimensions
+ })
+
subscriber.on('videoElementCreated', event => {
$(event.element).prop({
tabindex: -1
@@ -193,9 +199,6 @@
resize()
})
- metadata.audioActive = event.stream.audioActive
- metadata.videoActive = event.stream.videoActive
-
// Update the wrapper controls/status
participantUpdate(metadata.element, metadata)
})
@@ -213,7 +216,12 @@
if (metadata) {
metadata[event.changedProperty] = event.newValue
- participantUpdate(metadata.element, metadata)
+
+ if (event.changedProperty == 'videoDimensions') {
+ resize()
+ } else {
+ participantUpdate(metadata.element, metadata)
+ }
}
})
@@ -991,6 +999,8 @@
* @param content Optional content to prepend to the element
*/
function publisherCreate(params, content) {
+ let isScreen = params.role & Roles.SCREEN
+
// Create the element
let wrapper = $(
'
'
@@ -1015,6 +1025,10 @@
wrapper.prepend(content)
}
+ if (isScreen) {
+ wrapper.addClass('screen')
+ }
+
if (params.isSelf) {
if (sessionData.onMediaSetup) {
wrapper.find('.link-setup').removeClass('hidden')
@@ -1055,7 +1069,9 @@
// Remove the subscriber element, if exists
$('#subscriber-' + params.connectionId).remove()
- return wrapper[params.isSelf ? 'prependTo' : 'appendTo'](container)
+ let prio = params.isSelf || (isScreen && !$(publishersContainer).children('.screen').length)
+
+ return wrapper[prio ? 'prependTo' : 'appendTo'](publishersContainer)
.attr('id', 'publisher-' + params.connectionId)
.get(0)
}
@@ -1357,12 +1373,8 @@
* Window onresize event handler (updates room layout)
*/
function resize() {
- containerWidth = container.offsetWidth
- containerHeight = container.offsetHeight
-
- if (subscribersContainer) {
- containerHeight -= subscribersContainer.offsetHeight
- }
+ containerWidth = publishersContainer.offsetWidth
+ containerHeight = publishersContainer.offsetHeight
updateLayout()
$(container).parent()[window.screen.width <= 768 ? 'addClass' : 'removeClass']('mobile')
@@ -1372,12 +1384,59 @@
* Update the room "matrix" layout
*/
function updateLayout() {
- let numOfVideos = $(container).find('.meet-video').length
+ let publishers = $(publishersContainer).find('.meet-video')
+ let numOfVideos = publishers.length
+
if (!numOfVideos) {
return
}
- let css, rows, cols, height
+ let css, rows, cols, height, padding = 0
+
+ // Make the first screen sharing tile big
+ let screenVideo = publishers.filter('.screen').find('video').get(0)
+
+ if (screenVideo) {
+ let element = screenVideo.parentNode
+ let connId = element.id.replace(/^publisher-/, '')
+ let connection = connections[connId]
+
+ // We know the shared screen video dimensions, we can calculate
+ // width/height of the tile in the matrix
+ if (connection && connection.videoDimensions) {
+ let screenWidth = connection.videoDimensions.width
+ let screenHeight = containerHeight
+
+ // TODO: When the shared window is minimized the width/height is set to 1 (or 2)
+ // - at least on my system. We might need to handle this case nicer. Right now
+ // it create a 1-2px line on the left of the matrix - not a big issue.
+ // TODO: Make the 0.666 factor bigger for wide screen and small number of participants?
+ let maxWidth = Math.ceil(containerWidth * 0.666)
+
+ if (screenWidth > maxWidth) {
+ screenWidth = maxWidth
+ }
+
+ // Set the tile position and size
+ $(element).css({
+ width: screenWidth + 'px',
+ height: screenHeight + 'px',
+ position: 'absolute',
+ top: 0,
+ left: 0
+ })
+
+ padding = screenWidth + 'px'
+
+ // Now the estate for the rest of participants is what's left on the right side
+ containerWidth -= screenWidth
+ publishers = publishers.not(element)
+ numOfVideos -= 1
+ }
+ }
+
+ // Compensate the shared screen estate with a padding
+ $(publishersContainer).css('padding-left', padding)
const factor = containerWidth / containerHeight
@@ -1420,27 +1479,12 @@
// console.log('factor=' + factor, 'num=' + numOfVideos, 'cols = '+cols, 'rows=' + rows);
- height = containerHeight / rows
- css = {
- width: (100 / cols) + '%',
+ // Update all tiles (except the main shared screen) in the matrix
+ publishers.css({
+ width: (containerWidth / cols) + 'px',
// Height must be in pixels to make object-fit:cover working
- height: height + 'px'
- }
-
- // Update the matrix
- $(container).find('.meet-video').css(css)
- /*
- .each((idx, elem) => {
- let video = $(elem).children('video')[0]
-
- if (video && video.videoWidth && video.videoHeight && video.videoWidth > video.videoHeight) {
- // Set max-width to keep the original aspect ratio in cases
- // when there's enough room to display the element
- let maxWidth = height * video.videoWidth / video.videoHeight
- $(elem).css('max-width', maxWidth)
- }
- })
- */
+ height: (containerHeight / rows) + 'px'
+ })
}
/**
diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss
--- a/src/resources/themes/meet.scss
+++ b/src/resources/themes/meet.scss
@@ -65,6 +65,12 @@
}
}
+ &.screen video {
+ // For shared screen videos we use the original aspect ratio
+ object-fit: scale-down;
+ background: none;
+ }
+
&.fullscreen {
video {
// We don't want the video to be cut in fullscreen
@@ -195,42 +201,20 @@
overflow: hidden;
}
-#meet-session {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- flex: 1;
- position: relative; // for #meet-subscribers positioning
+#meet-publishers {
+ height: 100%;
+ position: relative;
}
#meet-subscribers {
- display: flex;
- flex-wrap: wrap;
- order: 999;
padding: 0.15em;
- width: 100%;
overflow-y: auto;
- &:empty {
- display: none;
- }
-
.meet-subscriber {
margin: 0.15em;
max-width: calc(25% - 0.4em);
}
- // when the subscribers list is the only child this means
- // there's no publisher videos in the room yet
- &:only-child {
- justify-content: center;
- align-content: center;
- }
-
- &:not(:only-child) {
- max-height: 30%;
- }
-
// Language interpreters will be displayed as subscribers, but will have still
// the video element that we will hide
video {
@@ -238,6 +222,36 @@
}
}
+#meet-session {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow: hidden;
+
+ & > div {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ #meet-publishers:empty {
+ & + #meet-subscribers {
+ justify-content: center;
+ align-content: center;
+ }
+ }
+
+ #meet-publishers:not(:empty) {
+ & + #meet-subscribers {
+ max-height: 30%;
+ }
+ }
+}
+
#meet-chat {
width: 0;
display: none;
diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php
--- a/src/tests/Browser/Meet/RoomControlsTest.php
+++ b/src/tests/Browser/Meet/RoomControlsTest.php
@@ -362,7 +362,7 @@
->assertToolbarButtonState('screen', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED);
$guest
- ->whenAvailable('div.meet-video:nth-child(3)', function (Browser $browser) {
+ ->whenAvailable('div.meet-video.screen', function (Browser $browser) {
$browser->waitFor('video')
->assertSeeIn('.meet-nickname', 'john')
->assertVisible('.controls button.link-fullscreen')
diff --git a/src/tests/Browser/Meet/RoomModeratorTest.php b/src/tests/Browser/Meet/RoomModeratorTest.php
--- a/src/tests/Browser/Meet/RoomModeratorTest.php
+++ b/src/tests/Browser/Meet/RoomModeratorTest.php
@@ -78,7 +78,7 @@
->assertMissing('@session div.meet-subscriber:not(.self) svg.user') // owner
->assertVisible('@session div.meet-subscriber:not(.self) svg.moderator') // owner
->click('@session div.meet-subscriber.self .meet-nickname')
- ->whenAvailable('@session .dropdown-menu', function (Browser $browser) {
+ ->whenAvailable('@session div.meet-subscriber.self .dropdown-menu', function (Browser $browser) {
$browser->assertMissing('.permissions');
})
->click('@session div.meet-subscriber:not(.self) .meet-nickname')
@@ -101,7 +101,7 @@
->assertMissing('@session div.meet-subscriber.self svg.user') // self
->assertVisible('@session div.meet-subscriber.self svg.moderator') // self
->click('@session div.meet-subscriber.self .meet-nickname')
- ->whenAvailable('@session .dropdown-menu', function (Browser $browser) {
+ ->whenAvailable('@session div.meet-subscriber.self .dropdown-menu', function (Browser $browser) {
$browser->assertChecked('.action-role-moderator input')
->assertDisabled('.action-role-moderator input');
})