Merge pull request #1071 from jitsi/ongoing-work-video-thumbnails

Ongoing work video thumbnails
This commit is contained in:
Paweł Domas 2016-10-27 17:13:27 -05:00 committed by GitHub
commit 2fe69d409b
14 changed files with 375 additions and 255 deletions

View File

@ -1279,10 +1279,6 @@ export default {
APP.UI.updateRecordingState(status); APP.UI.updateRecordingState(status);
}); });
room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
APP.UI.updateUserStatus(id, status);
});
room.on(ConferenceEvents.KICKED, () => { room.on(ConferenceEvents.KICKED, () => {
APP.UI.hideStats(); APP.UI.hideStats();
APP.UI.notifyKicked(); APP.UI.notifyKicked();

View File

@ -50,6 +50,15 @@
border-radius: 50%; border-radius: 50%;
} }
/**
* Absolute position the element at the top left corner
**/
@mixin topLeft() {
position: absolute;
top: 0;
left: 0;
}
@mixin absoluteAligning($sizeX, $sizeY) { @mixin absoluteAligning($sizeX, $sizeY) {
top: 50%; top: 50%;
left: 50%; left: 50%;

View File

@ -11,10 +11,10 @@ $hangupFontSize: 2em;
$defaultToolbarSize: 50px; $defaultToolbarSize: 50px;
// Video layout. // Video layout.
$thumbnailIndicatorSize: 23px; $thumbnailToolbarHeight: 22px;
$thumbnailIndicatorBorder: 0px; $thumbnailIndicatorBorder: 0px;
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
$thumbnailVideoMargin: 2px; $thumbnailVideoMargin: 2px;
$thumbnailToolbarHeight: 25px;
/** /**
* Color variables. * Color variables.
@ -46,6 +46,7 @@ $thumbnailPictogramColor: #fff;
$dominantSpeakerBg: #165ecc; $dominantSpeakerBg: #165ecc;
$raiseHandBg: #D6D61E; $raiseHandBg: #D6D61E;
$audioLevelBg: #44A5FF; $audioLevelBg: #44A5FF;
$connectionIndicatorBg: #165ecc;
$audioLevelShadow: rgba(9, 36, 77, 0.9); $audioLevelShadow: rgba(9, 36, 77, 0.9);
$videoStateIndicatorColor: $defaultColor; $videoStateIndicatorColor: $defaultColor;
$videoStateIndicatorBackground: $toolbarBackground; $videoStateIndicatorBackground: $toolbarBackground;

View File

@ -58,17 +58,36 @@
/** /**
* The toolbar of the video thumbnail. * The toolbar of the video thumbnail.
*/ */
.videocontainer__toolbar { .videocontainer__toolbar,
.videocontainer__toptoolbar {
position: absolute; position: absolute;
bottom: 0;
left: 0; left: 0;
z-index: 1; z-index: 3;
width: 100%; width: 100%;
box-sizing: border-box; // Includes the padding in the 100% width. 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; 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, #remoteVideos .videocontainer.videoContainerFocused,
@ -176,8 +195,10 @@
.videocontainer .editdisplayname { .videocontainer .editdisplayname {
display: inline-block; display: inline-block;
position: absolute; position: absolute;
left: 30%; left: 10%;
width: 40%; width: 80%;
top: 50%;
@include transform(translateY(-40%));
color: $participantNameColor; color: $participantNameColor;
text-align: center; text-align: center;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -200,72 +221,10 @@
padding: 0; 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 { #localVideoContainer .displayname:hover {
cursor: text; cursor: text;
} }
.videocontainer>span.status,
.videocontainer .displayname { .videocontainer .displayname {
pointer-events: none; pointer-events: none;
} }
@ -278,7 +237,6 @@
pointer-events: auto !important; pointer-events: auto !important;
} }
.videocontainer>a.status,
.videocontainer>a.displayname { .videocontainer>a.displayname {
display: inline-block; display: inline-block;
position: absolute; position: absolute;
@ -323,25 +281,92 @@
margin: 0px 0px 0px 5px; margin: 0px 0px 0px 5px;
} }
.videocontainer>span.indicator { #raisehandindicator {
position: absolute; background: $raiseHandBg;
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;
} }
.videocontainer>#raisehandindicator { #connectionindicator {
background: $raiseHandBg; 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 { #reloadPresentation {
display: none; display: none;
position: absolute; position: absolute;

View File

@ -254,6 +254,8 @@
</span> </span>
<audio id="localAudio" autoplay muted></audio> <audio id="localAudio" autoplay muted></audio>
<div class="videocontainer__toolbar"></div> <div class="videocontainer__toolbar"></div>
<div class="videocontainer__toptoolbar"></div>
<div class="videocontainer__hoverOverlay"></div>
</span> </span>
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio> <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio> <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>

View File

@ -609,10 +609,6 @@ UI.removeUser = function (id, displayName) {
VideoLayout.removeParticipantContainer(id); VideoLayout.removeParticipantContainer(id);
}; };
UI.updateUserStatus = function (id, status) {
VideoLayout.setPresenceStatus(id, status);
};
/** /**
* Update videotype for specified user. * Update videotype for specified user.
* @param {string} id user id * @param {string} id user id

View File

@ -625,7 +625,7 @@ function SharedVideoThumb (url)
this.videoSpanId = "sharedVideoContainer"; this.videoSpanId = "sharedVideoContainer";
this.container = this.createContainer(this.videoSpanId); this.container = this.createContainer(this.videoSpanId);
this.container.onclick = this.videoClick.bind(this); this.container.onclick = this.videoClick.bind(this);
this.bindHoverHandler();
SmallVideo.call(this, VideoLayout); SmallVideo.call(this, VideoLayout);
this.isVideoMuted = true; this.isVideoMuted = true;
} }

View File

@ -98,14 +98,27 @@ var JitsiPopover = (function () {
var self = this; var self = this;
$(".jitsipopover").on("mouseenter", function () { $(".jitsipopover").on("mouseenter", function () {
self.popoverIsHovered = true; self.popoverIsHovered = true;
if(typeof self.onHoverPopover === "function") {
self.onHoverPopover(self.popoverIsHovered);
}
}).on("mouseleave", function () { }).on("mouseleave", function () {
self.popoverIsHovered = false; self.popoverIsHovered = false;
self.hide(); self.hide();
if(typeof self.onHoverPopover === "function") {
self.onHoverPopover(self.popoverIsHovered);
}
}); });
this.refreshPosition(); this.refreshPosition();
}; };
/**
* Adds a hover listener to the popover.
*/
JitsiPopover.prototype.addOnHoverPopover = function (listener) {
this.onHoverPopover = listener;
};
/** /**
* Refreshes the position of the popover. * Refreshes the position of the popover.
*/ */

View File

@ -136,7 +136,7 @@ const TOOLTIP_POSITIONS = {
element.setAttribute('data-i18n', '[content]' + key); element.setAttribute('data-i18n', '[content]' + key);
APP.translation.translateElement($(element)); APP.translation.translateElement($(element));
} }
}, },
/** /**
@ -237,6 +237,18 @@ const TOOLTIP_POSITIONS = {
$("#"+id).addClass("hide"); $("#"+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) { hideDisabledButtons: function (mappings) {
var selector = Object.keys(mappings) var selector = Object.keys(mappings)
.map(function (buttonName) { .map(function (buttonName) {
@ -376,6 +388,49 @@ const TOOLTIP_POSITIONS = {
"cursor": "default" "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;
} }
}; };

View File

@ -2,13 +2,15 @@
/* jshint -W101 */ /* jshint -W101 */
import JitsiPopover from "../util/JitsiPopover"; import JitsiPopover from "../util/JitsiPopover";
import VideoLayout from "./VideoLayout"; import VideoLayout from "./VideoLayout";
import UIUtil from "../util/UIUtil";
/** /**
* Constructs new connection indicator. * Constructs new connection indicator.
* @param videoContainer the video container associated with the indicator. * @param videoContainer the video container associated with the indicator.
* @param videoId the identifier of the video
* @constructor * @constructor
*/ */
function ConnectionIndicator(videoContainer, id) { function ConnectionIndicator(videoContainer, videoId) {
this.videoContainer = videoContainer; this.videoContainer = videoContainer;
this.bandwidth = null; this.bandwidth = null;
this.packetLoss = null; this.packetLoss = null;
@ -18,7 +20,7 @@ function ConnectionIndicator(videoContainer, id) {
this.isResolutionHD = null; this.isResolutionHD = null;
this.transport = []; this.transport = [];
this.popover = null; this.popover = null;
this.id = id; this.id = videoId;
this.create(); this.create();
} }
@ -32,12 +34,12 @@ function ConnectionIndicator(videoContainer, id) {
* 0: string}} * 0: string}}
*/ */
ConnectionIndicator.connectionQualityValues = { ConnectionIndicator.connectionQualityValues = {
98: "18px", //full 98: "100%", //full
81: "15px",//4 bars 81: "80%",//4 bars
64: "11px",//3 bars 64: "55%",//3 bars
47: "7px",//2 bars 47: "40%",//2 bars
30: "3px",//1 bar 30: "20%",//1 bar
0: "0px"//empty 0: "0"//empty
}; };
ConnectionIndicator.getIP = function(value) { ConnectionIndicator.getIP = function(value) {
@ -259,18 +261,22 @@ function createIcon(classes, iconClass) {
* Creates the indicator * Creates the indicator
*/ */
ConnectionIndicator.prototype.create = function () { ConnectionIndicator.prototype.create = function () {
this.connectionIndicatorContainer = document.createElement("div"); let indicatorId = 'connectionindicator';
this.connectionIndicatorContainer.className = "connectionindicator"; let element = UIUtil.getVideoThumbnailIndicatorSpan({
this.connectionIndicatorContainer.style.display = "none"; videoSpanId: this.videoContainer.videoSpanId,
this.videoContainer.container.appendChild( indicatorId
this.connectionIndicatorContainer); });
this.popover = new JitsiPopover( element.classList.add('show');
$("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), { this.connectionIndicatorContainer = element;
content: "<div class=\"connection-info\" " +
"data-i18n='connectionindicator.na'></div>", let popoverContent = (
skin: "black", `<div class="connection-info" data-i18n="${indicatorId}.na"></div>`
onBeforePosition: el => APP.translation.translateElement(el) );
}); 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 // override popover show method to make sure we will update the content
// before showing the popover // before showing the popover
@ -283,13 +289,19 @@ ConnectionIndicator.prototype.create = function () {
origShowFunc.call(this.popover); origShowFunc.call(this.popover);
}.bind(this); }.bind(this);
this.emptyIcon = this.connectionIndicatorContainer.appendChild( let connectionIconContainer = document.createElement('div');
createIcon(["connection", "connection_empty"], "icon-connection")); connectionIconContainer.className = 'connection indicatoricon';
this.fullIcon = this.connectionIndicatorContainer.appendChild(
createIcon(["connection", "connection_full"], "icon-connection"));
this.interruptedIndicator = this.connectionIndicatorContainer.appendChild( this.emptyIcon = connectionIconContainer.appendChild(
createIcon(["connection", "connection_lost"],"icon-connection-lost")); 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.interruptedIndicator).hide();
this.connectionIndicatorContainer.appendChild(connectionIconContainer);
}; };
/** /**
@ -320,7 +332,6 @@ ConnectionIndicator.prototype.updateConnectionStatusIndicator
$(this.interruptedIndicator).show(); $(this.interruptedIndicator).show();
$(this.emptyIcon).hide(); $(this.emptyIcon).hide();
$(this.fullIcon).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; export default ConnectionIndicator;

View File

@ -11,6 +11,8 @@ function LocalVideo(VideoLayout, emitter) {
this.videoSpanId = "localVideoContainer"; this.videoSpanId = "localVideoContainer";
this.container = $("#localVideoContainer").get(0); this.container = $("#localVideoContainer").get(0);
this.localVideoId = null; this.localVideoId = null;
this.createConnectionIndicator();
this.bindHoverHandler();
if(config.enableLocalVideoFlip) if(config.enableLocalVideoFlip)
this._buildContextMenu(); this._buildContextMenu();
this.isLocal = true; this.isLocal = true;
@ -27,7 +29,6 @@ function LocalVideo(VideoLayout, emitter) {
// Set default display name. // Set default display name.
this.setDisplayName(); this.setDisplayName();
this.createConnectionIndicator();
this.addAudioLevelIndicator(); this.addAudioLevelIndicator();
} }
@ -70,7 +71,6 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
nameSpan = document.createElement('span'); nameSpan = document.createElement('span');
nameSpan.className = 'displayname'; nameSpan.className = 'displayname';
document.getElementById(this.videoSpanId) document.getElementById(this.videoSpanId)
.querySelector('.videocontainer__toolbar')
.appendChild(nameSpan); .appendChild(nameSpan);
@ -104,7 +104,6 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
APP.translation.translateElement($(editableText)); APP.translation.translateElement($(editableText));
this.container this.container
.querySelector('.videocontainer__toolbar')
.appendChild(editableText); .appendChild(editableText);
var self = this; var self = this;
@ -115,7 +114,7 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$localDisplayName.hide(); UIUtil.setVisibility($localDisplayName, false);
$editDisplayName.show(); $editDisplayName.show();
$editDisplayName.focus(); $editDisplayName.focus();
$editDisplayName.select(); $editDisplayName.select();
@ -123,7 +122,7 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
$editDisplayName.one("focusout", function () { $editDisplayName.one("focusout", function () {
self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value); self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
$editDisplayName.hide(); $editDisplayName.hide();
$localDisplayName.show(); UIUtil.setVisibility($localDisplayName, true);
}); });
$editDisplayName.on('keydown', function (e) { $editDisplayName.on('keydown', function (e) {

View File

@ -31,6 +31,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.addRemoteVideoContainer(); this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(this, this.id); this.connectionIndicator = new ConnectionIndicator(this, this.id);
this.setDisplayName(); this.setDisplayName();
this.bindHoverHandler();
this.flipX = false; this.flipX = false;
this.isLocal = false; this.isLocal = false;
/** /**
@ -233,11 +234,9 @@ if (!interfaceConfig.filmStripOnly) {
RemoteVideo.prototype.addRemoteVideoMenu = function () { RemoteVideo.prototype.addRemoteVideoMenu = function () {
var spanElement = document.createElement('span'); var spanElement = document.createElement('span');
spanElement.className = 'remotevideomenu toolbar-icon right'; spanElement.className = 'remotevideomenu';
this.container this.container.appendChild(spanElement);
.querySelector('.videocontainer__toolbar')
.appendChild(spanElement);
var menuElement = document.createElement('i'); var menuElement = document.createElement('i');
menuElement.className = 'icon-menu-up'; menuElement.className = 'icon-menu-up';
@ -512,9 +511,10 @@ RemoteVideo.prototype.hideConnectionIndicator = function () {
/** /**
* Sets the display name for the given video span id. * 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) { if (!this.container) {
console.warn( "Unable to set displayName - " + this.videoSpanId + console.warn( "Unable to set displayName - " + this.videoSpanId +
" does not exist"); " does not exist");
@ -530,10 +530,6 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
if (displaynameSpan.text() !== displayName) if (displaynameSpan.text() !== displayName)
displaynameSpan.text(displayName); displaynameSpan.text(displayName);
} }
else if (key && key.length > 0) {
var nameHtml = APP.translation.generateTranslationHTML(key);
$('#' + this.videoSpanId + '_name').html(nameHtml);
}
else else
$('#' + this.videoSpanId + '_name').text( $('#' + this.videoSpanId + '_name').text(
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME); interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
@ -541,7 +537,6 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
nameSpan = document.createElement('span'); nameSpan = document.createElement('span');
nameSpan.className = 'displayname'; nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0] $('#' + this.videoSpanId)[0]
.querySelector('.videocontainer__toolbar')
.appendChild(nameSpan); .appendChild(nameSpan);
if (displayName && displayName.length > 0) { if (displayName && displayName.length > 0) {
@ -573,10 +568,18 @@ RemoteVideo.createContainer = function (spanId) {
container.id = spanId; container.id = spanId;
container.className = 'videocontainer'; container.className = 'videocontainer';
let indicatorBar = document.createElement('div');
indicatorBar.className = "videocontainer__toptoolbar";
container.appendChild(indicatorBar);
let toolbar = document.createElement('div'); let toolbar = document.createElement('div');
toolbar.className = "videocontainer__toolbar"; toolbar.className = "videocontainer__toolbar";
container.appendChild(toolbar); container.appendChild(toolbar);
let overlay = document.createElement('div');
overlay.className = "videocontainer__hoverOverlay";
container.appendChild(overlay);
var remotes = document.getElementById('remoteVideos'); var remotes = document.getElementById('remoteVideos');
return remotes.appendChild(container); return remotes.appendChild(container);
}; };

View File

@ -1,4 +1,4 @@
/* global $, APP, JitsiMeetJS, interfaceConfig */ /* global $, JitsiMeetJS, interfaceConfig */
import Avatar from "../avatar/Avatar"; import Avatar from "../avatar/Avatar";
import UIUtil from "../util/UIUtil"; import UIUtil from "../util/UIUtil";
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from "../../../service/UI/UIEvents";
@ -21,11 +21,27 @@ const DISPLAY_VIDEO = 0;
const DISPLAY_AVATAR = 1; const DISPLAY_AVATAR = 1;
/** /**
* Display mode constant used when neither video nor avatar is being displayed * 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} * @type {number}
* @constant * @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) { function SmallVideo(VideoLayout) {
this.isAudioMuted = false; this.isAudioMuted = false;
@ -34,12 +50,7 @@ function SmallVideo(VideoLayout) {
this.videoStream = null; this.videoStream = null;
this.audioStream = null; this.audioStream = null;
this.VideoLayout = VideoLayout; this.VideoLayout = VideoLayout;
} this.videoIsHovered = false;
function setVisibility(selector, show) {
if (selector && selector.length > 0) {
selector.css("visibility", show ? "visible" : "hidden");
}
} }
/** /**
@ -60,18 +71,6 @@ SmallVideo.prototype.isVisible = function () {
return $('#' + this.videoSpanId).is(':visible'); 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. * Enables / disables the device availability icons for this small video.
* @param {enable} set to {true} to enable and {false} to disable * @param {enable} set to {true} to enable and {false} to disable
@ -132,37 +131,6 @@ SmallVideo.prototype.getVideoType = function () {
return this.videoType; 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. * Creates an audio or video element for a particular MediaStream.
*/ */
@ -192,6 +160,29 @@ SmallVideo.getStreamElementID = function (stream) {
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId(); 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 * Updates the data for the indicator
* @param id the id of the indicator * @param id the id of the indicator
@ -395,6 +386,16 @@ SmallVideo.prototype.$avatar = function () {
return $('#' + this.videoSpanId + ' .userAvatar'); 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 * Enables / disables the css responsible for focusing/pinning a video
* thumbnail. * thumbnail.
@ -445,19 +446,35 @@ SmallVideo.prototype.isVideoPlayable = function() {
* Determines what should be display on the thumbnail. * Determines what should be display on the thumbnail.
* *
* @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt> * @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
* or <tt>DISPLAY_BLACKNESS</tt>. * or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
*/ */
SmallVideo.prototype.selectDisplayMode = function() { SmallVideo.prototype.selectDisplayMode = function() {
// Display name is always and only displayed when user is on the stage // Display name is always and only displayed when user is on the stage
if (this.isCurrentlyOnLargeVideo()) { if (this.isCurrentlyOnLargeVideo()) {
return DISPLAY_BLACKNESS; return DISPLAY_BLACKNESS_WITH_NAME;
} else if (this.isVideoPlayable() && this.selectVideoElement().length) { } 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 { } 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. * Hides or shows the user's avatar.
* This update assumes that large video had been updated and we will * 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 // Determine whether video, avatar or blackness should be displayed
let displayMode = this.selectDisplayMode(); let displayMode = this.selectDisplayMode();
// Show/hide video // Show/hide video.
setVisibility(this.selectVideoElement(), displayMode === DISPLAY_VIDEO); UIUtil.setVisibility( this.selectVideoElement(),
// Show/hide the avatar (displayMode === DISPLAY_VIDEO
setVisibility(this.$avatar(), displayMode === DISPLAY_AVATAR); || 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) { SmallVideo.prototype.avatarChanged = function (avatarUrl) {
@ -519,13 +551,21 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
return; return;
} }
var indicatorSpan = this.getIndicatorSpan({ let indicatorSpanId = "dominantspeakerindicator";
id: 'dominantspeakerindicator', let content = `<i id="indicatoricon"
content: '<i id="indicatoricon" class="fa fa-bullhorn"></i>', ' class="indicatoricon fa fa-bullhorn"></i>`;
let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
videoSpanId: this.videoSpanId,
indicatorId: indicatorSpanId,
content,
tooltip: 'speaker' 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; return;
} }
var indicatorSpan = this.getIndicatorSpan({ let indicatorSpanId = "raisehandindicator";
id: 'raisehandindicator', let content = `<i id="indicatoricon"
content: '<i id="indicatoricon" class="icon-raised-hand"></i>', class="icon-raised-hand indicatoricon"></i>`;
let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
indicatorId: indicatorSpanId,
videoSpanId: this.videoSpanId,
content,
tooltip: 'raisedHand' tooltip: 'raisedHand'
}); });
indicatorSpan.style.display = show ? "" : "none"; if (show) {
}; indicatorSpan.classList.add('show');
} else {
/** indicatorSpan.classList.remove('show');
* 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;
} }
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;
}; };
/** /**

View File

@ -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. * Shows a visual indicator for the moderator of the conference.
* On local or remote participants. * On local or remote participants.