diff --git a/conference.js b/conference.js
index ea35690a2..c70914ebb 100644
--- a/conference.js
+++ b/conference.js
@@ -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
diff --git a/css/_videolayout_default.scss b/css/_videolayout_default.scss
index f3bdb45ec..26012f17a 100644
--- a/css/_videolayout_default.scss
+++ b/css/_videolayout_default.scss
@@ -97,6 +97,11 @@
color: #FFFFFF;/*#15A1ED*/
overflow: hidden;
}
+
+ &_ninja
+ {
+ font-size: 1.5em;
+ }
}
.icon-connection,
diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js
index 470d0fc3b..0d95ff58e 100644
--- a/modules/UI/videolayout/ConnectionIndicator.js
+++ b/modules/UI/videolayout/ConnectionIndicator.js
@@ -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();
+ }
};
/**
diff --git a/modules/UI/videolayout/LargeVideoManager.js b/modules/UI/videolayout/LargeVideoManager.js
index 4130cc5fd..a53af211e 100644
--- a/modules/UI/videolayout/LargeVideoManager.js
+++ b/modules/UI/videolayout/LargeVideoManager.js
@@ -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 LargeContainers 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) true to show the avatar
+ * @param {null|boolean} [show=null] true to show the avatar
* message or false 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
diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js
index 36da7bc98..a8beb3835 100644
--- a/modules/UI/videolayout/RemoteVideo.js
+++ b/modules/UI/videolayout/RemoteVideo.js
@@ -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 true if the remote participant is
- * currently having connectivity issues or false 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);
};
/**
diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js
index 81d313cbb..54a4da53e 100644
--- a/modules/UI/videolayout/SmallVideo.js
+++ b/modules/UI/videolayout/SmallVideo.js
@@ -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 false 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);
};
/**
diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js
index e6038e186..caa00615d 100644
--- a/modules/UI/videolayout/VideoLayout.js
+++ b/modules/UI/videolayout/VideoLayout.js
@@ -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);
},
/**