feat(VideoLayout): add ninja icon

Add ninja icon which wil be displayed when user's connection status is
inactive.

Apply grey filter only for interrupted state.

Do not use isLastN directly, but check ParticipantConnectionStatus.
This commit is contained in:
paweldomas 2017-05-25 08:48:06 -05:00 committed by yanas
parent 5c5864e94a
commit 12d7e61362
7 changed files with 148 additions and 108 deletions

View File

@ -2125,18 +2125,6 @@ export default {
eventEmitter.removeListener(eventName, listener);
},
/**
* Checks if the participant given by participantId is currently in the
* last N set if there's one supported.
*
* @param participantId the identifier of the participant
* @returns {boolean} {true} if the participant given by the participantId
* is currently in the last N set or if there's no last N set at this point
* and {false} otherwise
*/
isInLastN(participantId) {
return room.isInLastN(participantId);
},
/**
* Changes the display name for the local user
* @param nickname {string} the new display name

View File

@ -97,6 +97,11 @@
color: #FFFFFF;/*#15A1ED*/
overflow: hidden;
}
&_ninja
{
font-size: 1.5em;
}
}
.icon-connection,

View File

@ -1,9 +1,12 @@
/* global $, APP, interfaceConfig */
/* global $, APP, interfaceConfig, JitsiMeetJS */
/* jshint -W101 */
import JitsiPopover from "../util/JitsiPopover";
import UIUtil from "../util/UIUtil";
const ParticipantConnectionStatus
= JitsiMeetJS.constants.participantConnectionStatus;
/**
* Maps a connection quality value (in percent) to the width of the "full" icon.
*/
@ -334,8 +337,11 @@ ConnectionIndicator.prototype.create = function () {
createIcon(["connection_full"], "icon-connection"));
this.interruptedIndicator = connectionIconContainer.appendChild(
createIcon(["connection_lost"],"icon-connection-lost"));
this.ninjaIndicator = connectionIconContainer.appendChild(
createIcon(["connection_ninja"],"icon-ninja"));
$(this.interruptedIndicator).hide();
$(this.ninjaIndicator).hide();
this.connectionIndicatorContainer.appendChild(connectionIconContainer);
};
@ -351,23 +357,30 @@ ConnectionIndicator.prototype.remove = function() {
};
/**
* Updates the UI which displays warning about user's connectivity problems.
* Updates the UI which displays or not a warning about user's connectivity
* problems.
*
* @param {boolean} isActive true if the connection is working fine or false if
* the user is having connectivity issues.
* @param {ParticipantConnectionStatus} connectionStatus
*/
ConnectionIndicator.prototype.updateConnectionStatusIndicator
= function (isActive) {
this.isConnectionActive = isActive;
if (this.isConnectionActive) {
$(this.interruptedIndicator).hide();
$(this.emptyIcon).show();
$(this.fullIcon).show();
} else {
$(this.interruptedIndicator).show();
$(this.emptyIcon).hide();
$(this.fullIcon).hide();
}
= function (connectionStatus) {
this.connectionStatus = connectionStatus;
if (connectionStatus === ParticipantConnectionStatus.INTERRUPTED) {
$(this.interruptedIndicator).show();
$(this.emptyIcon).hide();
$(this.fullIcon).hide();
$(this.ninjaIndicator).hide();
} else if (connectionStatus === ParticipantConnectionStatus.INACTIVE) {
$(this.interruptedIndicator).hide();
$(this.emptyIcon).hide();
$(this.fullIcon).hide();
$(this.ninjaIndicator).show();
} else {
$(this.interruptedIndicator).hide();
$(this.emptyIcon).show();
$(this.fullIcon).show();
$(this.ninjaIndicator).hide();
}
};
/**

View File

@ -26,6 +26,18 @@ const VIDEO_RESOLUTION_POLL_INTERVAL = 2000;
* Manager for all Large containers.
*/
export default class LargeVideoManager {
/**
* Checks whether given container is a {@link VIDEO_CONTAINER_TYPE}.
* FIXME currently this is a workaround for the problem where video type is
* mixed up with container type.
* @param {string} containerType
* @return {boolean}
*/
static isVideoContainer(containerType) {
return containerType === VIDEO_CONTAINER_TYPE
|| containerType === DESKTOP_CONTAINER_TYPE;
}
constructor (emitter) {
/**
* The map of <tt>LargeContainer</tt>s where the key is the video
@ -116,7 +128,8 @@ export default class LargeVideoManager {
this.enableLocalConnectionProblemFilter(true);
this._setLocalConnectionMessage("connection.RECONNECTING");
// Show the message only if the video is currently being displayed
this.showLocalConnectionMessage(this.state === VIDEO_CONTAINER_TYPE);
this.showLocalConnectionMessage(
LargeVideoManager.isVideoContainer(this.state));
}
/**
@ -146,7 +159,12 @@ export default class LargeVideoManager {
preUpdate.then(() => {
const { id, stream, videoType, resolve } = this.newStreamData;
const isVideoFromCamera = videoType === VIDEO_CONTAINER_TYPE;
// FIXME this does not really make sense, because the videoType
// (camera or desktop) is a completely different thing than
// the video container type (Etherpad, SharedVideo, VideoContainer).
const isVideoContainer
= LargeVideoManager.isVideoContainer(videoType);
this.newStreamData = null;
@ -158,34 +176,26 @@ export default class LargeVideoManager {
// change the avatar url on large
this.updateAvatar(Avatar.getAvatarUrl(id));
// FIXME that does not really make sense, because the videoType
// (camera or desktop) is a completely different thing than
// the video container type (Etherpad, SharedVideo, VideoContainer).
// ----------------------------------------------------------------
// If the container is VIDEO_CONTAINER_TYPE, we need to check
// its stream whether exist and is muted to set isVideoMuted
// in rest of the cases it is false
let showAvatar = isVideoFromCamera && (!stream || stream.isMuted());
// If the user's connection is disrupted then the avatar will be
// 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
// there was a user switch (image is lost on stream detach) or if
// the video was not rendered, before the connection has failed.
const isConnectionActive = this._isConnectionActive(id);
const wasUsersImageCached
= !isUserSwitch && container.wasVideoRendered;
const isVideoMuted = !stream || stream.isMuted();
if (isVideoFromCamera
&& !isConnectionActive
&& (isUserSwitch || !container.wasVideoRendered)) {
showAvatar = true;
}
const connectionStatus
= APP.conference.getParticipantConnectionStatus(id);
const isVideoRenderable
= !isVideoMuted
&& (APP.conference.isLocalId(id)
|| connectionStatus
=== ParticipantConnectionStatus.ACTIVE
|| wasUsersImageCached);
// If audio only mode is enabled, always show the avatar for
// videos from another participant.
if (APP.conference.isAudioOnly()
&& (isVideoFromCamera
|| videoType === DESKTOP_CONTAINER_TYPE)) {
showAvatar = true;
}
let showAvatar
= isVideoContainer
&& (APP.conference.isAudioOnly() || !isVideoRenderable);
let promise;
@ -208,28 +218,31 @@ export default class LargeVideoManager {
this.updateLargeVideoAudioLevel(0);
}
const isConnectionInterrupted
= APP.conference.getParticipantConnectionStatus(id)
=== ParticipantConnectionStatus.INTERRUPTED;
let messageKey = null;
if (isConnectionInterrupted) {
messageKey = "connection.USER_CONNECTION_INTERRUPTED";
} else if (connectionStatus
=== ParticipantConnectionStatus.INACTIVE) {
messageKey = "connection.LOW_BANDWIDTH";
}
// Make sure no notification about remote failure is shown as
// its UI conflicts with the one for local connection interrupted.
// For the purposes of UI indicators, audio only is considered as
// an "active" connection.
const isConnected
const overrideAndHide
= APP.conference.isAudioOnly()
|| APP.conference.isConnectionInterrupted()
|| isConnectionActive;
// when isHavingConnectivityIssues, state can be inactive,
// interrupted or restoring. We show different message for
// interrupted and the rest.
const isConnectionInterrupted =
APP.conference.getParticipantConnectionStatus(id)
=== ParticipantConnectionStatus.INTERRUPTED;
|| APP.conference.isConnectionInterrupted();
this.updateParticipantConnStatusIndication(
id,
isConnected,
(isConnectionInterrupted)
? "connection.USER_CONNECTION_INTERRUPTED"
: "connection.LOW_BANDWIDTH");
!overrideAndHide && isConnectionInterrupted,
!overrideAndHide && messageKey !== null,
messageKey);
// resolve updateLargeVideo promise after everything is done
promise.then(resolve);
@ -265,18 +278,20 @@ export default class LargeVideoManager {
* 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.
* @param {boolean} showProblemsIndication
* @param {boolean} showMessage
* @param {string} messageKey the i18n key of the message
*
* @private
*/
updateParticipantConnStatusIndication (id, isConnected, messageKey) {
updateParticipantConnStatusIndication (
id, showProblemsIndication, showMessage, messageKey) {
// Apply grey filter on the large video
this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
this.videoContainer.showRemoteConnectionProblemIndicator(
showProblemsIndication);
if (isConnected) {
if (!showMessage) {
// Hide the message
this.showRemoteConnectionMessage(false);
} else {
@ -289,7 +304,7 @@ export default class LargeVideoManager {
// Show it now only if the VideoContainer is on top
this.showRemoteConnectionMessage(
this.state === VIDEO_CONTAINER_TYPE);
LargeVideoManager.isVideoContainer(this.state));
}
}
@ -412,15 +427,20 @@ export default class LargeVideoManager {
* 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
* @param {null|boolean} [show=null] <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.
* connection is either interrupted or inactive.
*/
showRemoteConnectionMessage (show) {
if (typeof show !== 'boolean') {
show = !this._isConnectionActive(this.id);
const connStatus
= APP.conference.getParticipantConnectionStatus(this.id);
show = !APP.conference.isLocalId(this.id)
&& (connStatus === ParticipantConnectionStatus.INTERRUPTED
|| connStatus === ParticipantConnectionStatus.INACTIVE);
}
if (show) {
@ -526,7 +546,7 @@ export default class LargeVideoManager {
// FIXME when video is being replaced with other content we need to hide
// companion icons/messages. It would be best if the container would
// be taking care of it by itself, but that is a bigger refactoring
if (this.state === VIDEO_CONTAINER_TYPE) {
if (LargeVideoManager.isVideoContainer(this.state)) {
this.showWatermark(false);
this.showLocalConnectionMessage(false);
this.showRemoteConnectionMessage(false);
@ -537,7 +557,7 @@ export default class LargeVideoManager {
let container = this.getContainer(type);
return container.show().then(() => {
if (type === VIDEO_CONTAINER_TYPE) {
if (LargeVideoManager.isVideoContainer(type)) {
// FIXME when video appears on top of other content we need to
// show companion icons/messages. It would be best if
// the container would be taking care of it by itself, but that

View File

@ -448,29 +448,27 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
/**
* @inheritDoc
* @override
*/
RemoteVideo.prototype.setMutedView = function(isMuted) {
SmallVideo.prototype.setMutedView.call(this, isMuted);
RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
SmallVideo.prototype.setVideoMutedView.call(this, isMuted);
// Update 'mutedWhileDisconnected' flag
this._figureOutMutedWhileDisconnected(this.isConnectionInterrupted());
this._figureOutMutedWhileDisconnected();
};
/**
* Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
* account remote participant's network connectivity and video muted status.
*
* @param {boolean} isDisconnected <tt>true</tt> if the remote participant is
* currently having connectivity issues or <tt>false</tt> otherwise.
*
* @private
*/
RemoteVideo.prototype._figureOutMutedWhileDisconnected
= function(isDisconnected) {
if (isDisconnected && this.isVideoMuted) {
this.mutedWhileDisconnected = true;
} else if (!isDisconnected && !this.isVideoMuted) {
this.mutedWhileDisconnected = false;
}
RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
const isActive = this.isConnectionActive();
if (!isActive && this.isVideoMuted) {
this.mutedWhileDisconnected = true;
} else if (isActive && !this.isVideoMuted) {
this.mutedWhileDisconnected = false;
}
};
/**
@ -572,26 +570,25 @@ RemoteVideo.prototype.updateView = function () {
* Updates the UI to reflect user's connectivity status.
*/
RemoteVideo.prototype.updateConnectionStatusIndicator = function () {
const isActive = this.isConnectionActive();
const connectionStatus = this.user.getConnectionStatus();
if (isActive === null) {
// Cancel processing at this point - no update
return;
logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`);
// FIXME rename 'mutedWhileDisconnected' to 'mutedWhileNotRendering'
// Update 'mutedWhileDisconnected' flag
this._figureOutMutedWhileDisconnected();
if(this.connectionIndicator) {
this.connectionIndicator.updateConnectionStatusIndicator(
connectionStatus);
}
logger.debug(this.id + " thumbnail is connection active ? " + isActive);
// Update 'mutedWhileDisconnected' flag
this._figureOutMutedWhileDisconnected(!isActive);
if(this.connectionIndicator)
this.connectionIndicator.updateConnectionStatusIndicator(isActive);
const isInterrupted
= connectionStatus === ParticipantConnectionStatus.INTERRUPTED;
// Toggle thumbnail video problem filter
this.selectVideoElement().toggleClass(
"videoThumbnailProblemFilter", !isActive);
"videoThumbnailProblemFilter", isInterrupted);
this.$avatar().toggleClass(
"videoThumbnailProblemFilter", !isActive);
"videoThumbnailProblemFilter", isInterrupted);
};
/**

View File

@ -6,6 +6,8 @@ import UIUtil from "../util/UIUtil";
import UIEvents from "../../../service/UI/UIEvents";
import AudioLevels from "../audio_levels/AudioLevels";
const ParticipantConnectionStatus
= JitsiMeetJS.constants.participantConnectionStatus;
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
/**
@ -444,9 +446,13 @@ SmallVideo.prototype.isCurrentlyOnLargeVideo = function () {
* or <tt>false</tt> otherwise.
*/
SmallVideo.prototype.isVideoPlayable = function() {
const connectionState
= APP.conference.getParticipantConnectionStatus(this.id);
return this.videoStream // Is there anything to display ?
&& !this.isVideoMuted && !this.videoStream.isMuted() // Muted ?
&& (this.isLocal || APP.conference.isInLastN(this.id));
&& (this.isLocal
|| connectionState === ParticipantConnectionStatus.ACTIVE);
};
/**

View File

@ -1,4 +1,4 @@
/* global APP, $, interfaceConfig */
/* global APP, $, interfaceConfig, JitsiMeetJS */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import Filmstrip from "./Filmstrip";
@ -10,6 +10,9 @@ import LargeVideoManager from "./LargeVideoManager";
import {VIDEO_CONTAINER_TYPE} from "./VideoContainer";
import LocalVideo from "./LocalVideo";
const ParticipantConnectionStatus
= JitsiMeetJS.constants.participantConnectionStatus;
var remoteVideos = {};
var localVideoThumbnail = null;
@ -559,8 +562,16 @@ var VideoLayout = {
* is fine.
*/
showLocalConnectionInterrupted (isInterrupted) {
localVideoThumbnail.connectionIndicator
.updateConnectionStatusIndicator(!isInterrupted);
// Currently local video thumbnail displays only "active" or
// "interrupted" despite the fact that ConnectionIndicator supports more
// states.
const status
= isInterrupted
? ParticipantConnectionStatus.INTERRUPTED
: ParticipantConnectionStatus.ACTIVE;
localVideoThumbnail
.connectionIndicator.updateConnectionStatusIndicator(status);
},
/**