feat(VideoLayout): add remote connection problems UI
Grey filter will be applied to the remote video/avatar displayed on "large" and a message indicating remote connectivity issues will be shown on top of that.
This commit is contained in:
parent
42fd3097de
commit
661ea2cf45
|
@ -450,6 +450,11 @@
|
||||||
filter: grayscale(.5) opacity(0.8);
|
filter: grayscale(.5) opacity(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remoteVideoProblemFilter {
|
||||||
|
-webkit-filter: grayscale(100%);
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
.videoProblemFilter {
|
.videoProblemFilter {
|
||||||
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||||
filter: blur(10px) grayscale(.5) opacity(0.8);
|
filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||||
|
@ -460,6 +465,28 @@
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#remoteConnectionMessage {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
width: auto;
|
||||||
|
z-index: 1011;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
color: #FFF;
|
||||||
|
opacity: .80;
|
||||||
|
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||||
|
0px 1px 1px rgba(0,0,0,0.3),
|
||||||
|
1px 0px 1px rgba(0,0,0,0.3),
|
||||||
|
0px 0px 1px rgba(0,0,0,0.3);
|
||||||
|
|
||||||
|
background: rgba(0,0,0,.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
#videoConnectionMessage {
|
#videoConnectionMessage {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -228,6 +228,7 @@
|
||||||
<img id="dominantSpeakerAvatar" src=""/>
|
<img id="dominantSpeakerAvatar" src=""/>
|
||||||
<canvas id="dominantSpeakerAudioLevel"></canvas>
|
<canvas id="dominantSpeakerAudioLevel"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
<span id="remoteConnectionMessage"></span>
|
||||||
<div id="largeVideoWrapper">
|
<div id="largeVideoWrapper">
|
||||||
<video id="largeVideo" muted="true" autoplay></video>
|
<video id="largeVideo" muted="true" autoplay></video>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -324,7 +324,8 @@
|
||||||
"ATTACHED": "Attached",
|
"ATTACHED": "Attached",
|
||||||
"FETCH_SESSION_ID": "Obtaining session-id...",
|
"FETCH_SESSION_ID": "Obtaining session-id...",
|
||||||
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
||||||
"GET_SESSION_ID_ERROR": "Get session-id error: "
|
"GET_SESSION_ID_ERROR": "Get session-id error: ",
|
||||||
|
"USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues..."
|
||||||
},
|
},
|
||||||
"recording":
|
"recording":
|
||||||
{
|
{
|
||||||
|
|
|
@ -121,7 +121,8 @@ export default class LargeVideoManager {
|
||||||
|
|
||||||
// Include hide()/fadeOut only if we're switching between users
|
// Include hide()/fadeOut only if we're switching between users
|
||||||
let preUpdate;
|
let preUpdate;
|
||||||
if (this.newStreamData.id != this.id) {
|
let isUserSwitch = this.newStreamData.id != this.id;
|
||||||
|
if (isUserSwitch) {
|
||||||
preUpdate = container.hide();
|
preUpdate = container.hide();
|
||||||
} else {
|
} else {
|
||||||
preUpdate = Promise.resolve();
|
preUpdate = Promise.resolve();
|
||||||
|
@ -146,25 +147,46 @@ export default class LargeVideoManager {
|
||||||
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
|
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
|
||||||
// its stream whether exist and is muted to set isVideoMuted
|
// its stream whether exist and is muted to set isVideoMuted
|
||||||
// in rest of the cases it is false
|
// in rest of the cases it is false
|
||||||
let isVideoMuted = false;
|
let showAvatar = false;
|
||||||
if (videoType == VIDEO_CONTAINER_TYPE)
|
if (videoType == VIDEO_CONTAINER_TYPE)
|
||||||
isVideoMuted = stream ? stream.isMuted() : true;
|
showAvatar = stream ? stream.isMuted() : true;
|
||||||
|
|
||||||
// show the avatar on large if needed
|
// If the user's connection is disrupted then the avatar will be
|
||||||
container.showAvatar(isVideoMuted);
|
// displayed in case we have no video image cached. That is if
|
||||||
|
// there was a user switch(image is lost on stream detach) or if
|
||||||
|
// the video was not rendered, before the connection has failed.
|
||||||
|
let isHavingConnectivityIssues
|
||||||
|
= APP.conference.isParticipantConnectionActive(id) === false;
|
||||||
|
if (isHavingConnectivityIssues
|
||||||
|
&& (isUserSwitch | !container.wasVideoRendered)) {
|
||||||
|
showAvatar = true;
|
||||||
|
}
|
||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
// do not show stream if video is muted
|
// do not show stream if video is muted
|
||||||
// but we still should show watermark
|
// but we still should show watermark
|
||||||
if (isVideoMuted) {
|
if (showAvatar) {
|
||||||
this.showWatermark(true);
|
this.showWatermark(true);
|
||||||
// If the avatar is to be displayed the video should be hidden
|
// If the intention of this switch is to show the avatar
|
||||||
|
// we need to make sure that the video is hidden
|
||||||
promise = container.hide();
|
promise = container.hide();
|
||||||
} else {
|
} else {
|
||||||
promise = container.show();
|
promise = container.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// show the avatar on large if needed
|
||||||
|
container.showAvatar(showAvatar);
|
||||||
|
|
||||||
|
// Make sure no notification about remote failure is shown as
|
||||||
|
// it's UI conflicts with the one for local connection interrupted.
|
||||||
|
if (APP.conference.isConnectionInterrupted()) {
|
||||||
|
this.updateParticipantConnStatusIndication(id, true);
|
||||||
|
} else {
|
||||||
|
this.updateParticipantConnStatusIndication(
|
||||||
|
id, !isHavingConnectivityIssues);
|
||||||
|
}
|
||||||
|
|
||||||
// resolve updateLargeVideo promise after everything is done
|
// resolve updateLargeVideo promise after everything is done
|
||||||
promise.then(resolve);
|
promise.then(resolve);
|
||||||
|
|
||||||
|
@ -177,6 +199,38 @@ export default class LargeVideoManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/hides notification about participant's connectivity issues to be
|
||||||
|
* shown on the large video area.
|
||||||
|
*
|
||||||
|
* @param {string} id the id of remote participant(MUC nickname)
|
||||||
|
* @param {boolean} isConnected true if the connection is active or false
|
||||||
|
* when the user is having connectivity issues.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updateParticipantConnStatusIndication (id, isConnected) {
|
||||||
|
|
||||||
|
// Apply grey filter on the large video
|
||||||
|
this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
// Hide the message
|
||||||
|
this.showRemoteConnectionMessage(false);
|
||||||
|
} else {
|
||||||
|
// Get user's display name
|
||||||
|
let displayName
|
||||||
|
= APP.conference.getParticipantDisplayName(id);
|
||||||
|
this._setRemoteConnectionMessage(
|
||||||
|
"connection.USER_CONNECTION_INTERRUPTED",
|
||||||
|
{ displayName: displayName });
|
||||||
|
|
||||||
|
// Show it now only if the VideoContainer is on top
|
||||||
|
this.showRemoteConnectionMessage(
|
||||||
|
this.state === VIDEO_CONTAINER_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update large video.
|
* Update large video.
|
||||||
* Switches to large video even if previously other container was visible.
|
* Switches to large video even if previously other container was visible.
|
||||||
|
@ -274,11 +328,59 @@ export default class LargeVideoManager {
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
$('#videoConnectionMessage').css({display: "block"});
|
$('#videoConnectionMessage').css({display: "block"});
|
||||||
|
// Avatar message conflicts with 'videoConnectionMessage',
|
||||||
|
// so it must be hidden
|
||||||
|
this.showRemoteConnectionMessage(false);
|
||||||
} else {
|
} else {
|
||||||
$('#videoConnectionMessage').css({display: "none"});
|
$('#videoConnectionMessage').css({display: "none"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows hides the "avatar" message which is to be displayed either in
|
||||||
|
* the middle of the screen or below the avatar image.
|
||||||
|
*
|
||||||
|
* @param {null|boolean} show (optional) <tt>true</tt> to show the avatar
|
||||||
|
* message or <tt>false</tt> to hide it. If not provided then the connection
|
||||||
|
* status of the user currently on the large video will be obtained form
|
||||||
|
* "APP.conference" and the message will be displayed if the user's
|
||||||
|
* connection is interrupted.
|
||||||
|
*/
|
||||||
|
showRemoteConnectionMessage (show) {
|
||||||
|
if (typeof show !== 'boolean') {
|
||||||
|
show = APP.conference.isParticipantConnectionActive(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
$('#remoteConnectionMessage').css({display: "block"});
|
||||||
|
// 'videoConnectionMessage' message conflicts with 'avatarMessage',
|
||||||
|
// so it must be hidden
|
||||||
|
this.showVideoConnectionMessage(false);
|
||||||
|
} else {
|
||||||
|
$('#remoteConnectionMessage').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the text which describes that the remote user is having
|
||||||
|
* connectivity issues.
|
||||||
|
*
|
||||||
|
* @param {string} msgKey the translation key which will be used to get
|
||||||
|
* the message text.
|
||||||
|
* @param {object} msgOptions translation options object.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_setRemoteConnectionMessage (msgKey, msgOptions) {
|
||||||
|
if (msgKey) {
|
||||||
|
let text = APP.translation.translateString(msgKey, msgOptions);
|
||||||
|
$('#remoteConnectionMessage')
|
||||||
|
.attr("data-i18n", msgKey).text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoContainer.positionRemoteConnectionMessage();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updated the text which is to be shown on the top of large video.
|
* Updated the text which is to be shown on the top of large video.
|
||||||
*
|
*
|
||||||
|
@ -353,6 +455,7 @@ export default class LargeVideoManager {
|
||||||
if (this.state === VIDEO_CONTAINER_TYPE) {
|
if (this.state === VIDEO_CONTAINER_TYPE) {
|
||||||
this.showWatermark(false);
|
this.showWatermark(false);
|
||||||
this.showVideoConnectionMessage(false);
|
this.showVideoConnectionMessage(false);
|
||||||
|
this.showRemoteConnectionMessage(false);
|
||||||
}
|
}
|
||||||
oldContainer.hide();
|
oldContainer.hide();
|
||||||
|
|
||||||
|
@ -366,6 +469,10 @@ export default class LargeVideoManager {
|
||||||
// the container would be taking care of it by itself, but that
|
// the container would be taking care of it by itself, but that
|
||||||
// is a bigger refactoring
|
// is a bigger refactoring
|
||||||
this.showWatermark(true);
|
this.showWatermark(true);
|
||||||
|
// "avatar" and "video connection" can not be displayed both
|
||||||
|
// at the same time, but the latter is of higher priority and it
|
||||||
|
// will hide the avatar one if will be displayed.
|
||||||
|
this.showRemoteConnectionMessage(/* fet the current state */);
|
||||||
this.showVideoConnectionMessage(/* fetch the current state */);
|
this.showVideoConnectionMessage(/* fetch the current state */);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -173,8 +173,19 @@ export class VideoContainer extends LargeContainer {
|
||||||
|
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicates whether or not the avatar is currently displayed.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.avatarDisplayed = false;
|
||||||
this.$avatar = $('#dominantSpeaker');
|
this.$avatar = $('#dominantSpeaker');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A jQuery selector of the remote connection message.
|
||||||
|
* @type {jQuery|HTMLElement}
|
||||||
|
*/
|
||||||
|
this.$remoteConnectionMessage = $('#remoteConnectionMessage');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether or not the video stream attached to the video
|
* Indicates whether or not the video stream attached to the video
|
||||||
* element has started(which means that there is any image rendered
|
* element has started(which means that there is any image rendered
|
||||||
|
@ -266,6 +277,30 @@ export class VideoContainer extends LargeContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update position of the remote connection message which describes that
|
||||||
|
* the remote user is having connectivity issues.
|
||||||
|
*/
|
||||||
|
positionRemoteConnectionMessage () {
|
||||||
|
|
||||||
|
if (this.avatarDisplayed) {
|
||||||
|
let $avatarImage = $("#dominantSpeakerAvatar");
|
||||||
|
this.$remoteConnectionMessage.css(
|
||||||
|
'top',
|
||||||
|
$avatarImage.offset().top + $avatarImage.height() + 10);
|
||||||
|
} else {
|
||||||
|
let height = this.$remoteConnectionMessage.height();
|
||||||
|
let parentHeight = this.$remoteConnectionMessage.parent().height();
|
||||||
|
this.$remoteConnectionMessage.css(
|
||||||
|
'top', (parentHeight/2) - (height/2));
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = this.$remoteConnectionMessage.width();
|
||||||
|
let parentWidth = this.$remoteConnectionMessage.parent().width();
|
||||||
|
this.$remoteConnectionMessage.css(
|
||||||
|
'left', ((parentWidth/2) - (width/2)));
|
||||||
|
}
|
||||||
|
|
||||||
resize (containerWidth, containerHeight, animate = false) {
|
resize (containerWidth, containerHeight, animate = false) {
|
||||||
let [width, height]
|
let [width, height]
|
||||||
= this.getVideoSize(containerWidth, containerHeight);
|
= this.getVideoSize(containerWidth, containerHeight);
|
||||||
|
@ -278,6 +313,8 @@ export class VideoContainer extends LargeContainer {
|
||||||
|
|
||||||
this.$avatar.css('top', top);
|
this.$avatar.css('top', top);
|
||||||
|
|
||||||
|
this.positionRemoteConnectionMessage();
|
||||||
|
|
||||||
this.$wrapper.animate({
|
this.$wrapper.animate({
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
@ -362,10 +399,23 @@ export class VideoContainer extends LargeContainer {
|
||||||
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
|
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
|
||||||
|
|
||||||
this.$avatar.css("visibility", show ? "visible" : "hidden");
|
this.$avatar.css("visibility", show ? "visible" : "hidden");
|
||||||
|
this.avatarDisplayed = show;
|
||||||
|
|
||||||
this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED, show);
|
this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED, show);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the remote user who is currently displayed by this video
|
||||||
|
* container is having connectivity issues.
|
||||||
|
*
|
||||||
|
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
|
||||||
|
* the indication.
|
||||||
|
*/
|
||||||
|
showRemoteConnectionProblemIndicator (show) {
|
||||||
|
this.$video.toggleClass("remoteVideoProblemFilter", show);
|
||||||
|
this.$avatar.toggleClass("remoteVideoProblemFilter", show);
|
||||||
|
}
|
||||||
|
|
||||||
// We are doing fadeOut/fadeIn animations on parent div which wraps
|
// We are doing fadeOut/fadeIn animations on parent div which wraps
|
||||||
// largeVideo, because when Temasys plugin is in use it replaces
|
// largeVideo, because when Temasys plugin is in use it replaces
|
||||||
// <video> elements with plugin <object> tag. In Safari jQuery is
|
// <video> elements with plugin <object> tag. In Safari jQuery is
|
||||||
|
|
|
@ -650,6 +650,12 @@ var VideoLayout = {
|
||||||
* the user is having connectivity issues.
|
* the user is having connectivity issues.
|
||||||
*/
|
*/
|
||||||
onParticipantConnectionStatusChanged (id, isActive) {
|
onParticipantConnectionStatusChanged (id, isActive) {
|
||||||
|
// Show/hide warning on the large video
|
||||||
|
if (this.isCurrentlyOnLarge(id)) {
|
||||||
|
if (largeVideo) {
|
||||||
|
largeVideo.updateParticipantConnStatusIndication(id, isActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Show/hide warning on the thumbnail
|
// Show/hide warning on the thumbnail
|
||||||
let remoteVideo = remoteVideos[id];
|
let remoteVideo = remoteVideos[id];
|
||||||
if (remoteVideo) {
|
if (remoteVideo) {
|
||||||
|
|
Loading…
Reference in New Issue