Merge pull request #878 from jitsi/video-thumbnail-redesign

Video thumbnails redesign
This commit is contained in:
hristoterezov 2016-09-15 13:48:07 -05:00 committed by GitHub
commit 9b25467080
15 changed files with 329 additions and 175 deletions

View File

@ -16,7 +16,7 @@
font-weight: normal; font-weight: normal;
font-variant: normal; font-variant: normal;
text-transform: none; text-transform: none;
line-height: 0.75em; line-height: 1.22em;
font-size: 1.22em; font-size: 1.22em;
/* Better Font Rendering =========== */ /* Better Font Rendering =========== */

View File

@ -36,6 +36,19 @@
} }
} }
@mixin circle($diameter) {
width: $diameter;
height: $diameter;
border-radius: 50%;
}
@mixin absoluteAligning($sizeX, $sizeY) {
top: 50%;
left: 50%;
position: absolute;
@include transform(translate(-#{$sizeX / 2}, -#{$sizeY / 2}))
}
@mixin transform($func) { @mixin transform($func) {
-moz-transform: $func; -moz-transform: $func;
-ms-transform: $func; -ms-transform: $func;

View File

@ -10,6 +10,11 @@ $hangupFontSize: 2em;
*/ */
$defaultToolbarSize: 50px; $defaultToolbarSize: 50px;
// Video layout.
$thumbnailIndicatorSize: 23px;
$thumbnailIndicatorBorder: 0px;
$thumbnailVideoMargin: 2px;
/** /**
* Color variables. * Color variables.
*/ */
@ -17,13 +22,29 @@ $defaultColor: #F1F1F1;
$defaultSemiDarkColor: #ACACAC; $defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #4F4F4F; $defaultDarkColor: #4F4F4F;
$defaultBackground: #474747; $defaultBackground: #474747;
// Toolbar
$toolbarSelectBackground: rgba(0, 0, 0, .6); $toolbarSelectBackground: rgba(0, 0, 0, .6);
// Main controls
$inputBackground: rgba(132, 132, 132, .5); $inputBackground: rgba(132, 132, 132, .5);
$inputSemiBackground: rgba(132, 132, 132, .8); $inputSemiBackground: rgba(132, 132, 132, .8);
$inputLightBackground: #EBEBEB; $inputLightBackground: #EBEBEB;
$inputBorderColor: #EBEBEB; $inputBorderColor: #EBEBEB;
$buttonBackground: #44A5FF; $buttonBackground: #44A5FF;
// Video layout.
$videoThumbnailHovered: #44A5FF;
$videoThumbnailSelected: #165ecc;
$participantNameColor: #fff;
$thumbnailPictogramColor: #fff;
$dominantSpeakerBg: #165ecc;
$raiseHandBg: #D6D61E;
$rateStarDefault: #ccc;
$rateStarActivity: #165ecc;
$rateStarLabelColor: #333;
/** /**
* Misc. * Misc.
*/ */
@ -35,7 +56,3 @@ $defaultWatermarkLink: '../images/watermark.png';
*/ */
$toolbarZ: 900; $toolbarZ: 900;
$overlayZ: 800; $overlayZ: 800;
$rateStarDefault: #ccc;
$rateStarActivity: #f6c342;
$rateStarLabelColor: #333;

View File

@ -13,17 +13,17 @@
display: -ms-flexbox; display: -ms-flexbox;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
flex-direction: row; flex-direction: row-reverse;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-end; justify-content: flex-start;
position:absolute; position:absolute;
text-align:right; text-align:right;
height:196px; height:196px;
padding: 18px; padding: 10px 10px 10px 5px;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 20px; right: 0;
width:auto; width:auto;
border:1px solid transparent; border:1px solid transparent;
z-index: 5; z-index: 5;
@ -43,10 +43,23 @@
#remoteVideos .videocontainer { #remoteVideos .videocontainer {
display: none; display: none;
position: relative;
background-color: black; background-color: black;
background-size: contain; background-size: contain;
border-radius:1px; border-radius:1px;
border: 1px solid #212425; margin: 0 $thumbnailVideoMargin;
border: 1px solid $defaultDarkColor;
}
.videocontainer__toolbar {
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
width: 100%;
height: 25px;
max-height: 100%;
background-color: rgba(0, 0, 0, 0.5);
} }
#remoteVideos .videocontainer.videoContainerFocused { #remoteVideos .videocontainer.videoContainerFocused {
@ -58,18 +71,13 @@
-webkit-animation-iteration-count: 1; -webkit-animation-iteration-count: 1;
} }
#remoteVideos .videocontainer:hover {
border: 1px solid #c1c1c1;
}
#remoteVideos .videocontainer.videoContainerFocused { #remoteVideos .videocontainer.videoContainerFocused {
box-shadow: inset 0 0 28px #006d91; border: 1px solid $videoThumbnailSelected;
border: 1px solid #006d91;
} }
#remoteVideos .videocontainer:hover,
#remoteVideos .videocontainer.videoContainerFocused:hover { #remoteVideos .videocontainer.videoContainerFocused:hover {
box-shadow: inset 0 0 5px #c1c1c1, 0 0 10px #c1c1c1, inset 0 0 60px #006d91; border: 1px solid $videoThumbnailHovered;
border: 1px solid #c1c1c1;
} }
#localVideoWrapper { #localVideoWrapper {
@ -145,11 +153,11 @@
#remoteVideos .videocontainer>div.remotevideomenu { #remoteVideos .videocontainer>div.remotevideomenu {
position: absolute; position: absolute;
color: #FFFFFF; color: #FFFFFF;
top: 0; bottom: 0;
left: 0; right: 0;
padding: 5px 0px; padding: 5px 0px;
width: 25px; width: 25px;
font-size: 11pt; font-size: 9pt;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7); text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
border: 0px; border: 0px;
z-index: 2; z-index: 2;
@ -166,22 +174,28 @@
.videocontainer>span.displayname, .videocontainer>span.displayname,
.videocontainer>input.displayname { .videocontainer>input.displayname {
display: none; display: inline-block;
position: absolute; position: absolute;
color: #FFFFFF; bottom: 4px;
background: rgba(0,0,0,.7); left: 25%;
color: $participantNameColor;
text-align: center; text-align: center;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 70%; width: 50%;
height: 20%; font-size: 12px;
left: 15%; font-weight: 100;
top: 40%; letter-spacing: 1px;
padding: 5px;
font-size: 11pt;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
z-index: 2; z-index: 2;
border-radius:3px; }
.videocontainer>input.displayname {
outline: none;
border: none;
background: none;
box-shadow: none;
padding: 0;
} }
.videocontainer>span.status { .videocontainer>span.status {
@ -291,7 +305,8 @@
display: inline-block; display: inline-block;
position: absolute; position: absolute;
color: #FFFFFF; color: #FFFFFF;
top: 0; bottom: 0;
left: 0;
padding: 8px 5px; padding: 8px 5px;
width: 25px; width: 25px;
font-size: 8pt; font-size: 8pt;
@ -305,8 +320,7 @@
display: inline-block; display: inline-block;
position: absolute; position: absolute;
color: #FFFFFF; color: #FFFFFF;
top: 0; bottom: 0;
right: 0;
padding: 8px 5px; padding: 8px 5px;
width: 25px; width: 25px;
font-size: 8pt; font-size: 8pt;
@ -316,24 +330,30 @@
} }
.videocontainer>span.indicator { .videocontainer>span.indicator {
bottom: 0px; position: absolute;
top: 0px;
left: 0px; left: 0px;
width: 25px; @include circle($thumbnailIndicatorSize);
height: 25px; box-sizing: border-box;
line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
z-index: 3; z-index: 3;
text-align: center; text-align: center;
border-radius: 50%; background: $dominantSpeakerBg;
background: #21B9FC; margin: 7px;
margin: 5px;
display: inline-block; display: inline-block;
position: absolute; color: $thumbnailPictogramColor;
color: #FFFFFF; font-size: 8pt;
font-size: 11pt; border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
border: 0px; }
.videocontainer>#raisehandindicator {
background: $raiseHandBg;
} }
#indicatoricon { #indicatoricon {
padding-top: 5px; width: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
} }
#reloadPresentation { #reloadPresentation {
@ -395,10 +415,8 @@
} }
.userAvatar { .userAvatar {
height: 100%; @include circle(60px);
position: absolute; @include absoluteAligning(60px, 60px);
left: 0;
border-radius: 2px;
} }
.sharedVideoAvatar { .sharedVideoAvatar {

View File

@ -238,12 +238,13 @@
</div> </div>
<div id="remoteVideos"> <div id="remoteVideos">
<span id="localVideoContainer" class="videocontainer"> <span id="localVideoContainer" class="videocontainer videocontainer_small">
<span id="localVideoWrapper"> <span id="localVideoWrapper">
<!--<video id="localVideo" autoplay muted></video> - is now per stream generated --> <!--<video id="localVideo" autoplay muted></video> - is now per stream generated -->
</span> </span>
<audio id="localAudio" autoplay muted></audio> <audio id="localAudio" autoplay muted></audio>
<span class="focusindicator"></span> <span class="focusindicator"></span>
<div class="videocontainer__toolbar"></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

@ -34,5 +34,9 @@ var interfaceConfig = {
filmStripOnly: false, filmStripOnly: false,
RANDOM_AVATAR_URL_PREFIX: false, RANDOM_AVATAR_URL_PREFIX: false,
RANDOM_AVATAR_URL_SUFFIX: false, RANDOM_AVATAR_URL_SUFFIX: false,
FILM_STRIP_MAX_HEIGHT: 120 FILM_STRIP_MAX_HEIGHT: 120,
LOCAL_THUMBNAIL_RATIO_WIDTH: 16,
LOCAL_THUMBNAIL_RATIO_HEIGHT: 9,
REMOTE_THUMBNAIL_RATIO_WIDTH: 1,
REMOTE_THUMBNAIL_RATIO_HEIGHT: 1
}; };

View File

@ -1,5 +1,6 @@
/* global $, APP, config, interfaceConfig, JitsiMeetJS */ /* global $, APP, config, interfaceConfig, JitsiMeetJS */
import UIEvents from "../../service/UI/UIEvents"; import UIEvents from "../../service/UI/UIEvents";
import UIUtil from "./util/UIUtil";
/** /**
* Constructs the html for the overall feedback window. * Constructs the html for the overall feedback window.

View File

@ -10,7 +10,7 @@ let ASDrawContext = null;
let audioLevelCanvasCache = {}; let audioLevelCanvasCache = {};
let dominantSpeakerAudioElement = null; let dominantSpeakerAudioElement = null;
function initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) { function _initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) {
let ASRadius = dominantSpeakerAvatarSize / 2; let ASRadius = dominantSpeakerAvatarSize / 2;
let ASCenter = (dominantSpeakerAvatarSize + ASRadius) / 2; let ASCenter = (dominantSpeakerAvatarSize + ASRadius) / 2;
@ -28,7 +28,9 @@ function initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) {
/** /**
* Resizes the given audio level canvas to match the given thumbnail size. * Resizes the given audio level canvas to match the given thumbnail size.
*/ */
function resizeAudioLevelCanvas(audioLevelCanvas, thumbnailWidth, thumbnailHeight) { function _resizeAudioLevelCanvas( audioLevelCanvas,
thumbnailWidth,
thumbnailHeight) {
audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA; audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA; audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
} }
@ -138,18 +140,18 @@ const AudioLevels = {
dominantSpeakerAudioElement.height = dominantSpeakerHeight; dominantSpeakerAudioElement.height = dominantSpeakerHeight;
let dominantSpeakerAvatar = $("#dominantSpeakerAvatar"); let dominantSpeakerAvatar = $("#dominantSpeakerAvatar");
initDominantSpeakerAudioLevels(dominantSpeakerAvatar.width()); _initDominantSpeakerAudioLevels(dominantSpeakerAvatar.width());
}, },
/** /**
* Updates the audio level canvas for the given id. If the canvas * Updates the audio level canvas for the given id. If the canvas
* didn't exist we create it. * didn't exist we create it.
*/ */
updateAudioLevelCanvas (id, thumbWidth, thumbHeight) { createAudioLevelCanvas (id, thumbWidth, thumbHeight) {
let videoSpanId = 'localVideoContainer';
if (id) { let videoSpanId = (id === "local")
videoSpanId = `participant_${id}`; ? "localVideoContainer"
} : `participant_${id}`;
let videoSpan = document.getElementById(videoSpanId); let videoSpan = document.getElementById(videoSpanId);
@ -172,13 +174,13 @@ const AudioLevels = {
= `-${interfaceConfig.CANVAS_EXTRA/2}px`; = `-${interfaceConfig.CANVAS_EXTRA/2}px`;
audioLevelCanvas.style.left audioLevelCanvas.style.left
= `-${interfaceConfig.CANVAS_EXTRA/2}px`; = `-${interfaceConfig.CANVAS_EXTRA/2}px`;
resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight); _resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
videoSpan.appendChild(audioLevelCanvas); videoSpan.appendChild(audioLevelCanvas);
} else { } else {
audioLevelCanvas = audioLevelCanvas.get(0); audioLevelCanvas = audioLevelCanvas.get(0);
resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight); _resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
} }
}, },
@ -242,20 +244,30 @@ const AudioLevels = {
ASDrawContext.fill(); ASDrawContext.fill();
}, },
updateCanvasSize (thumbWidth, thumbHeight) { updateCanvasSize (localVideo, remoteVideo) {
let canvasWidth = thumbWidth + interfaceConfig.CANVAS_EXTRA; let localCanvasWidth
let canvasHeight = thumbHeight + interfaceConfig.CANVAS_EXTRA; = localVideo.thumbWidth + interfaceConfig.CANVAS_EXTRA;
let localCanvasHeight
= localVideo.thumbHeight + interfaceConfig.CANVAS_EXTRA;
let remoteCanvasWidth
= remoteVideo.thumbWidth + interfaceConfig.CANVAS_EXTRA;
let remoteCanvasHeight
= remoteVideo.thumbHeight + interfaceConfig.CANVAS_EXTRA;
FilmStrip.getThumbs().children('canvas').each(function () { let { remoteThumbs, localThumb } = FilmStrip.getThumbs();
$(this).attr('width', canvasWidth);
$(this).attr('height', canvasHeight); remoteThumbs.children('canvas').each(function () {
$(this).attr('width', remoteCanvasWidth);
$(this).attr('height', remoteCanvasHeight);
}); });
Object.keys(audioLevelCanvasCache).forEach(function (id) { if(localThumb) {
audioLevelCanvasCache[id].width = canvasWidth; localThumb.children('canvas').each(function () {
audioLevelCanvasCache[id].height = canvasHeight; $(this).attr('width', localCanvasWidth);
$(this).attr('height', localCanvasHeight);
}); });
} }
}
}; };
export default AudioLevels; export default AudioLevels;

