diff --git a/conference.js b/conference.js index d27ff4278..b10f6c556 100644 --- a/conference.js +++ b/conference.js @@ -1279,10 +1279,6 @@ export default { APP.UI.updateRecordingState(status); }); - room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) { - APP.UI.updateUserStatus(id, status); - }); - room.on(ConferenceEvents.KICKED, () => { APP.UI.hideStats(); APP.UI.notifyKicked(); diff --git a/css/_mixins.scss b/css/_mixins.scss index ba1dedb8d..89ffa27c9 100644 --- a/css/_mixins.scss +++ b/css/_mixins.scss @@ -50,6 +50,15 @@ border-radius: 50%; } +/** +* Absolute position the element at the top left corner +**/ +@mixin topLeft() { + position: absolute; + top: 0; + left: 0; +} + @mixin absoluteAligning($sizeX, $sizeY) { top: 50%; left: 50%; diff --git a/css/_variables.scss b/css/_variables.scss index 7e0ec2c19..7050edeea 100644 --- a/css/_variables.scss +++ b/css/_variables.scss @@ -11,10 +11,10 @@ $hangupFontSize: 2em; $defaultToolbarSize: 50px; // Video layout. -$thumbnailIndicatorSize: 23px; +$thumbnailToolbarHeight: 22px; $thumbnailIndicatorBorder: 0px; +$thumbnailIndicatorSize: $thumbnailToolbarHeight; $thumbnailVideoMargin: 2px; -$thumbnailToolbarHeight: 25px; /** * Color variables. @@ -46,6 +46,7 @@ $thumbnailPictogramColor: #fff; $dominantSpeakerBg: #165ecc; $raiseHandBg: #D6D61E; $audioLevelBg: #44A5FF; +$connectionIndicatorBg: #165ecc; $audioLevelShadow: rgba(9, 36, 77, 0.9); $videoStateIndicatorColor: $defaultColor; $videoStateIndicatorBackground: $toolbarBackground; diff --git a/css/_videolayout_default.scss b/css/_videolayout_default.scss index aba12b0ff..bf99c3a0c 100644 --- a/css/_videolayout_default.scss +++ b/css/_videolayout_default.scss @@ -58,17 +58,36 @@ /** * The toolbar of the video thumbnail. */ -.videocontainer__toolbar { +.videocontainer__toolbar, +.videocontainer__toptoolbar { position: absolute; - bottom: 0; left: 0; - z-index: 1; + z-index: 3; width: 100%; box-sizing: border-box; // Includes the padding in the 100% width. - height: $thumbnailToolbarHeight; - max-height: 100%; - background-color: rgba(0, 0, 0, 0.5); +} + +.videocontainer__toolbar { + bottom: 0; padding: 0 5px 0 5px; + height: $thumbnailToolbarHeight; +} + +.videocontainer__toptoolbar { + $toolbarPadding: 5px; + top: 0; + padding: $toolbarPadding; + padding-bottom: 0; + height: $thumbnailIndicatorSize + $toolbarPadding; +} + +.videocontainer__hoverOverlay { + position: relative; + width: 100%; + height: 100%; + visibility: hidden; + background: rgba(0,0,0,.6); + z-index: 2; } #remoteVideos .videocontainer.videoContainerFocused, @@ -176,8 +195,10 @@ .videocontainer .editdisplayname { display: inline-block; position: absolute; - left: 30%; - width: 40%; + left: 10%; + width: 80%; + top: 50%; + @include transform(translateY(-40%)); color: $participantNameColor; text-align: center; text-overflow: ellipsis; @@ -200,72 +221,10 @@ padding: 0; } -.videocontainer>span.status { - display: inline-block; - position: absolute; - color: #FFFFFF; - background: rgba(0,0,0,.7); - text-align: center; - text-overflow: ellipsis; - width: 70%; - height: 15%; - left: 15%; - bottom: 2%; - padding: 5px; - font-size: 10pt; - overflow: hidden; - white-space: nowrap; - z-index: 2; - border-radius:3px; -} - -.connectionindicator -{ - display: inline-block; - position: absolute; - top: 7px; - right: 0; - padding: 0px 5px; - z-index: 3; - width: 18px; - height: 13px; -} - -.connection.connection_empty -{ - color: #8B8B8B;/*#FFFFFF*/ - overflow: hidden; -} - -.connection.connection_lost -{ - color: #8B8B8B; - overflow: visible; -} - -.connection.connection_full -{ - color: #FFFFFF;/*#15A1ED*/ - overflow: hidden; -} - -.connection -{ - position: absolute; - left: 0px; - top: 0px; - font-size: 8pt; - border: 0px; - width: 18px; - height: 13px; -} - -#localVideoContainer>span.status:hover, #localVideoContainer .displayname:hover { cursor: text; } -.videocontainer>span.status, .videocontainer .displayname { pointer-events: none; } @@ -278,7 +237,6 @@ pointer-events: auto !important; } -.videocontainer>a.status, .videocontainer>a.displayname { display: inline-block; position: absolute; @@ -323,25 +281,92 @@ margin: 0px 0px 0px 5px; } -.videocontainer>span.indicator { - position: absolute; - top: 0px; - left: 0px; - @include circle($thumbnailIndicatorSize); - box-sizing: border-box; - line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder; - z-index: 3; - text-align: center; - background: $dominantSpeakerBg; - margin: 7px; - display: inline-block; - color: $thumbnailPictogramColor; - font-size: 8pt; - border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor; +#raisehandindicator { + background: $raiseHandBg; } -.videocontainer>#raisehandindicator { - background: $raiseHandBg; +#connectionindicator { + background: $connectionIndicatorBg; +} + +.videocontainer__toptoolbar span.indicator { + position: relative; + font-size: 8pt; + text-align: center; + line-height: $thumbnailToolbarHeight; + display: none; + padding: 0; + margin-right: 5px; + float: left; + @include circle($thumbnailIndicatorSize); + box-sizing: border-box; + z-index: 3; + background: $dominantSpeakerBg; + color: $thumbnailPictogramColor; + border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor; + + .indicatoricon { + position: absolute; + top: 50%; + left: 0; + @include transform(translateY(-50%)); + width: $thumbnailIndicatorSize - 2 * $thumbnailIndicatorBorder; + height: $thumbnailIndicatorSize - 2 * $thumbnailIndicatorBorder; + line-height: $thumbnailIndicatorSize - 2 * $thumbnailIndicatorBorder; + } + + .connection { + position: relative; + margin: 0 auto; + width: 12px; + height: 8px; + + &_empty + { + @include topLeft(); + max-width: 12px; + width: 12px; + color: #8B8B8B;/*#FFFFFF*/ + overflow: hidden; + } + + &_lost + { + @include topLeft(); + max-width: 12px; + width: 12px; + color: #8B8B8B; + overflow: visible; + } + + &_full + { + @include topLeft(); + max-width: 12px; + width: 12px; + color: #FFFFFF;/*#15A1ED*/ + overflow: hidden; + } + } + + .icon-connection, + .icon-connection-lost { + font-size: 6pt; + } +} + +.remotevideomenu +{ + display: inline-block; + position: absolute; + top: 0px; + right: 0; + margin: 7px; + z-index: 3; + width: 18px; + height: 13px; + color: #FFF; + font-size: 8pt; } /** @@ -384,12 +409,6 @@ } } -#indicatoricon { - width: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder; - height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder; - line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder; -} - #reloadPresentation { display: none; position: absolute; diff --git a/index.html b/index.html index 3426833d3..0b48e162c 100644 --- a/index.html +++ b/index.html @@ -254,6 +254,8 @@
+
+
diff --git a/modules/UI/UI.js b/modules/UI/UI.js index b922bca71..76a919988 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -609,10 +609,6 @@ UI.removeUser = function (id, displayName) { VideoLayout.removeParticipantContainer(id); }; -UI.updateUserStatus = function (id, status) { - VideoLayout.setPresenceStatus(id, status); -}; - /** * Update videotype for specified user. * @param {string} id user id diff --git a/modules/UI/shared_video/SharedVideo.js b/modules/UI/shared_video/SharedVideo.js index 064e2f0c5..8991de840 100644 --- a/modules/UI/shared_video/SharedVideo.js +++ b/modules/UI/shared_video/SharedVideo.js @@ -625,7 +625,7 @@ function SharedVideoThumb (url) this.videoSpanId = "sharedVideoContainer"; this.container = this.createContainer(this.videoSpanId); this.container.onclick = this.videoClick.bind(this); - + this.bindHoverHandler(); SmallVideo.call(this, VideoLayout); this.isVideoMuted = true; } diff --git a/modules/UI/util/JitsiPopover.js b/modules/UI/util/JitsiPopover.js index f49c4ea50..26608ca8d 100644 --- a/modules/UI/util/JitsiPopover.js +++ b/modules/UI/util/JitsiPopover.js @@ -98,14 +98,27 @@ var JitsiPopover = (function () { var self = this; $(".jitsipopover").on("mouseenter", function () { self.popoverIsHovered = true; + if(typeof self.onHoverPopover === "function") { + self.onHoverPopover(self.popoverIsHovered); + } }).on("mouseleave", function () { self.popoverIsHovered = false; self.hide(); + if(typeof self.onHoverPopover === "function") { + self.onHoverPopover(self.popoverIsHovered); + } }); this.refreshPosition(); }; + /** + * Adds a hover listener to the popover. + */ + JitsiPopover.prototype.addOnHoverPopover = function (listener) { + this.onHoverPopover = listener; + }; + /** * Refreshes the position of the popover. */ diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js index fe4d1f739..8e7d74fc5 100644 --- a/modules/UI/util/UIUtil.js +++ b/modules/UI/util/UIUtil.js @@ -136,7 +136,7 @@ const TOOLTIP_POSITIONS = { element.setAttribute('data-i18n', '[content]' + key); APP.translation.translateElement($(element)); - } + } }, /** @@ -237,6 +237,18 @@ const TOOLTIP_POSITIONS = { $("#"+id).addClass("hide"); }, + /** + * Shows / hides the element with the given jQuery selector. + * + * @param {jQuery} selector the jQuery selector of the element to show/hide + * @param {boolean} isVisible + */ + setVisibility(selector, isVisible) { + if (selector && selector.length > 0) { + selector.css("visibility", isVisible ? "visible" : "hidden"); + } + }, + hideDisabledButtons: function (mappings) { var selector = Object.keys(mappings) .map(function (buttonName) { @@ -376,6 +388,49 @@ const TOOLTIP_POSITIONS = { "cursor": "default" }); } + }, + + /** + * Gets an "indicator" span for a video thumbnail. + * If element doesn't exist then creates it and appends + * video span container. + * + * @param {object} opts + * @param opts.indicatorId {String} - identificator of indicator + * @param opts.videoSpanId {String} - identificator of video span + * @param opts.content {String} HTML content of indicator + * @param opts.tooltip {String} - tooltip key for translation + * + * @returns {HTMLSpanElement} indicatorSpan + */ + getVideoThumbnailIndicatorSpan(opts = {}) { + let indicatorId = opts.indicatorId; + let videoSpanId = opts.videoSpanId; + let indicators = $(`#${videoSpanId} [id="${indicatorId}"]`); + let indicatorSpan; + + if (indicators.length <= 0) { + indicatorSpan = document.createElement('span'); + indicatorSpan.className = 'indicator'; + indicatorSpan.id = indicatorId; + + if(opts.content) { + indicatorSpan.innerHTML = opts.content; + } + + if (opts.tooltip) { + this.setTooltip(indicatorSpan, opts.tooltip, "top"); + APP.translation.translateElement($(indicatorSpan)); + } + + document.getElementById(videoSpanId) + .querySelector('.videocontainer__toptoolbar') + .appendChild(indicatorSpan); + } else { + indicatorSpan = indicators[0]; + } + + return indicatorSpan; } }; diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js index d8312cb1e..829c7c97d 100644 --- a/modules/UI/videolayout/ConnectionIndicator.js +++ b/modules/UI/videolayout/ConnectionIndicator.js @@ -2,13 +2,15 @@ /* jshint -W101 */ import JitsiPopover from "../util/JitsiPopover"; import VideoLayout from "./VideoLayout"; +import UIUtil from "../util/UIUtil"; /** * Constructs new connection indicator. * @param videoContainer the video container associated with the indicator. + * @param videoId the identifier of the video * @constructor */ -function ConnectionIndicator(videoContainer, id) { +function ConnectionIndicator(videoContainer, videoId) { this.videoContainer = videoContainer; this.bandwidth = null; this.packetLoss = null; @@ -18,7 +20,7 @@ function ConnectionIndicator(videoContainer, id) { this.isResolutionHD = null; this.transport = []; this.popover = null; - this.id = id; + this.id = videoId; this.create(); } @@ -32,12 +34,12 @@ function ConnectionIndicator(videoContainer, id) { * 0: string}} */ ConnectionIndicator.connectionQualityValues = { - 98: "18px", //full - 81: "15px",//4 bars - 64: "11px",//3 bars - 47: "7px",//2 bars - 30: "3px",//1 bar - 0: "0px"//empty + 98: "100%", //full + 81: "80%",//4 bars + 64: "55%",//3 bars + 47: "40%",//2 bars + 30: "20%",//1 bar + 0: "0"//empty }; ConnectionIndicator.getIP = function(value) { @@ -259,18 +261,22 @@ function createIcon(classes, iconClass) { * Creates the indicator */ ConnectionIndicator.prototype.create = function () { - this.connectionIndicatorContainer = document.createElement("div"); - this.connectionIndicatorContainer.className = "connectionindicator"; - this.connectionIndicatorContainer.style.display = "none"; - this.videoContainer.container.appendChild( - this.connectionIndicatorContainer); - this.popover = new JitsiPopover( - $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), { - content: "
", - skin: "black", - onBeforePosition: el => APP.translation.translateElement(el) - }); + let indicatorId = 'connectionindicator'; + let element = UIUtil.getVideoThumbnailIndicatorSpan({ + videoSpanId: this.videoContainer.videoSpanId, + indicatorId + }); + element.classList.add('show'); + this.connectionIndicatorContainer = element; + + let popoverContent = ( + `
` + ); + this.popover = new JitsiPopover($(element), { + content: popoverContent, + skin: "black", + onBeforePosition: el => APP.translation.translateElement(el) + }); // override popover show method to make sure we will update the content // before showing the popover @@ -283,13 +289,19 @@ ConnectionIndicator.prototype.create = function () { origShowFunc.call(this.popover); }.bind(this); - this.emptyIcon = this.connectionIndicatorContainer.appendChild( - createIcon(["connection", "connection_empty"], "icon-connection")); - this.fullIcon = this.connectionIndicatorContainer.appendChild( - createIcon(["connection", "connection_full"], "icon-connection")); - this.interruptedIndicator = this.connectionIndicatorContainer.appendChild( - createIcon(["connection", "connection_lost"],"icon-connection-lost")); + let connectionIconContainer = document.createElement('div'); + connectionIconContainer.className = 'connection indicatoricon'; + + + this.emptyIcon = connectionIconContainer.appendChild( + createIcon(["connection_empty"], "icon-connection")); + this.fullIcon = connectionIconContainer.appendChild( + createIcon(["connection_full"], "icon-connection")); + this.interruptedIndicator = connectionIconContainer.appendChild( + createIcon(["connection_lost"],"icon-connection-lost")); + $(this.interruptedIndicator).hide(); + this.connectionIndicatorContainer.appendChild(connectionIconContainer); }; /** @@ -320,7 +332,6 @@ ConnectionIndicator.prototype.updateConnectionStatusIndicator $(this.interruptedIndicator).show(); $(this.emptyIcon).hide(); $(this.fullIcon).hide(); - this.updateConnectionQuality(0 /* zero bars */); } }; @@ -427,4 +438,11 @@ ConnectionIndicator.prototype.updateResolutionIndicator = function () { } }; +/** + * Adds a hover listener to the popover. + */ +ConnectionIndicator.prototype.addPopoverHoverListener = function (listener) { + this.popover.addOnHoverPopover(listener); +}; + export default ConnectionIndicator; diff --git a/modules/UI/videolayout/LocalVideo.js b/modules/UI/videolayout/LocalVideo.js index 0aadf0e81..ff1c9ca22 100644 --- a/modules/UI/videolayout/LocalVideo.js +++ b/modules/UI/videolayout/LocalVideo.js @@ -11,6 +11,8 @@ function LocalVideo(VideoLayout, emitter) { this.videoSpanId = "localVideoContainer"; this.container = $("#localVideoContainer").get(0); this.localVideoId = null; + this.createConnectionIndicator(); + this.bindHoverHandler(); if(config.enableLocalVideoFlip) this._buildContextMenu(); this.isLocal = true; @@ -27,7 +29,6 @@ function LocalVideo(VideoLayout, emitter) { // Set default display name. this.setDisplayName(); - this.createConnectionIndicator(); this.addAudioLevelIndicator(); } @@ -70,7 +71,6 @@ LocalVideo.prototype.setDisplayName = function(displayName) { nameSpan = document.createElement('span'); nameSpan.className = 'displayname'; document.getElementById(this.videoSpanId) - .querySelector('.videocontainer__toolbar') .appendChild(nameSpan); @@ -104,7 +104,6 @@ LocalVideo.prototype.setDisplayName = function(displayName) { APP.translation.translateElement($(editableText)); this.container - .querySelector('.videocontainer__toolbar') .appendChild(editableText); var self = this; @@ -115,7 +114,7 @@ LocalVideo.prototype.setDisplayName = function(displayName) { e.preventDefault(); e.stopPropagation(); - $localDisplayName.hide(); + UIUtil.setVisibility($localDisplayName, false); $editDisplayName.show(); $editDisplayName.focus(); $editDisplayName.select(); @@ -123,7 +122,7 @@ LocalVideo.prototype.setDisplayName = function(displayName) { $editDisplayName.one("focusout", function () { self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value); $editDisplayName.hide(); - $localDisplayName.show(); + UIUtil.setVisibility($localDisplayName, true); }); $editDisplayName.on('keydown', function (e) { diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index 359d191d3..87015baa8 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -31,6 +31,7 @@ function RemoteVideo(user, VideoLayout, emitter) { this.addRemoteVideoContainer(); this.connectionIndicator = new ConnectionIndicator(this, this.id); this.setDisplayName(); + this.bindHoverHandler(); this.flipX = false; this.isLocal = false; /** @@ -233,11 +234,9 @@ if (!interfaceConfig.filmStripOnly) { RemoteVideo.prototype.addRemoteVideoMenu = function () { var spanElement = document.createElement('span'); - spanElement.className = 'remotevideomenu toolbar-icon right'; + spanElement.className = 'remotevideomenu'; - this.container - .querySelector('.videocontainer__toolbar') - .appendChild(spanElement); + this.container.appendChild(spanElement); var menuElement = document.createElement('i'); menuElement.className = 'icon-menu-up'; @@ -512,9 +511,10 @@ RemoteVideo.prototype.hideConnectionIndicator = function () { /** * Sets the display name for the given video span id. + * + * @param displayName the display name to set */ -RemoteVideo.prototype.setDisplayName = function(displayName, key) { - +RemoteVideo.prototype.setDisplayName = function(displayName) { if (!this.container) { console.warn( "Unable to set displayName - " + this.videoSpanId + " does not exist"); @@ -530,10 +530,6 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) { if (displaynameSpan.text() !== displayName) displaynameSpan.text(displayName); } - else if (key && key.length > 0) { - var nameHtml = APP.translation.generateTranslationHTML(key); - $('#' + this.videoSpanId + '_name').html(nameHtml); - } else $('#' + this.videoSpanId + '_name').text( interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME); @@ -541,7 +537,6 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) { nameSpan = document.createElement('span'); nameSpan.className = 'displayname'; $('#' + this.videoSpanId)[0] - .querySelector('.videocontainer__toolbar') .appendChild(nameSpan); if (displayName && displayName.length > 0) { @@ -573,10 +568,18 @@ RemoteVideo.createContainer = function (spanId) { container.id = spanId; container.className = 'videocontainer'; + let indicatorBar = document.createElement('div'); + indicatorBar.className = "videocontainer__toptoolbar"; + container.appendChild(indicatorBar); + let toolbar = document.createElement('div'); toolbar.className = "videocontainer__toolbar"; container.appendChild(toolbar); + let overlay = document.createElement('div'); + overlay.className = "videocontainer__hoverOverlay"; + container.appendChild(overlay); + var remotes = document.getElementById('remoteVideos'); return remotes.appendChild(container); }; diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index eb19178b2..04de14658 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -1,4 +1,4 @@ -/* global $, APP, JitsiMeetJS, interfaceConfig */ +/* global $, JitsiMeetJS, interfaceConfig */ import Avatar from "../avatar/Avatar"; import UIUtil from "../util/UIUtil"; import UIEvents from "../../../service/UI/UIEvents"; @@ -21,11 +21,27 @@ const DISPLAY_VIDEO = 0; const DISPLAY_AVATAR = 1; /** * Display mode constant used when neither video nor avatar is being displayed - * on the small video. + * on the small video. And we just show the display name. * @type {number} * @constant */ -const DISPLAY_BLACKNESS = 2; +const DISPLAY_BLACKNESS_WITH_NAME = 2; + +/** + * Display mode constant used when video is displayed and display name + * at the same time. + * @type {number} + * @constant + */ +const DISPLAY_VIDEO_WITH_NAME = 3; + +/** + * Display mode constant used when neither video nor avatar is being displayed + * on the small video. And we just show the display name. + * @type {number} + * @constant + */ +const DISPLAY_AVATAR_WITH_NAME = 4; function SmallVideo(VideoLayout) { this.isAudioMuted = false; @@ -34,12 +50,7 @@ function SmallVideo(VideoLayout) { this.videoStream = null; this.audioStream = null; this.VideoLayout = VideoLayout; -} - -function setVisibility(selector, show) { - if (selector && selector.length > 0) { - selector.css("visibility", show ? "visible" : "hidden"); - } + this.videoIsHovered = false; } /** @@ -60,18 +71,6 @@ SmallVideo.prototype.isVisible = function () { return $('#' + this.videoSpanId).is(':visible'); }; -SmallVideo.prototype.showDisplayName = function(isShow) { - var nameSpan = $('#' + this.videoSpanId + ' .displayname').get(0); - if (isShow) { - if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) - nameSpan.setAttribute("style", "display:inline-block;"); - } - else { - if (nameSpan) - nameSpan.setAttribute("style", "display:none;"); - } -}; - /** * Enables / disables the device availability icons for this small video. * @param {enable} set to {true} to enable and {false} to disable @@ -132,37 +131,6 @@ SmallVideo.prototype.getVideoType = function () { return this.videoType; }; -/** - * Shows the presence status message for the given video. - */ -SmallVideo.prototype.setPresenceStatus = function (statusMsg) { - if (!this.container) { - // No container - return; - } - - var statusSpan = $('#' + this.videoSpanId + '>span.status'); - if (!statusSpan.length) { - //Add status span - statusSpan = document.createElement('span'); - statusSpan.className = 'status'; - statusSpan.id = this.videoSpanId + '_status'; - $('#' + this.videoSpanId)[0].appendChild(statusSpan); - - statusSpan = $('#' + this.videoSpanId + '>span.status'); - } - - // Display status - if (statusMsg && statusMsg.length) { - $('#' + this.videoSpanId + '_status').text(statusMsg); - statusSpan.get(0).setAttribute("style", "display:inline-block;"); - } - else { - // Hide - statusSpan.get(0).setAttribute("style", "display:none;"); - } -}; - /** * Creates an audio or video element for a particular MediaStream. */ @@ -192,6 +160,29 @@ SmallVideo.getStreamElementID = function (stream) { return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId(); }; +/** + * Configures hoverIn/hoverOut handlers. Depends on connection indicator. + */ +SmallVideo.prototype.bindHoverHandler = function () { + // Add hover handler + $(this.container).hover( + () => { + this.videoIsHovered = true; + this.updateView(); + }, + () => { + this.videoIsHovered = false; + this.updateView(); + } + ); + if (this.connectionIndicator) { + this.connectionIndicator.addPopoverHoverListener( + () => { + this.updateView(); + }); + } +}; + /** * Updates the data for the indicator * @param id the id of the indicator @@ -395,6 +386,16 @@ SmallVideo.prototype.$avatar = function () { return $('#' + this.videoSpanId + ' .userAvatar'); }; +/** + * Returns the display name element, which appears on the video thumbnail. + * + * @return {jQuery} a jQuery selector pointing to the display name element of + * the video thumbnail + */ +SmallVideo.prototype.$displayName = function () { + return $('#' + this.videoSpanId + ' .displayname'); +}; + /** * Enables / disables the css responsible for focusing/pinning a video * thumbnail. @@ -445,19 +446,35 @@ SmallVideo.prototype.isVideoPlayable = function() { * Determines what should be display on the thumbnail. * * @return {number} one of DISPLAY_VIDEO,DISPLAY_AVATAR - * or DISPLAY_BLACKNESS. + * or DISPLAY_BLACKNESS_WITH_NAME. */ SmallVideo.prototype.selectDisplayMode = function() { // Display name is always and only displayed when user is on the stage if (this.isCurrentlyOnLargeVideo()) { - return DISPLAY_BLACKNESS; + return DISPLAY_BLACKNESS_WITH_NAME; } else if (this.isVideoPlayable() && this.selectVideoElement().length) { - return DISPLAY_VIDEO; + // check hovering and change state to video with name + return this._isHovered() ? + DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO; } else { - return DISPLAY_AVATAR; + // check hovering and change state to avatar with name + return this._isHovered() ? + DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR; } }; +/** + * Checks whether current video is considered hovered. Currently it is hovered + * if the mouse is over the video, or if the connection + * indicator is shown(hovered). + * @private + */ +SmallVideo.prototype._isHovered = function () { + return this.videoIsHovered + || (this.connectionIndicator + && this.connectionIndicator.popover.popoverIsHovered); +}; + /** * Hides or shows the user's avatar. * This update assumes that large video had been updated and we will @@ -479,10 +496,25 @@ SmallVideo.prototype.updateView = function () { // Determine whether video, avatar or blackness should be displayed let displayMode = this.selectDisplayMode(); - // Show/hide video - setVisibility(this.selectVideoElement(), displayMode === DISPLAY_VIDEO); - // Show/hide the avatar - setVisibility(this.$avatar(), displayMode === DISPLAY_AVATAR); + // Show/hide video. + UIUtil.setVisibility( this.selectVideoElement(), + (displayMode === DISPLAY_VIDEO + || displayMode === DISPLAY_VIDEO_WITH_NAME)); + // Show/hide the avatar. + UIUtil.setVisibility( this.$avatar(), + (displayMode === DISPLAY_AVATAR + || displayMode === DISPLAY_AVATAR_WITH_NAME)); + // Show/hide the display name. + UIUtil.setVisibility( this.$displayName(), + (displayMode === DISPLAY_BLACKNESS_WITH_NAME + || displayMode === DISPLAY_VIDEO_WITH_NAME + || displayMode === DISPLAY_AVATAR_WITH_NAME)); + // show hide overlay when there is a video or avatar under + // the display name + UIUtil.setVisibility( $('#' + this.videoSpanId + + ' .videocontainer__hoverOverlay'), + (displayMode === DISPLAY_AVATAR_WITH_NAME + || displayMode === DISPLAY_VIDEO_WITH_NAME)); }; SmallVideo.prototype.avatarChanged = function (avatarUrl) { @@ -519,13 +551,21 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) { return; } - var indicatorSpan = this.getIndicatorSpan({ - id: 'dominantspeakerindicator', - content: '', + let indicatorSpanId = "dominantspeakerindicator"; + let content = ``; + let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({ + videoSpanId: this.videoSpanId, + indicatorId: indicatorSpanId, + content, tooltip: 'speaker' }); - indicatorSpan.style.display = show ? "" : "none"; + if (show) { + indicatorSpan.classList.add('show'); + } else { + indicatorSpan.classList.remove('show'); + } }; /** @@ -539,43 +579,21 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) { return; } - var indicatorSpan = this.getIndicatorSpan({ - id: 'raisehandindicator', - content: '', + let indicatorSpanId = "raisehandindicator"; + let content = ``; + let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({ + indicatorId: indicatorSpanId, + videoSpanId: this.videoSpanId, + content, tooltip: 'raisedHand' }); - indicatorSpan.style.display = show ? "" : "none"; -}; - -/** - * Gets (creating if necessary) the "indicator" span for this SmallVideo. - * - * @param options.id {String} element ID - * @param options.content {String} HTML content of the indicator - * @param options.tooltip {String} The key that should be passed to tooltip - * - * @returns {HTMLElement} DOM represention of the indicator - */ -SmallVideo.prototype.getIndicatorSpan = function(options) { - var indicator = this.container.querySelector('#' + options.id); - - if (indicator) { - return indicator; + if (show) { + indicatorSpan.classList.add('show'); + } else { + indicatorSpan.classList.remove('show'); } - - indicator = document.createElement('span'); - indicator.className = 'indicator'; - indicator.id = options.id; - - indicator.innerHTML = options.content; - - UIUtil.setTooltip(indicator, options.tooltip, "top"); - APP.translation.translateElement($(indicator)); - - this.container.appendChild(indicator); - - return indicator; }; /** diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 749dd23ec..a38a4b2ca 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -449,15 +449,6 @@ var VideoLayout = { } }, - /** - * Shows the presence status message for the given video. - */ - setPresenceStatus (id, statusMsg) { - let remoteVideo = remoteVideos[id]; - if (remoteVideo) - remoteVideo.setPresenceStatus(statusMsg); - }, - /** * Shows a visual indicator for the moderator of the conference. * On local or remote participants.