diff --git a/config.js b/config.js index 4dda7b85f..9d6fb6a1e 100644 --- a/config.js +++ b/config.js @@ -70,4 +70,5 @@ var config = { 'During that time service will not be available. ' + 'Apologise for inconvenience.',*/ disableThirdPartyRequests: false, + minHDResolution: 540 }; diff --git a/css/main.css b/css/main.css index 5a937025f..d2b7d1c41 100644 --- a/css/main.css +++ b/css/main.css @@ -9,7 +9,7 @@ html, body{ color: #424242; font-family:'Helvetica Neue', Helvetica, sans-serif; font-weight: 400; - background: #000000; + background: #4E4E4E; overflow: hidden; } @@ -38,16 +38,6 @@ html, body{ position: relative; } -#toolbar a.button::after { - content: ''; - display: inline-block; - position: absolute; - left: 40px; - width: 1px; - height: 20px; - background: #373737; -} - #toolbar a.button:last-child::after { content: none; } @@ -57,7 +47,7 @@ html, body{ position: relative; color: #FFFFFF; top: 0; - padding: 10px 0; + padding: 9px 0; width: 38px; cursor: pointer; text-align: center; @@ -109,7 +99,7 @@ html, body{ height: 13px; line-height: 13px; font-weight: bold; - border-radius: 2px; + border-radius: 1px; font-size: 11px; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; } @@ -144,10 +134,8 @@ a.bottomToolbarButton:hover { top: 0px; cursor: pointer; background: rgba(255, 255, 255, 0.1); - border-radius: 6px; - background-clip: padding-box; - -webkit-border-radius: 6px; - -webkit-background-clip: padding-box; + border-radius: 1px; + -webkit-border-radius: 1px; } .no-fa-video-camera, .fa-microphone-slash { @@ -210,7 +198,7 @@ button { height: 35px; padding: 0 1em 0 2em; position: relative; - border-radius: 3px; + border-radius: 1px; font-weight: bold; color: #fff; line-height: 35px; @@ -272,10 +260,9 @@ div.feedbackButton:hover { margin-right: 5px; bottom: 40px; width: 29px; - border-radius: 6px; + border-radius: 1px; color: #FFF; - border: 1px solid rgba(256, 256, 256, 0.2); - background: rgba(0,0,0,0.8); + background: rgba(0,0,0,0.5); z-index: 6; /*+1 from #remoteVideos*/ } @@ -349,7 +336,7 @@ div.feedbackButton:hover { } #toast-container.notification-bottom-right { - bottom: 120px; + bottom: 140px; right: 5px; } diff --git a/css/popup_menu.css b/css/popup_menu.css index 300d95f4b..041e04229 100644 --- a/css/popup_menu.css +++ b/css/popup_menu.css @@ -13,7 +13,7 @@ ul.popupmenu { width: 100px; background-color: rgba(0,0,0,0.9); border: 1px solid rgba(256, 256, 256, 0.2); - border-radius:8px; + border-radius:3px; } ul.popupmenu:after { @@ -31,7 +31,7 @@ ul.popupmenu li { ul.popupmenu li:hover { background-color: rgba(256, 256, 256, .2); - border-radius:6px; + border-radius:3px; } /*Link Appearance*/ diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 73668cf56..e87860a8a 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -21,6 +21,7 @@ z-index: 5; transition: bottom 2s; overflow: visible !important; + font-size: 0pt; /*!!!Removes the gap between the local video container and the remote videos.*/ } #remotevideos.hidden { @@ -37,12 +38,12 @@ display: none; background-color: black; background-size: contain; - border-radius:8px; - border: 2px solid #212425; - margin-right: 3px; + border-radius:1px; + border: 1px solid #212425; + /*margin-right: 1px;*/ } -#remoteVideos .videocontainer:hover, +/*#remoteVideos .videocontainer:hover,*/ #remoteVideos .videocontainer.videoContainerFocused { cursor: hand; /* transform:scale(1.08, 1.08); @@ -56,25 +57,21 @@ } #remoteVideos .videocontainer:hover { - box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF; - border: 2px solid #FFFFFF; + border: 1px solid #c1c1c1; } #remoteVideos .videocontainer.videoContainerFocused { box-shadow: inset 0 0 28px #006d91; - border: 2px solid #006d91; + border: 1px solid #006d91; } #remoteVideos .videocontainer.videoContainerFocused:hover { - box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91; - border: 2px solid #FFFFFF; + box-shadow: inset 0 0 5px #c1c1c1, 0 0 10px #c1c1c1, inset 0 0 60px #006d91; + border: 1px solid #c1c1c1; } #localVideoWrapper { display:inline-block; - -webkit-mask-box-image: url(../images/videomask.svg); - border-radius:4px !important; - border: 0px !important; } /* With TemasysWebRTC plugin element is used @@ -82,7 +79,8 @@ #remoteVideos .videocontainer>video, #remoteVideos .videocontainer>object { cursor: hand; - border-radius:4px; + border-radius:1px; + object-fit: cover; } .flipVideoX { @@ -95,7 +93,8 @@ #localVideoWrapper>video, #localVideoWrapper>object { cursor: hand; - border-radius:4px !important; + border-radius:1px !important; + object-fit: cover; } #largeVideo, @@ -175,7 +174,7 @@ overflow: hidden; white-space: nowrap; z-index: 2; - border-radius:20px; + border-radius:3px; } .videocontainer>span.status { @@ -194,7 +193,7 @@ overflow: hidden; white-space: nowrap; z-index: 2; - border-radius:20px; + border-radius:3px; } .connectionindicator @@ -368,9 +367,8 @@ padding-right:2px; height:38px; width:auto; - background-color: rgba(0,0,0,0.8); - border: 1px solid rgba(256, 256, 256, 0.2); - border-radius: 6px; + background-color: rgba(0,0,0,0.5); + border-radius: 1px; pointer-events: auto; } @@ -393,14 +391,14 @@ display: inline-block; position: absolute; z-index: 0; - border-radius:10px; + border-radius:1px; pointer-events: none; } #dominantSpeaker { visibility: hidden; - width: 150px; - height: 150px; + width: 300px; + height: 300px; margin: auto; overflow: hidden; position: relative; @@ -419,21 +417,22 @@ } #dominantSpeakerAvatar { - width: 100px; - height: 100px; - top: 25px; + width: 200px; + height: 200px; + top: 50px; margin: auto; position: relative; - border-radius: 50px; + border-radius: 100px; z-index: 3; visibility: inherit; + background-color: #000000; } .userAvatar { height: 100%; position: absolute; - left: 35px; - border-radius: 200px; + left: 0; + border-radius: 2px; } .noMic { @@ -485,4 +484,15 @@ 0px 1px 1px rgba(0,0,0,0.3), 1px 0px 1px rgba(0,0,0,0.3), 0px 0px 1px rgba(0,0,0,0.3); +} + +#videoResolutionLabel { + display: none; + position: absolute; + top: 5px; + right: 5px; + background: rgba(0,0,0,.5); + padding: 10px; + color: rgba(255,255,255,.5); + z-index: 10000; } \ No newline at end of file diff --git a/css/welcome_page.css b/css/welcome_page.css index 3b3c54796..84435dc3c 100644 --- a/css/welcome_page.css +++ b/css/welcome_page.css @@ -42,11 +42,11 @@ } #enter_room_form { - border-radius: 10px; + border-radius: 1px; background-color: #FFFFFF; border: none; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; + -moz-border-radius: 1px; + -webkit-border-radius: 1px; -webkit-appearance: none; height: 55px; box-shadow: none; @@ -82,8 +82,8 @@ width: 73px; height: 45px; background-color: #16a8fe; - moz-border-radius: 10px; - -webkit-border-radius: 10px; + moz-border-radius: 1px; + -webkit-border-radius: 1px; color: #ffffff; font-weight: 600; border: none; diff --git a/index.html b/index.html index 4154cbdc5..ce8268e2b 100644 --- a/index.html +++ b/index.html @@ -151,6 +151,7 @@ + HD
diff --git a/interface_config.js b/interface_config.js index 7101d6d05..23668d8cc 100644 --- a/interface_config.js +++ b/interface_config.js @@ -1,6 +1,6 @@ var interfaceConfig = { CANVAS_EXTRA: 104, - CANVAS_RADIUS: 7, + CANVAS_RADIUS: 0, SHADOW_COLOR: '#ffffff', INITIAL_TOOLBAR_TIMEOUT: 20000, TOOLBAR_TIMEOUT: 4000, @@ -14,7 +14,6 @@ var interfaceConfig = { GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true, APP_NAME: "Jitsi Meet", INVITATION_POWERED_BY: true, - DOMINANT_SPEAKER_AVATAR_SIZE: 100, TOOLBAR_BUTTONS: ['authentication', 'microphone', 'camera', 'desktop', 'recording', 'security', 'invite', 'chat', 'etherpad', 'fullscreen', 'sip', 'dialpad', 'settings', 'hangup', 'filmstrip', @@ -30,5 +29,5 @@ var interfaceConfig = { filmStripOnly: false, RANDOM_AVATAR_URL_PREFIX: false, RANDOM_AVATAR_URL_SUFFIX: false, - FILM_STRIP_MAX_HEIGHT: 160 + FILM_STRIP_MAX_HEIGHT: 120 }; diff --git a/modules/UI/audio_levels/AudioLevels.js b/modules/UI/audio_levels/AudioLevels.js index 7e1bc68dd..68568710d 100644 --- a/modules/UI/audio_levels/AudioLevels.js +++ b/modules/UI/audio_levels/AudioLevels.js @@ -8,13 +8,16 @@ const LOCAL_LEVEL = 'local'; let ASDrawContext = null; let audioLevelCanvasCache = {}; +let dominantSpeakerAudioElement = null; -function initDominantSpeakerAudioLevels() { - let ASRadius = interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE / 2; - let ASCenter = (interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE + ASRadius) / 2; +function initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) { + let ASRadius = dominantSpeakerAvatarSize / 2; + let ASCenter = (dominantSpeakerAvatarSize + ASRadius) / 2; // Draw a circle. + ASDrawContext.beginPath(); ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI); + ASDrawContext.closePath(); // Add a shadow around the circle ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR; @@ -90,14 +93,14 @@ function getShadowLevel (audioLevel) { let shadowLevel = 0; if (audioLevel <= 0.3) { - shadowLevel - = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3)); + shadowLevel = Math.round( + interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3)); } else if (audioLevel <= 0.6) { - shadowLevel - = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3)); + shadowLevel = Math.round( + interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3)); } else { - shadowLevel - = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4)); + shadowLevel = Math.round( + interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4)); } return shadowLevel; @@ -124,8 +127,18 @@ function getVideoSpanId(id) { const AudioLevels = { init () { - ASDrawContext = $('#dominantSpeakerAudioLevel')[0].getContext('2d'); - initDominantSpeakerAudioLevels(); + dominantSpeakerAudioElement = $('#dominantSpeakerAudioLevel')[0]; + ASDrawContext = dominantSpeakerAudioElement.getContext('2d'); + + let parentContainer = $("#dominantSpeaker"); + let dominantSpeakerWidth = parentContainer.width(); + let dominantSpeakerHeight = parentContainer.height(); + + dominantSpeakerAudioElement.width = dominantSpeakerWidth; + dominantSpeakerAudioElement.height = dominantSpeakerHeight; + + let dominantSpeakerAvatar = $("#dominantSpeakerAvatar"); + initDominantSpeakerAudioLevels(dominantSpeakerAvatar.width()); }, /** @@ -155,8 +168,10 @@ const AudioLevels = { audioLevelCanvas = document.createElement('canvas'); audioLevelCanvas.className = "audiolevel"; - audioLevelCanvas.style.bottom = `-${interfaceConfig.CANVAS_EXTRA/2}px`; - audioLevelCanvas.style.left = `-${interfaceConfig.CANVAS_EXTRA/2}px`; + audioLevelCanvas.style.bottom + = `-${interfaceConfig.CANVAS_EXTRA/2}px`; + audioLevelCanvas.style.left + = `-${interfaceConfig.CANVAS_EXTRA/2}px`; resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight); videoSpan.appendChild(audioLevelCanvas); @@ -213,7 +228,10 @@ const AudioLevels = { return; } - ASDrawContext.clearRect(0, 0, 300, 300); + ASDrawContext.clearRect(0, 0, + dominantSpeakerAudioElement.width, + dominantSpeakerAudioElement.height); + if (!audioLevel) { return; } diff --git a/modules/UI/avatar/Avatar.js b/modules/UI/avatar/Avatar.js index 31b489d43..7fbadd444 100644 --- a/modules/UI/avatar/Avatar.js +++ b/modules/UI/avatar/Avatar.js @@ -44,18 +44,26 @@ export default { if (!avatarId) { console.warn( `No avatar stored yet for ${userId} - using ID as avatar ID`); + console.log("USER ID ", userId); avatarId = userId; } avatarId = MD5.hexdigest(avatarId.trim().toLowerCase()); - // Default to using gravatar. - let urlPref = 'https://www.gravatar.com/avatar/'; - let urlSuf = "?d=wavatar&size=100"; - if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) { + let urlPref = null; + let urlSuf = null; + if (!random) { + urlPref = 'https://www.gravatar.com/avatar/'; + urlSuf = "?d=wavatar&size=200"; + } + else if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) { urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX; urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX; } + else { + urlPref = 'https://robohash.org/'; + urlSuf = ".png?size=200x200"; + } return urlPref + avatarId + urlSuf; } diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js index 642dc9754..40bb85363 100644 --- a/modules/UI/videolayout/ConnectionIndicator.js +++ b/modules/UI/videolayout/ConnectionIndicator.js @@ -1,6 +1,7 @@ -/* global APP, $ */ +/* global APP, $, config */ /* jshint -W101 */ import JitsiPopover from "../util/JitsiPopover"; +import VideoLayout from "./VideoLayout"; /** * Constructs new connection indicator. @@ -14,6 +15,7 @@ function ConnectionIndicator(videoContainer, id) { this.bitrate = null; this.showMoreValue = false; this.resolution = null; + this.isResolutionHD = null; this.transport = []; this.popover = null; this.id = id; @@ -292,7 +294,6 @@ ConnectionIndicator.prototype.remove = function() { */ ConnectionIndicator.prototype.updateConnectionQuality = function (percent, object) { - if (percent === null) { this.connectionIndicatorContainer.style.display = "none"; this.popover.forceHide(); @@ -316,6 +317,10 @@ ConnectionIndicator.prototype.updateConnectionQuality = ConnectionIndicator.connectionQualityValues[quality]; } } + if (object.isResolutionHD) { + this.isResolutionHD = object.isResolutionHD; + } + this.updateResolutionIndicator(); this.updatePopoverData(); }; @@ -325,6 +330,7 @@ ConnectionIndicator.prototype.updateConnectionQuality = */ ConnectionIndicator.prototype.updateResolution = function (resolution) { this.resolution = resolution; + this.updateResolutionIndicator(); this.updatePopoverData(); }; @@ -354,4 +360,29 @@ ConnectionIndicator.prototype.hideIndicator = function () { this.popover.forceHide(); }; +/** + * Updates the resolution indicator. + */ +ConnectionIndicator.prototype.updateResolutionIndicator = function () { + + if (this.id !== null + && VideoLayout.isCurrentlyOnLarge(this.id)) { + + let showResolutionLabel = false; + + if (this.isResolutionHD !== null) + showResolutionLabel = this.isResolutionHD; + else if (this.resolution !== null) { + let resolutions = this.resolution || {}; + Object.keys(resolutions).map(function (ssrc) { + let {width, height} = resolutions[ssrc]; + if (height >= config.minHDResolution) + showResolutionLabel = true; + }); + } + + VideoLayout.updateResolutionLabel(showResolutionLabel); + } +}; + export default ConnectionIndicator; diff --git a/modules/UI/videolayout/FilmStrip.js b/modules/UI/videolayout/FilmStrip.js index 2e0a8ce50..19539f752 100644 --- a/modules/UI/videolayout/FilmStrip.js +++ b/modules/UI/videolayout/FilmStrip.js @@ -2,7 +2,7 @@ import UIUtil from "../util/UIUtil"; -const thumbAspectRatio = 16.0 / 9.0; +const thumbAspectRatio = 1 / 1; const FilmStrip = { init () { @@ -44,11 +44,7 @@ const FilmStrip = { * that we want to take into account when calculating the film strip width. */ calculateThumbnailSize (isSideBarVisible) { - // Calculate the available height, which is the inner window height - // minus 39px for the header minus 2px for the delimiter lines on the - // top and bottom of the large video, minus the 36px space inside the - // remoteVideos container used for highlighting shadow. - let availableHeight = 100; + let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT; let numvids = this.getThumbs(true).length; @@ -80,17 +76,17 @@ const FilmStrip = { let maxHeight // If the MAX_HEIGHT property hasn't been specified // we have the static value. - = Math.min( interfaceConfig.FILM_STRIP_MAX_HEIGHT || 160, + = Math.min( interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120, availableHeight); availableHeight - = Math.min( maxHeight, - availableWidth / thumbAspectRatio, - window.innerHeight - 18); + = Math.min( maxHeight, window.innerHeight - 18); - if (availableHeight < availableWidth / thumbAspectRatio) { - availableWidth = Math.floor(availableHeight * thumbAspectRatio); + if (availableHeight < availableWidth) { + availableWidth = availableHeight; } + else + availableHeight = availableWidth; return { thumbWidth: availableWidth, diff --git a/modules/UI/videolayout/LargeVideo.js b/modules/UI/videolayout/LargeVideo.js index 494062af9..b01b275da 100644 --- a/modules/UI/videolayout/LargeVideo.js +++ b/modules/UI/videolayout/LargeVideo.js @@ -8,7 +8,6 @@ import FilmStrip from './FilmStrip'; import Avatar from "../avatar/Avatar"; import {createDeferred} from '../../util/helpers'; -const avatarSize = interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE; const FADE_DURATION_MS = 300; /** @@ -175,6 +174,8 @@ class VideoContainer extends LargeContainer { this.$avatar = $('#dominantSpeaker'); this.$wrapper = $('#largeVideoWrapper'); + this.avatarHeight = $("#dominantSpeakerAvatar").height(); + // This does not work with Temasys plugin - has to be a property to be // copied between new elements //this.$video.on('play', onPlay); @@ -245,7 +246,7 @@ class VideoContainer extends LargeContainer { containerWidth, containerHeight); // update avatar position - let top = containerHeight / 2 - avatarSize / 4 * 3; + let top = containerHeight / 2 - this.avatarHeight / 4 * 3; this.$avatar.css('top', top); @@ -366,7 +367,8 @@ export default class LargeVideoManager { this.containers = {}; this.state = VideoContainerType; - this.videoContainer = new VideoContainer(() => this.resizeContainer(VideoContainerType)); + this.videoContainer + = new VideoContainer(() => this.resizeContainer(VideoContainerType)); this.addContainer(VideoContainerType, this.videoContainer); // use the same video container to handle and desktop tracks this.addContainer("desktop", this.videoContainer); @@ -381,22 +383,26 @@ export default class LargeVideoManager { }); if (interfaceConfig.SHOW_JITSI_WATERMARK) { - let leftWatermarkDiv = this.$container.find("div.watermark.leftwatermark"); + let leftWatermarkDiv + = this.$container.find("div.watermark.leftwatermark"); leftWatermarkDiv.css({display: 'block'}); - leftWatermarkDiv.parent().attr('href', interfaceConfig.JITSI_WATERMARK_LINK); + leftWatermarkDiv.parent().attr( + 'href', interfaceConfig.JITSI_WATERMARK_LINK); } if (interfaceConfig.SHOW_BRAND_WATERMARK) { - let rightWatermarkDiv = this.$container.find("div.watermark.rightwatermark"); + let rightWatermarkDiv + = this.$container.find("div.watermark.rightwatermark"); rightWatermarkDiv.css({ display: 'block', backgroundImage: 'url(images/rightwatermark.png)' }); - rightWatermarkDiv.parent().attr('href', interfaceConfig.BRAND_WATERMARK_LINK); + rightWatermarkDiv.parent().attr( + 'href', interfaceConfig.BRAND_WATERMARK_LINK); } if (interfaceConfig.SHOW_POWERED_BY) { @@ -472,7 +478,8 @@ export default class LargeVideoManager { return promise; }).then(() => { - // after everything is done check again if there are any pending new streams. + // after everything is done check again if there are any pending + // new streams. this.updateInProcess = false; this.scheduleLargeVideoUpdate(); }); diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 22e6e16ef..64ab698c6 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -974,6 +974,19 @@ var VideoLayout = { */ getLargeVideoID () { return largeVideo.id; + }, + + /** + * Updates the resolution label, indicating to the user that the large + * video stream is currently HD. + */ + updateResolutionLabel(isResolutionHD) { + let videoResolutionLabel = $("#videoResolutionLabel"); + + if (isResolutionHD && !videoResolutionLabel.is(":visible")) + videoResolutionLabel.css({display: "block"}); + else if (!isResolutionHD && videoResolutionLabel.is(":visible")) + videoResolutionLabel.css({display: "none"}); } };