View File

@ -7,7 +7,6 @@ import SideContainerToggler from "../side_pannels/SideContainerToggler";
let roomUrl = null; let roomUrl = null;
let emitter = null; let emitter = null;
/** /**
* Opens the invite link dialog. * Opens the invite link dialog.
*/ */

View File

@ -3,8 +3,6 @@
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil"; import UIUtil from "../util/UIUtil";
const thumbAspectRatio = 1 / 1;
const FilmStrip = { const FilmStrip = {
/** /**
* *
@ -66,13 +64,52 @@ const FilmStrip = {
- parseInt(this.filmStrip.css('paddingRight'), 10); - parseInt(this.filmStrip.css('paddingRight'), 10);
}, },
/** calculateThumbnailSize() {
* Calculates the thumbnail size. let availableSizes = this.calculateAvailableSize();
*/ let width = availableSizes.availableWidth;
calculateThumbnailSize () { let height = availableSizes.availableHeight;
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
let numvids = this.getThumbs(true).length; return this.calculateThumbnailSizeFromAvailable(width, height);
},
/**
* Normalizes local and remote thumbnail ratios
*/
normalizeThumbnailRatio () {
let remoteHeightRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_HEIGHT;
let remoteWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_WIDTH;
let localHeightRatio = interfaceConfig.LOCAL_THUMBNAIL_RATIO_HEIGHT;
let localWidthRatio = interfaceConfig.LOCAL_THUMBNAIL_RATIO_WIDTH;
let commonHeightRatio = remoteHeightRatio * localHeightRatio;
let localRatioCoefficient = localWidthRatio / localHeightRatio;
let remoteRatioCoefficient = remoteWidthRatio / remoteHeightRatio;
remoteWidthRatio = commonHeightRatio * remoteRatioCoefficient;
remoteHeightRatio = commonHeightRatio;
localWidthRatio = commonHeightRatio * localRatioCoefficient;
localHeightRatio = commonHeightRatio;
let localRatio = {
widthRatio: localWidthRatio,
heightRatio: localHeightRatio
};
let remoteRatio = {
widthRatio: remoteWidthRatio,
heightRatio: remoteHeightRatio
};
return { localRatio, remoteRatio };
},
calculateAvailableSize() {
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
let thumbs = this.getThumbs(true);
let numvids = thumbs.remoteThumbs.length;
let localVideoContainer = $("#localVideoContainer"); let localVideoContainer = $("#localVideoContainer");
@ -92,11 +129,10 @@ const FilmStrip = {
let availableWidth = videoAreaAvailableWidth; let availableWidth = videoAreaAvailableWidth;
// If the number of videos is 0 or undefined we don't need to calculate // If local thumb is not hidden
// further. if(thumbs.localThumb) {
if (numvids)
availableWidth = Math.floor( availableWidth = Math.floor(
(videoAreaAvailableWidth - numvids * ( (videoAreaAvailableWidth - (
UIUtil.parseCssInt( UIUtil.parseCssInt(
localVideoContainer.css('borderLeftWidth'), 10) localVideoContainer.css('borderLeftWidth'), 10)
+ UIUtil.parseCssInt( + UIUtil.parseCssInt(
@ -109,36 +145,90 @@ const FilmStrip = {
localVideoContainer.css('marginLeft'), 10) localVideoContainer.css('marginLeft'), 10)
+ UIUtil.parseCssInt( + UIUtil.parseCssInt(
localVideoContainer.css('marginRight'), 10))) localVideoContainer.css('marginRight'), 10)))
/ numvids); );
}
// If the number of videos is 0 or undefined we don't need to calculate
// further.
if (numvids) {
let remoteVideoContainer = thumbs.remoteThumbs.eq(0);
availableWidth = Math.floor(
(videoAreaAvailableWidth - numvids * (
UIUtil.parseCssInt(
remoteVideoContainer.css('borderLeftWidth'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('borderRightWidth'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('paddingLeft'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('paddingRight'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('marginLeft'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('marginRight'), 10)))
);
}
let maxHeight let maxHeight
// If the MAX_HEIGHT property hasn't been specified // If the MAX_HEIGHT property hasn't been specified
// we have the static value. // we have the static value.
= Math.min( interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120, = Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
availableHeight); availableHeight);
availableHeight availableHeight
= Math.min( maxHeight, window.innerHeight - 18); = Math.min(maxHeight, window.innerHeight - 18);
if (availableHeight < availableWidth) { return { availableWidth, availableHeight };
availableWidth = availableHeight; },
calculateThumbnailSizeFromAvailable(availableWidth, availableHeight) {
let { localRatio, remoteRatio } = this.normalizeThumbnailRatio();
let { remoteThumbs } = this.getThumbs(true);
let remoteProportion = remoteRatio.widthRatio * remoteThumbs.length;
let widthProportion = remoteProportion + localRatio.widthRatio;
let heightUnit = availableHeight / localRatio.heightRatio;
let widthUnit = availableWidth / widthProportion;
if (heightUnit < widthUnit) {
widthUnit = heightUnit;
} }
else else
availableHeight = availableWidth; heightUnit = widthUnit;
let localVideo = {
thumbWidth: widthUnit * localRatio.widthRatio,
thumbHeight: heightUnit * localRatio.heightRatio
};
let remoteVideo = {
thumbWidth: widthUnit * remoteRatio.widthRatio,
thumbHeight: widthUnit * remoteRatio.heightRatio
};
return { return {
thumbWidth: availableWidth, localVideo,
thumbHeight: availableHeight remoteVideo
}; };
}, },
resizeThumbnails (thumbWidth, thumbHeight, resizeThumbnails (local, remote,
animate = false, forceUpdate = false) { animate = false, forceUpdate = false) {
return new Promise(resolve => { return new Promise(resolve => {
this.getThumbs(!forceUpdate).animate({ let thumbs = this.getThumbs(!forceUpdate);
height: thumbHeight,
width: thumbWidth thumbs.localThumb.animate({
height: local.thumbHeight,
width: local.thumbWidth
}, {
queue: false,
duration: animate ? 500 : 0,
complete: resolve
});
thumbs.remoteThumbs.animate({
height: remote.thumbHeight,
width: remote.thumbWidth
}, { }, {
queue: false, queue: false,
duration: animate ? 500 : 0, duration: animate ? 500 : 0,
@ -147,7 +237,7 @@ const FilmStrip = {
this.filmStrip.animate({ this.filmStrip.animate({
// adds 2 px because of small video 1px border // adds 2 px because of small video 1px border
height: thumbHeight + 2 height: remote.thumbHeight + 2
}, { }, {
queue: false, queue: false,
duration: animate ? 500 : 0 duration: animate ? 500 : 0
@ -165,13 +255,19 @@ const FilmStrip = {
selector += ':visible'; selector += ':visible';
} }
// Exclude the local video container if it has been hidden. let localThumb = $("#localVideoContainer");
if ($("#localVideoContainer").hasClass("hidden")) let remoteThumbs = this.filmStrip.children(selector)
return this.filmStrip.children(selector)
.not("#localVideoContainer"); .not("#localVideoContainer");
else
return this.filmStrip.children(selector); // Exclude the local video container if it has been hidden.
if (localThumb.hasClass("hidden")) {
return { remoteThumbs };
} else {
return { remoteThumbs, localThumb };
} }
},
}; };
export default FilmStrip; export default FilmStrip;

View File

@ -11,7 +11,6 @@ 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.bindHoverHandler();
if(config.enableLocalVideoFlip) if(config.enableLocalVideoFlip)
this._buildContextMenu(); this._buildContextMenu();
this.isLocal = true; this.isLocal = true;
@ -44,7 +43,7 @@ function createEditDisplayNameButton() {
editButton.className = 'displayname'; editButton.className = 'displayname';
UIUtil.setTooltip(editButton, UIUtil.setTooltip(editButton,
"videothumbnail.editnickname", "videothumbnail.editnickname",
"top"); "left");
editButton.innerHTML = '<i class="icon-edit"></i>'; editButton.innerHTML = '<i class="icon-edit"></i>';
return editButton; return editButton;
@ -72,7 +71,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
if (displayName && displayName.length > 0) { if (displayName && displayName.length > 0) {
meHTML = APP.translation.generateTranslationHTML("me"); meHTML = APP.translation.generateTranslationHTML("me");
$('#localDisplayName').html( $('#localDisplayName').html(
UIUtil.escapeHtml(displayName) + ' (' + meHTML + ')' `${UIUtil.escapeHtml(displayName)} (${meHTML})`
); );
} else { } else {
$('#localDisplayName').html(defaultLocalDisplayName); $('#localDisplayName').html(defaultLocalDisplayName);
@ -80,11 +79,9 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
} }
this.updateView(); this.updateView();
} else { } else {
var editButton = createEditDisplayNameButton();
nameSpan = document.createElement('span'); nameSpan = document.createElement('span');
nameSpan.className = 'displayname'; nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0].appendChild(nameSpan); document.getElementById(this.videoSpanId).appendChild(nameSpan);
if (displayName && displayName.length > 0) { if (displayName && displayName.length > 0) {
@ -97,7 +94,6 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
nameSpan.id = 'localDisplayName'; nameSpan.id = 'localDisplayName';
this.container.appendChild(editButton);
//translates popover of edit button //translates popover of edit button
APP.translation.translateElement($("a.displayname")); APP.translation.translateElement($("a.displayname"));
@ -124,21 +120,23 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
var self = this; var self = this;
$('#localVideoContainer .displayname') $('#localVideoContainer .displayname')
.bind("click", function (e) { .bind("click", function (e) {
let $editDisplayName = $('#editDisplayName');
let $localDisplayName = $('#localDisplayName');
var editDisplayName = $('#editDisplayName');
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$('#localDisplayName').hide(); $localDisplayName.hide();
editDisplayName.show(); $editDisplayName.show();
editDisplayName.focus(); $editDisplayName.focus();
editDisplayName.select(); $editDisplayName.select();
editDisplayName.one("focusout", function (e) { $editDisplayName.one("focusout", function (e) {
self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value); self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
$('#editDisplayName').hide(); $editDisplayName.hide();
$localDisplayName.show();
}); });
editDisplayName.on('keydown', function (e) { $editDisplayName.on('keydown', function (e) {
if (e.keyCode === 13) { if (e.keyCode === 13) {
e.preventDefault(); e.preventDefault();
$('#editDisplayName').hide(); $('#editDisplayName').hide();

View File

@ -17,7 +17,6 @@ function RemoteVideo(id, VideoLayout, emitter) {
this.addRemoteVideoContainer(); this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(this, id); this.connectionIndicator = new ConnectionIndicator(this, id);
this.setDisplayName(); this.setDisplayName();
this.bindHoverHandler();
this.flipX = false; this.flipX = false;
this.isLocal = false; this.isLocal = false;
this.isMuted = false; this.isMuted = false;
@ -34,8 +33,10 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
if (APP.conference.isModerator) { if (APP.conference.isModerator) {
this.addRemoteVideoMenu(); this.addRemoteVideoMenu();
} }
let {thumbWidth, thumbHeight} = this.VideoLayout.resizeThumbnails();
AudioLevels.updateAudioLevelCanvas(this.id, thumbWidth, thumbHeight); let { remoteVideo } = this.VideoLayout.resizeThumbnails();
let { thumbHeight, thumbWidth } = remoteVideo;
AudioLevels.createAudioLevelCanvas(this.id, thumbWidth, thumbHeight);
return this.container; return this.container;
}; };
@ -427,12 +428,16 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
}; };
RemoteVideo.createContainer = function (spanId) { RemoteVideo.createContainer = function (spanId) {
var container = document.createElement('span'); let container = document.createElement('span');
container.id = spanId; container.id = spanId;
container.className = 'videocontainer'; container.className = 'videocontainer';
let toolbar = document.createElement('div');
toolbar.className = "videocontainer__toolbar";
container.appendChild(toolbar);
var remotes = document.getElementById('remoteVideos'); var remotes = document.getElementById('remoteVideos');
return remotes.appendChild(container); return remotes.appendChild(container);
}; };
export default RemoteVideo; export default RemoteVideo;

View File

@ -171,26 +171,6 @@ SmallVideo.getStreamElementID = function (stream) {
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId(); return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
}; };
/**
* Configures hoverIn/hoverOut handlers.
*/
SmallVideo.prototype.bindHoverHandler = function () {
// Add hover handler
var self = this;
$(this.container).hover(
function () {
self.showDisplayName(true);
},
function () {
// If the video has been "pinned" by the user we want to
// keep the display name on place.
if (!self.VideoLayout.isLargeVideoVisible() ||
!self.VideoLayout.isCurrentlyOnLarge(self.id))
self.showDisplayName(false);
}
);
};
/** /**
* 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
@ -219,6 +199,7 @@ SmallVideo.prototype.showAudioIndicator = function(isMuted) {
if (audioMutedSpan.length > 0) { if (audioMutedSpan.length > 0) {
audioMutedSpan.popover('hide'); audioMutedSpan.popover('hide');
audioMutedSpan.remove(); audioMutedSpan.remove();
this.updateIconPositions();
} }
} }
else { else {
@ -230,12 +211,14 @@ SmallVideo.prototype.showAudioIndicator = function(isMuted) {
"top"); "top");
this.container.appendChild(audioMutedSpan); this.container.appendChild(audioMutedSpan);
APP.translation.translateElement($('#' + this.videoSpanId + " > span")); APP.translation
.translateElement($('#' + this.videoSpanId + " > span"));
var mutedIndicator = document.createElement('i'); var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-mic-disabled'; mutedIndicator.className = 'icon-mic-disabled';
audioMutedSpan.appendChild(mutedIndicator); audioMutedSpan.appendChild(mutedIndicator);
} }
this.updateIconPositions(); this.updateIconPositions();
} }
this.isMuted = isMuted; this.isMuted = isMuted;
@ -254,6 +237,7 @@ SmallVideo.prototype.setMutedView = function(isMuted) {
if (isMuted === false) { if (isMuted === false) {
if (videoMutedSpan.length > 0) { if (videoMutedSpan.length > 0) {
videoMutedSpan.remove(); videoMutedSpan.remove();
this.updateIconPositions();
} }
} }
else { else {
@ -270,7 +254,8 @@ SmallVideo.prototype.setMutedView = function(isMuted) {
"top"); "top");
videoMutedSpan.appendChild(mutedIndicator); videoMutedSpan.appendChild(mutedIndicator);
//translate texts for muted indicator //translate texts for muted indicator
APP.translation.translateElement($('#' + this.videoSpanId + " > span > i")); APP.translation
.translateElement($('#' + this.videoSpanId + " > span > i"));
} }
this.updateIconPositions(); this.updateIconPositions();
@ -278,13 +263,18 @@ SmallVideo.prototype.setMutedView = function(isMuted) {
}; };
SmallVideo.prototype.updateIconPositions = function () { SmallVideo.prototype.updateIconPositions = function () {
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted'); let audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator'); let videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted'); audioMutedSpan.css({left: "0px"});
videoMutedSpan.css({left: (audioMutedSpan.length > 0? 25 : 0) + "px"});
var connectionIndicator
= $('#' + this.videoSpanId + '>div.connectionindicator');
if(connectionIndicator.length > 0 && if(connectionIndicator.length > 0 &&
connectionIndicator[0].style.display != "none") { connectionIndicator[0].style.display != "none") {
audioMutedSpan.css({right: "23px"}); audioMutedSpan.css({right: "23px"});
videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"}); videoMutedSpan.css({right:
((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
} else { } else {
audioMutedSpan.css({right: "0px"}); audioMutedSpan.css({right: "0px"});
videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"}); videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
@ -317,7 +307,8 @@ SmallVideo.prototype.createModeratorIndicatorElement = function () {
"top"); "top");
//translates text in focus indicators //translates text in focus indicators
APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator')); APP.translation
.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
}; };
/** /**
@ -406,8 +397,6 @@ SmallVideo.prototype.updateView = function () {
setVisibility(video, showVideo); setVisibility(video, showVideo);
} }
setVisibility(avatar, showAvatar); setVisibility(avatar, showAvatar);
this.showDisplayName(!showVideo && !showAvatar);
}; };
SmallVideo.prototype.avatarChanged = function (avatarUrl) { SmallVideo.prototype.avatarChanged = function (avatarUrl) {
@ -465,9 +454,8 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) {
var indicatorSpanId = "raisehandindicator"; var indicatorSpanId = "raisehandindicator";
var indicatorSpan = this.getIndicatorSpan(indicatorSpanId); var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
indicatorSpan.style.background = "#D6D61E";
indicatorSpan.innerHTML indicatorSpan.innerHTML
= "<i id='indicatoricon' class='fa fa-hand-paper-o'></i>"; = "<i id='indicatoricon' class='icon-raised-hand'></i>";
// adds a tooltip // adds a tooltip
UIUtil.setTooltip(indicatorSpan, "raisedHand", "left"); UIUtil.setTooltip(indicatorSpan, "raisedHand", "left");

View File

@ -105,8 +105,9 @@ var VideoLayout = {
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE); localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
// if we do not resize the thumbs here, if there is no video device // if we do not resize the thumbs here, if there is no video device
// the local video thumb maybe one pixel // the local video thumb maybe one pixel
let {thumbWidth, thumbHeight} = this.resizeThumbnails(false, true); let { localVideo } = this.resizeThumbnails(false, true);
AudioLevels.updateAudioLevelCanvas(null, thumbWidth, thumbHeight); AudioLevels.createAudioLevelCanvas(
"local", localVideo.thumbWidth, localVideo.thumbHeight);
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked); emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
this.lastNCount = config.channelLastN; this.lastNCount = config.channelLastN;
@ -254,7 +255,8 @@ var VideoLayout = {
electLastVisibleVideo () { electLastVisibleVideo () {
// pick the last visible video in the row // pick the last visible video in the row
// if nobody else is left, this picks the local video // if nobody else is left, this picks the local video
let thumbs = FilmStrip.getThumbs(true).filter('[id!="mixedstream"]'); let remoteThumbs = FilmStrip.getThumbs(true).remoteThumbs;
let thumbs = remoteThumbs.filter('[id!="mixedstream"]');
let lastVisible = thumbs.filter(':visible:last'); let lastVisible = thumbs.filter(':visible:last');
if (lastVisible.length) { if (lastVisible.length) {
@ -268,7 +270,7 @@ var VideoLayout = {
} }
console.info("Last visible video no longer exists"); console.info("Last visible video no longer exists");
thumbs = FilmStrip.getThumbs(); thumbs = FilmStrip.getThumbs().remoteThumbs;
if (thumbs.length) { if (thumbs.length) {
let id = getPeerContainerResourceId(thumbs[0]); let id = getPeerContainerResourceId(thumbs[0]);
if (remoteVideos[id]) { if (remoteVideos[id]) {
@ -401,7 +403,7 @@ var VideoLayout = {
// In case this is not currently in the last n we don't show it. // In case this is not currently in the last n we don't show it.
if (localLastNCount && localLastNCount > 0 && if (localLastNCount && localLastNCount > 0 &&
FilmStrip.getThumbs().length >= localLastNCount + 2) { FilmStrip.getThumbs().remoteThumbs.length >= localLastNCount + 2) {
remoteVideo.showPeerContainer('hide'); remoteVideo.showPeerContainer('hide');
} else { } else {
VideoLayout.resizeThumbnails(false, true); VideoLayout.resizeThumbnails(false, true);
@ -486,19 +488,19 @@ var VideoLayout = {
forceUpdate = false, forceUpdate = false,
onComplete = null) { onComplete = null) {
let {thumbWidth, thumbHeight} let { localVideo, remoteVideo }
= FilmStrip.calculateThumbnailSize(); = FilmStrip.calculateThumbnailSize();
$('.userAvatar').css('left', (thumbWidth - thumbHeight) / 2); let {thumbWidth, thumbHeight} = remoteVideo;
FilmStrip.resizeThumbnails(thumbWidth, thumbHeight, FilmStrip.resizeThumbnails(localVideo, remoteVideo,
animate, forceUpdate) animate, forceUpdate)
.then(function () { .then(function () {
AudioLevels.updateCanvasSize(thumbWidth, thumbHeight); AudioLevels.updateCanvasSize(localVideo, remoteVideo);
if (onComplete && typeof onComplete === "function") if (onComplete && typeof onComplete === "function")
onComplete(); onComplete();
}); });
return {thumbWidth, thumbHeight}; return { localVideo, remoteVideo };
}, },
/** /**
@ -656,7 +658,7 @@ var VideoLayout = {
var updateLargeVideo = false; var updateLargeVideo = false;
// Handle LastN/local LastN changes. // Handle LastN/local LastN changes.
FilmStrip.getThumbs().each(( index, element ) => { FilmStrip.getThumbs().remoteThumbs.each(( index, element ) => {
var resourceJid = getPeerContainerResourceId(element); var resourceJid = getPeerContainerResourceId(element);
var smallVideo = remoteVideos[resourceJid]; var smallVideo = remoteVideos[resourceJid];

View File

@ -21,18 +21,18 @@
"bootstrap": "3.1.1", "bootstrap": "3.1.1",
"events": "*", "events": "*",
"i18next-client": "1.7.7", "i18next-client": "1.7.7",
"jquery": "~2.1.1",
"jQuery-Impromptu": "git+https://github.com/trentrichardson/jQuery-Impromptu.git#v6.0.0", "jQuery-Impromptu": "git+https://github.com/trentrichardson/jQuery-Impromptu.git#v6.0.0",
"lib-jitsi-meet": "git+https://github.com/jitsi/lib-jitsi-meet.git", "jquery": "~2.1.1",
"jquery-contextmenu": "*", "jquery-contextmenu": "*",
"jquery-ui": "1.10.5", "jquery-ui": "1.10.5",
"jssha": "1.5.0", "jssha": "1.5.0",
"jws": "*",
"lib-jitsi-meet": "git+https://github.com/jitsi/lib-jitsi-meet.git",
"postis": "^2.2.0",
"retry": "0.6.1", "retry": "0.6.1",
"strophe": "^1.2.2", "strophe": "^1.2.2",
"strophejs-plugins": "^0.0.6", "strophejs-plugins": "^0.0.6",
"toastr": "^2.0.3", "toastr": "^2.0.3"
"postis": "^2.2.0",
"jws": "*"
}, },
"devDependencies": { "devDependencies": {
"babel-polyfill": "*", "babel-polyfill": "*",