Merge pull request #938 from jitsi/participant_conn_status
Adds participant connection status notifications
This commit is contained in:
commit
2a8700bca3
|
@ -691,6 +691,51 @@ export default {
|
|||
isConnectionInterrupted () {
|
||||
return connectionIsInterrupted;
|
||||
},
|
||||
/**
|
||||
* Finds JitsiParticipant for given id.
|
||||
*
|
||||
* @param {string} id participant's identifier(MUC nickname).
|
||||
*
|
||||
* @returns {JitsiParticipant|null} participant instance for given id or
|
||||
* null if not found.
|
||||
*/
|
||||
getParticipantById (id) {
|
||||
return room ? room.getParticipantById(id) : null;
|
||||
},
|
||||
/**
|
||||
* Checks whether the user identified by given id is currently connected.
|
||||
*
|
||||
* @param {string} id participant's identifier(MUC nickname)
|
||||
*
|
||||
* @returns {boolean|null} true if participant's connection is ok or false
|
||||
* if the user is having connectivity issues.
|
||||
*/
|
||||
isParticipantConnectionActive (id) {
|
||||
let participant = this.getParticipantById(id);
|
||||
return participant ? participant.isConnectionActive() : null;
|
||||
},
|
||||
/**
|
||||
* Gets the display name foe the <tt>JitsiParticipant</tt> identified by
|
||||
* the given <tt>id</tt>.
|
||||
*
|
||||
* @param id {string} the participant's id(MUC nickname/JVB endpoint id)
|
||||
*
|
||||
* @return {string} the participant's display name or the default string if
|
||||
* absent.
|
||||
*/
|
||||
getParticipantDisplayName (id) {
|
||||
let displayName = getDisplayName(id);
|
||||
if (displayName) {
|
||||
return displayName;
|
||||
} else {
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
return APP.translation.generateTranslationHTML(
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
||||
} else {
|
||||
return interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
}
|
||||
}
|
||||
},
|
||||
getMyUserId () {
|
||||
return this._room
|
||||
&& this._room.myUserId();
|
||||
|
@ -1085,7 +1130,7 @@ export default {
|
|||
|
||||
console.log('USER %s connnected', id, user);
|
||||
APP.API.notifyUserJoined(id);
|
||||
APP.UI.addUser(id, user.getDisplayName());
|
||||
APP.UI.addUser(user);
|
||||
|
||||
// check the roles for the new user and reflect them
|
||||
APP.UI.updateUserRole(user);
|
||||
|
@ -1174,6 +1219,10 @@ export default {
|
|||
ConferenceEvents.LAST_N_ENDPOINTS_CHANGED, (ids, enteringIds) => {
|
||||
APP.UI.handleLastNEndpoints(ids, enteringIds);
|
||||
});
|
||||
room.on(
|
||||
ConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED, (id, isActive) => {
|
||||
APP.UI.participantConnectionStatusChanged(id, isActive);
|
||||
});
|
||||
room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
|
||||
if (this.isLocalId(id)) {
|
||||
this.isDominantSpeaker = true;
|
||||
|
@ -1205,10 +1254,12 @@ export default {
|
|||
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
connectionIsInterrupted = true;
|
||||
ConnectionQuality.updateLocalConnectionQuality(0);
|
||||
APP.UI.showLocalConnectionInterrupted(true);
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
|
||||
connectionIsInterrupted = false;
|
||||
APP.UI.showLocalConnectionInterrupted(false);
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, (id, displayName) => {
|
||||
|
|
|
@ -233,6 +233,12 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.connection.connection_lost
|
||||
{
|
||||
color: #8B8B8B;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.connection.connection_full
|
||||
{
|
||||
color: #FFFFFF;/*#15A1ED*/
|
||||
|
@ -456,12 +462,44 @@
|
|||
filter: grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
.remoteVideoProblemFilter {
|
||||
-webkit-filter: grayscale(100%);
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.videoProblemFilter {
|
||||
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
#videoConnectionMessage {
|
||||
.videoThumbnailProblemFilter {
|
||||
-webkit-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;
|
||||
}
|
||||
|
||||
#localConnectionMessage {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
|
|
@ -228,10 +228,11 @@
|
|||
<img id="dominantSpeakerAvatar" src=""/>
|
||||
<canvas id="dominantSpeakerAudioLevel"></canvas>
|
||||
</div>
|
||||
<span id="remoteConnectionMessage"></span>
|
||||
<div id="largeVideoWrapper">
|
||||
<video id="largeVideo" muted="true" autoplay></video>
|
||||
</div>
|
||||
<span id="videoConnectionMessage"></span>
|
||||
<span id="localConnectionMessage"></span>
|
||||
<span id="videoResolutionLabel">HD</span>
|
||||
<span id="recordingLabel" class="centeredVideoLabel">
|
||||
<span id="recordingLabelText"></span>
|
||||
|
|
|
@ -324,7 +324,8 @@
|
|||
"ATTACHED": "Attached",
|
||||
"FETCH_SESSION_ID": "Obtaining session-id...",
|
||||
"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":
|
||||
{
|
||||
|
|
|
@ -261,6 +261,17 @@ UI.changeDisplayName = function (id, displayName) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows/hides the indication about local connection being interrupted.
|
||||
*
|
||||
* @param {boolean} isInterrupted <tt>true</tt> if local connection is
|
||||
* currently in the interrupted state or <tt>false</tt> if the connection
|
||||
* is fine.
|
||||
*/
|
||||
UI.showLocalConnectionInterrupted = function (isInterrupted) {
|
||||
VideoLayout.showLocalConnectionInterrupted(isInterrupted);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the "raised hand" status for a participant.
|
||||
*/
|
||||
|
@ -602,10 +613,11 @@ UI.getSharedDocumentManager = function () {
|
|||
|
||||
/**
|
||||
* Show user on UI.
|
||||
* @param {string} id user id
|
||||
* @param {string} displayName user nickname
|
||||
* @param {JitsiParticipant} user
|
||||
*/
|
||||
UI.addUser = function (id, displayName) {
|
||||
UI.addUser = function (user) {
|
||||
var id = user.getId();
|
||||
var displayName = user.getDisplayName();
|
||||
UI.hideRingOverLay();
|
||||
ContactList.addContact(id);
|
||||
|
||||
|
@ -618,7 +630,7 @@ UI.addUser = function (id, displayName) {
|
|||
UIUtil.playSoundNotification('userJoined');
|
||||
|
||||
// Add Peer's container
|
||||
VideoLayout.addParticipantContainer(id);
|
||||
VideoLayout.addParticipantContainer(user);
|
||||
|
||||
// Configure avatar
|
||||
UI.setUserEmail(id);
|
||||
|
@ -983,6 +995,17 @@ UI.handleLastNEndpoints = function (ids, enteringIds) {
|
|||
VideoLayout.onLastNEndpointsChanged(ids, enteringIds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Will handle notification about participant's connectivity status change.
|
||||
*
|
||||
* @param {string} id the id of remote participant(MUC jid)
|
||||
* @param {boolean} isActive true if the connection is ok or false if the user
|
||||
* is having connectivity issues.
|
||||
*/
|
||||
UI.participantConnectionStatusChanged = function (id, isActive) {
|
||||
VideoLayout.onParticipantConnectionStatusChanged(id, isActive);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update audio level visualization for specified user.
|
||||
* @param {string} id user id
|
||||
|
|
|
@ -243,7 +243,7 @@ export default class SharedVideoManager {
|
|||
|
||||
let thumb = new SharedVideoThumb(self.url);
|
||||
thumb.setDisplayName(player.getVideoData().title);
|
||||
VideoLayout.addParticipantContainer(self.url, thumb);
|
||||
VideoLayout.addRemoteVideoContainer(self.url, thumb);
|
||||
|
||||
let iframe = player.getIframe();
|
||||
self.sharedVideo = new SharedVideoContainer(
|
||||
|
|
|
@ -245,13 +245,13 @@ ConnectionIndicator.prototype.showMore = function () {
|
|||
};
|
||||
|
||||
|
||||
function createIcon(classes) {
|
||||
function createIcon(classes, iconClass) {
|
||||
var icon = document.createElement("span");
|
||||
for(var i in classes) {
|
||||
icon.classList.add(classes[i]);
|
||||
}
|
||||
icon.appendChild(
|
||||
document.createElement("i")).classList.add("icon-connection");
|
||||
document.createElement("i")).classList.add(iconClass);
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
@ -282,9 +282,12 @@ ConnectionIndicator.prototype.create = function () {
|
|||
}.bind(this);
|
||||
|
||||
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_empty"]));
|
||||
createIcon(["connection", "connection_empty"], "icon-connection"));
|
||||
this.fullIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_full"]));
|
||||
createIcon(["connection", "connection_full"], "icon-connection"));
|
||||
this.interruptedIndicator = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_lost"],"icon-connection-lost"));
|
||||
$(this.interruptedIndicator).hide();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -298,6 +301,27 @@ ConnectionIndicator.prototype.remove = function() {
|
|||
this.popover.forceHide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI which displays 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.
|
||||
*/
|
||||
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();
|
||||
this.updateConnectionQuality(0 /* zero bars */);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the data of the indicator
|
||||
* @param percent the percent of connection quality
|
||||
|
@ -314,12 +338,14 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
|||
this.connectionIndicatorContainer.style.display = "block";
|
||||
}
|
||||
}
|
||||
this.bandwidth = object.bandwidth;
|
||||
this.bitrate = object.bitrate;
|
||||
this.packetLoss = object.packetLoss;
|
||||
this.transport = object.transport;
|
||||
if (object.resolution) {
|
||||
this.resolution = object.resolution;
|
||||
if (object) {
|
||||
this.bandwidth = object.bandwidth;
|
||||
this.bitrate = object.bitrate;
|
||||
this.packetLoss = object.packetLoss;
|
||||
this.transport = object.transport;
|
||||
if (object.resolution) {
|
||||
this.resolution = object.resolution;
|
||||
}
|
||||
}
|
||||
for (var quality in ConnectionIndicator.connectionQualityValues) {
|
||||
if (percent >= quality) {
|
||||
|
@ -327,7 +353,7 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
|||
ConnectionIndicator.connectionQualityValues[quality];
|
||||
}
|
||||
}
|
||||
if (object.isResolutionHD) {
|
||||
if (object && typeof object.isResolutionHD === 'boolean') {
|
||||
this.isResolutionHD = object.isResolutionHD;
|
||||
}
|
||||
this.updateResolutionIndicator();
|
||||
|
|
|
@ -5,12 +5,18 @@ import Avatar from "../avatar/Avatar";
|
|||
import {createDeferred} from '../../util/helpers';
|
||||
import UIUtil from "../util/UIUtil";
|
||||
import {VideoContainer, VIDEO_CONTAINER_TYPE} from "./VideoContainer";
|
||||
import LargeContainer from "./LargeContainer";
|
||||
|
||||
/**
|
||||
* Manager for all Large containers.
|
||||
*/
|
||||
export default class LargeVideoManager {
|
||||
constructor (emitter) {
|
||||
/**
|
||||
* The map of <tt>LargeContainer</tt>s where the key is the video
|
||||
* container type.
|
||||
* @type {Object.<string, LargeContainer>}
|
||||
*/
|
||||
this.containers = {};
|
||||
|
||||
this.state = VIDEO_CONTAINER_TYPE;
|
||||
|
@ -85,21 +91,18 @@ export default class LargeVideoManager {
|
|||
* Called when the media connection has been interrupted.
|
||||
*/
|
||||
onVideoInterrupted () {
|
||||
this.enableVideoProblemFilter(true);
|
||||
let reconnectingKey = "connection.RECONNECTING";
|
||||
$('#videoConnectionMessage')
|
||||
.attr("data-i18n", reconnectingKey)
|
||||
.text(APP.translation.translateString(reconnectingKey));
|
||||
this.enableLocalConnectionProblemFilter(true);
|
||||
this._setLocalConnectionMessage("connection.RECONNECTING")
|
||||
// Show the message only if the video is currently being displayed
|
||||
this.showVideoConnectionMessage(this.state === VIDEO_CONTAINER_TYPE);
|
||||
this.showLocalConnectionMessage(this.state === VIDEO_CONTAINER_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the media connection has been restored.
|
||||
*/
|
||||
onVideoRestored () {
|
||||
this.enableVideoProblemFilter(false);
|
||||
this.showVideoConnectionMessage(false);
|
||||
this.enableLocalConnectionProblemFilter(false);
|
||||
this.showLocalConnectionMessage(false);
|
||||
}
|
||||
|
||||
get id () {
|
||||
|
@ -118,7 +121,8 @@ export default class LargeVideoManager {
|
|||
|
||||
// Include hide()/fadeOut only if we're switching between users
|
||||
let preUpdate;
|
||||
if (this.newStreamData.id != this.id) {
|
||||
let isUserSwitch = this.newStreamData.id != this.id;
|
||||
if (isUserSwitch) {
|
||||
preUpdate = container.hide();
|
||||
} else {
|
||||
preUpdate = Promise.resolve();
|
||||
|
@ -136,27 +140,53 @@ 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 we the continer 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 isVideoMuted = false;
|
||||
let showAvatar = false;
|
||||
if (videoType == VIDEO_CONTAINER_TYPE)
|
||||
isVideoMuted = stream ? stream.isMuted() : true;
|
||||
showAvatar = stream ? stream.isMuted() : true;
|
||||
|
||||
// show the avatar on large if needed
|
||||
container.showAvatar(isVideoMuted);
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// do not show stream if video is muted
|
||||
// but we still should show watermark
|
||||
if (isVideoMuted) {
|
||||
if (showAvatar) {
|
||||
this.showWatermark(true);
|
||||
promise = Promise.resolve();
|
||||
// If the intention of this switch is to show the avatar
|
||||
// we need to make sure that the video is hidden
|
||||
promise = container.hide();
|
||||
} else {
|
||||
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
|
||||
promise.then(resolve);
|
||||
|
||||
|
@ -169,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.
|
||||
* Switches to large video even if previously other container was visible.
|
||||
|
@ -229,12 +291,13 @@ export default class LargeVideoManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Enables/disables the filter indicating a video problem to the user.
|
||||
* Enables/disables the filter indicating a video problem to the user caused
|
||||
* by the problems with local media connection.
|
||||
*
|
||||
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
|
||||
*/
|
||||
enableVideoProblemFilter (enable) {
|
||||
this.videoContainer.enableVideoProblemFilter(enable);
|
||||
enableLocalConnectionProblemFilter (enable) {
|
||||
this.videoContainer.enableLocalConnectionProblemFilter(enable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -253,23 +316,87 @@ export default class LargeVideoManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the "video connection message".
|
||||
* Shows/hides the message indicating problems with local media connection.
|
||||
* @param {boolean|null} show(optional) tells whether the message is to be
|
||||
* displayed or not. If missing the condition will be based on the value
|
||||
* obtained from {@link APP.conference.isConnectionInterrupted}.
|
||||
*/
|
||||
showVideoConnectionMessage (show) {
|
||||
showLocalConnectionMessage (show) {
|
||||
if (typeof show !== 'boolean') {
|
||||
show = APP.conference.isConnectionInterrupted();
|
||||
}
|
||||
|
||||
if (show) {
|
||||
$('#videoConnectionMessage').css({display: "block"});
|
||||
$('#localConnectionMessage').css({display: "block"});
|
||||
// Avatar message conflicts with 'videoConnectionMessage',
|
||||
// so it must be hidden
|
||||
this.showRemoteConnectionMessage(false);
|
||||
} else {
|
||||
$('#videoConnectionMessage').css({display: "none"});
|
||||
$('#localConnectionMessage').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.showLocalConnectionMessage(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, when
|
||||
* local media connection is interrupted.
|
||||
*
|
||||
* @param {string} msgKey the translation key which will be used to get
|
||||
* the message text to be displayed on the large video.
|
||||
* @param {object} msgOptions translation options object
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setLocalConnectionMessage (msgKey, msgOptions) {
|
||||
$('#localConnectionMessage')
|
||||
.attr("data-i18n", msgKey)
|
||||
.text(APP.translation.translateString(msgKey, msgOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add container of specified type.
|
||||
* @param {string} type container type
|
||||
|
@ -328,7 +455,8 @@ export default class LargeVideoManager {
|
|||
// be taking care of it by itself, but that is a bigger refactoring
|
||||
if (this.state === VIDEO_CONTAINER_TYPE) {
|
||||
this.showWatermark(false);
|
||||
this.showVideoConnectionMessage(false);
|
||||
this.showLocalConnectionMessage(false);
|
||||
this.showRemoteConnectionMessage(false);
|
||||
}
|
||||
oldContainer.hide();
|
||||
|
||||
|
@ -342,7 +470,11 @@ export default class LargeVideoManager {
|
|||
// the container would be taking care of it by itself, but that
|
||||
// is a bigger refactoring
|
||||
this.showWatermark(true);
|
||||
this.showVideoConnectionMessage(/* fetch the current state */);
|
||||
// "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.showLocalConnectionMessage(/* fetch the current state */);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -201,7 +201,7 @@ LocalVideo.prototype.changeVideo = function (stream) {
|
|||
localVideoContainer.removeChild(localVideo);
|
||||
// when removing only the video element and we are on stage
|
||||
// update the stage
|
||||
if(this.VideoLayout.isCurrentlyOnLarge(this.id))
|
||||
if(this.isCurrentlyOnLargeVideo())
|
||||
this.VideoLayout.updateLargeVideo(this.id);
|
||||
stream.off(TrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
||||
};
|
||||
|
|
|
@ -8,17 +8,45 @@ import UIUtils from "../util/UIUtil";
|
|||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import JitsiPopover from "../util/JitsiPopover";
|
||||
|
||||
function RemoteVideo(id, VideoLayout, emitter) {
|
||||
this.id = id;
|
||||
/**
|
||||
* Creates new instance of the <tt>RemoteVideo</tt>.
|
||||
* @param user {JitsiParticipant} the user for whom remote video instance will
|
||||
* be created.
|
||||
* @param {VideoLayout} VideoLayout the video layout instance.
|
||||
* @param {EventEmitter} emitter the event emitter which will be used by
|
||||
* the new instance to emit events.
|
||||
* @constructor
|
||||
*/
|
||||
function RemoteVideo(user, VideoLayout, emitter) {
|
||||
this.user = user;
|
||||
this.id = user.getId();
|
||||
this.emitter = emitter;
|
||||
this.videoSpanId = `participant_${id}`;
|
||||
this.videoSpanId = `participant_${this.id}`;
|
||||
SmallVideo.call(this, VideoLayout);
|
||||
this.hasRemoteVideoMenu = false;
|
||||
this.addRemoteVideoContainer();
|
||||
this.connectionIndicator = new ConnectionIndicator(this, id);
|
||||
this.connectionIndicator = new ConnectionIndicator(this, this.id);
|
||||
this.setDisplayName();
|
||||
this.flipX = false;
|
||||
this.isLocal = false;
|
||||
/**
|
||||
* The flag is set to <tt>true</tt> after the 'onplay' event has been
|
||||
* triggered on the current video element. It goes back to <tt>false</tt>
|
||||
* when the stream is removed. It is used to determine whether the video
|
||||
* playback has ever started.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.wasVideoPlayed = false;
|
||||
/**
|
||||
* The flag is set to <tt>true</tt> if remote participant's video gets muted
|
||||
* during his media connection disruption. This is to prevent black video
|
||||
* being render on the thumbnail, because even though once the video has
|
||||
* been played the image usually remains on the video element it seems that
|
||||
* after longer period of the video element being hidden this image can be
|
||||
* lost.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.mutedWhileDisconnected = false;
|
||||
}
|
||||
|
||||
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
|
@ -162,6 +190,33 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
RemoteVideo.prototype.setMutedView = function(isMuted) {
|
||||
SmallVideo.prototype.setMutedView.call(this, isMuted);
|
||||
// Update 'mutedWhileDisconnected' flag
|
||||
this._figureOutMutedWhileDisconnected(this.isConnectionActive() === false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the remote video menu element for the given <tt>id</tt> in the
|
||||
* given <tt>parentElement</tt>.
|
||||
|
@ -209,13 +264,88 @@ RemoteVideo.prototype.removeRemoteStreamElement = function (stream) {
|
|||
var select = $('#' + elementID);
|
||||
select.remove();
|
||||
|
||||
if (isVideo) {
|
||||
this.wasVideoPlayed = false;
|
||||
}
|
||||
|
||||
console.info((isVideo ? "Video" : "Audio") +
|
||||
" removed " + this.id, select);
|
||||
|
||||
// when removing only the video element and we are on stage
|
||||
// update the stage
|
||||
if (isVideo && this.VideoLayout.isCurrentlyOnLarge(this.id))
|
||||
if (isVideo && this.isCurrentlyOnLargeVideo())
|
||||
this.VideoLayout.updateLargeVideo(this.id);
|
||||
else
|
||||
// Missing video stream will affect display mode
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the remote user associated with this <tt>RemoteVideo</tt>
|
||||
* has connectivity issues.
|
||||
*
|
||||
* @return {boolean} <tt>true</tt> if the user's connection is fine or
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
RemoteVideo.prototype.isConnectionActive = function() {
|
||||
return this.user.isConnectionActive();
|
||||
};
|
||||
|
||||
/**
|
||||
* The remote video is considered "playable" once the stream has started
|
||||
* according to the {@link #hasVideoStarted} result.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @override
|
||||
*/
|
||||
RemoteVideo.prototype.isVideoPlayable = function () {
|
||||
return SmallVideo.prototype.isVideoPlayable.call(this)
|
||||
&& this.hasVideoStarted() && !this.mutedWhileDisconnected;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
RemoteVideo.prototype.updateView = function () {
|
||||
|
||||
this.updateConnectionStatusIndicator(
|
||||
null /* will obtain the status from 'conference' */);
|
||||
|
||||
// This must be called after 'updateConnectionStatusIndicator' because it
|
||||
// affects the display mode by modifying 'mutedWhileDisconnected' flag
|
||||
SmallVideo.prototype.updateView.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI to reflect user's connectivity status.
|
||||
* @param isActive {boolean|null} 'true' if user's connection is active or
|
||||
* 'false' when the use is having some connectivity issues and a warning
|
||||
* should be displayed. When 'null' is passed then the current value will be
|
||||
* obtained from the conference instance.
|
||||
*/
|
||||
RemoteVideo.prototype.updateConnectionStatusIndicator = function (isActive) {
|
||||
// Check for initial value if 'isActive' is not defined
|
||||
if (typeof isActive !== "boolean") {
|
||||
isActive = this.isConnectionActive();
|
||||
if (isActive === null) {
|
||||
// Cancel processing at this point - no update
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(this.id + " thumbnail is connection active ? " + isActive);
|
||||
|
||||
// Update 'mutedWhileDisconnected' flag
|
||||
this._figureOutMutedWhileDisconnected(!isActive);
|
||||
|
||||
if(this.connectionIndicator)
|
||||
this.connectionIndicator.updateConnectionStatusIndicator(isActive);
|
||||
|
||||
// Toggle thumbnail video problem filter
|
||||
this.selectVideoElement().toggleClass(
|
||||
"videoThumbnailProblemFilter", !isActive);
|
||||
this.$avatar().toggleClass(
|
||||
"videoThumbnailProblemFilter", !isActive);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -246,22 +376,23 @@ RemoteVideo.prototype.waitForPlayback = function (streamElement, stream) {
|
|||
// Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
|
||||
// when video playback starts
|
||||
var onPlayingHandler = function () {
|
||||
self.wasVideoPlayed = true;
|
||||
self.VideoLayout.videoactive(streamElement, self.id);
|
||||
streamElement.onplaying = null;
|
||||
// Refresh to show the video
|
||||
self.updateView();
|
||||
};
|
||||
streamElement.onplaying = onPlayingHandler;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether or not video stream exists and has started for this
|
||||
* RemoteVideo instance. This is checked by trying to select video element in
|
||||
* this container and checking if 'currentTime' field's value is greater than 0.
|
||||
* Checks whether the video stream has started for this RemoteVideo instance.
|
||||
*
|
||||
* @returns {*|boolean} true if this RemoteVideo has active video stream running
|
||||
* @returns {boolean} true if this RemoteVideo has a video stream for which
|
||||
* the playback has been started.
|
||||
*/
|
||||
RemoteVideo.prototype.hasVideoStarted = function () {
|
||||
var videoSelector = this.selectVideoElement();
|
||||
return videoSelector.length && videoSelector[0].currentTime > 0;
|
||||
return this.wasVideoPlayed;
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
||||
|
|
|
@ -5,6 +5,27 @@ import UIEvents from "../../../service/UI/UIEvents";
|
|||
|
||||
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
|
||||
|
||||
/**
|
||||
* Display mode constant used when video is being displayed on the small video.
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
const DISPLAY_VIDEO = 0;
|
||||
/**
|
||||
* Display mode constant used when the user's avatar is being displayed on
|
||||
* the small video.
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
const DISPLAY_AVATAR = 1;
|
||||
/**
|
||||
* Display mode constant used when neither video nor avatar is being displayed
|
||||
* on the small video.
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
const DISPLAY_BLACKNESS = 2;
|
||||
|
||||
function SmallVideo(VideoLayout) {
|
||||
this.isAudioMuted = false;
|
||||
this.hasAvatar = false;
|
||||
|
@ -337,6 +358,16 @@ SmallVideo.prototype.selectVideoElement = function () {
|
|||
return $(RTCUIHelper.findVideoElement($('#' + this.videoSpanId)[0]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the HTML image element which displays user's avatar.
|
||||
*
|
||||
* @return {jQuery|HTMLElement} a jQuery selector pointing to the HTML image
|
||||
* element which displays the user's avatar.
|
||||
*/
|
||||
SmallVideo.prototype.$avatar = function () {
|
||||
return $('#' + this.videoSpanId + ' .userAvatar');
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables / disables the css responsible for focusing/pinning a video
|
||||
* thumbnail.
|
||||
|
@ -359,6 +390,47 @@ SmallVideo.prototype.hasVideo = function () {
|
|||
return this.selectVideoElement().length !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the user associated with this <tt>SmallVideo</tt> is currently
|
||||
* being displayed on the "large video".
|
||||
*
|
||||
* @return {boolean} <tt>true</tt> if the user is displayed on the large video
|
||||
* or <tt>false</tt> otherwise.
|
||||
*/
|
||||
SmallVideo.prototype.isCurrentlyOnLargeVideo = function () {
|
||||
return this.VideoLayout.isCurrentlyOnLarge(this.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether there is a playable video stream available for the user
|
||||
* associated with this <tt>SmallVideo</tt>.
|
||||
*
|
||||
* @return {boolean} <tt>true</tt> if there is a playable video stream available
|
||||
* or <tt>false</tt> otherwise.
|
||||
*/
|
||||
SmallVideo.prototype.isVideoPlayable = function() {
|
||||
return this.videoStream // Is there anything to display ?
|
||||
&& !this.isVideoMuted && !this.videoStream.isMuted() // Muted ?
|
||||
&& (this.isLocal || this.VideoLayout.isInLastN(this.id));
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines what should be display on the thumbnail.
|
||||
*
|
||||
* @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
|
||||
* or <tt>DISPLAY_BLACKNESS</tt>.
|
||||
*/
|
||||
SmallVideo.prototype.selectDisplayMode = function() {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
if (this.isCurrentlyOnLargeVideo()) {
|
||||
return DISPLAY_BLACKNESS;
|
||||
} else if (this.isVideoPlayable() && this.selectVideoElement().length) {
|
||||
return DISPLAY_VIDEO;
|
||||
} else {
|
||||
return DISPLAY_AVATAR;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides or shows the user's avatar.
|
||||
* This update assumes that large video had been updated and we will
|
||||
|
@ -378,46 +450,28 @@ SmallVideo.prototype.updateView = function () {
|
|||
}
|
||||
}
|
||||
|
||||
let video = this.selectVideoElement();
|
||||
|
||||
let avatar = $('#' + this.videoSpanId + ' .userAvatar');
|
||||
|
||||
var isCurrentlyOnLarge = this.VideoLayout.isCurrentlyOnLarge(this.id);
|
||||
|
||||
var showVideo = !this.isVideoMuted && !isCurrentlyOnLarge;
|
||||
var showAvatar;
|
||||
if ((!this.isLocal
|
||||
&& !this.VideoLayout.isInLastN(this.id))
|
||||
|| this.isVideoMuted) {
|
||||
showAvatar = true;
|
||||
} else {
|
||||
// We want to show the avatar when the video is muted or not exists
|
||||
// that is when 'true' or 'null' is returned
|
||||
showAvatar = !this.videoStream || this.videoStream.isMuted();
|
||||
}
|
||||
|
||||
showAvatar = showAvatar && !isCurrentlyOnLarge;
|
||||
|
||||
if (video && video.length > 0) {
|
||||
setVisibility(video, showVideo);
|
||||
}
|
||||
setVisibility(avatar, showAvatar);
|
||||
// Determine whether video, avatar or blackness should be displayed
|
||||
let displayMode = this.selectDisplayMode();
|
||||
// Show/hide video
|
||||
setVisibility(this.selectVideoElement(), displayMode === DISPLAY_VIDEO);
|
||||
// Show/hide the avatar
|
||||
setVisibility(this.$avatar(), displayMode === DISPLAY_AVATAR);
|
||||
};
|
||||
|
||||
SmallVideo.prototype.avatarChanged = function (avatarUrl) {
|
||||
var thumbnail = $('#' + this.videoSpanId);
|
||||
var avatar = $('#' + this.videoSpanId + ' .userAvatar');
|
||||
var avatarSel = this.$avatar();
|
||||
this.hasAvatar = true;
|
||||
|
||||
// set the avatar in the thumbnail
|
||||
if (avatar && avatar.length > 0) {
|
||||
avatar[0].src = avatarUrl;
|
||||
if (avatarSel && avatarSel.length > 0) {
|
||||
avatarSel[0].src = avatarUrl;
|
||||
} else {
|
||||
if (thumbnail && thumbnail.length > 0) {
|
||||
avatar = document.createElement('img');
|
||||
avatar.className = 'userAvatar';
|
||||
avatar.src = avatarUrl;
|
||||
thumbnail.append(avatar);
|
||||
var avatarElement = document.createElement('img');
|
||||
avatarElement.className = 'userAvatar';
|
||||
avatarElement.src = avatarUrl;
|
||||
thumbnail.append(avatarElement);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -173,25 +173,51 @@ export class VideoContainer extends LargeContainer {
|
|||
|
||||
this.isVisible = false;
|
||||
|
||||
/**
|
||||
* Flag indicates whether or not the avatar is currently displayed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.avatarDisplayed = false;
|
||||
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
|
||||
* element has started(which means that there is any image rendered
|
||||
* even if the video is stalled).
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.wasVideoRendered = false;
|
||||
|
||||
this.$wrapper = $('#largeVideoWrapper');
|
||||
|
||||
this.avatarHeight = $("#dominantSpeakerAvatar").height();
|
||||
|
||||
var onPlayCallback = function (event) {
|
||||
if (typeof onPlay === 'function') {
|
||||
onPlay(event);
|
||||
}
|
||||
this.wasVideoRendered = true;
|
||||
}.bind(this);
|
||||
// This does not work with Temasys plugin - has to be a property to be
|
||||
// copied between new <object> elements
|
||||
//this.$video.on('play', onPlay);
|
||||
this.$video[0].onplay = onPlay;
|
||||
this.$video[0].onplay = onPlayCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a filter on the video which indicates that there are some
|
||||
* problems with the media connection.
|
||||
* problems with the local media connection.
|
||||
*
|
||||
* @param {boolean} enable <tt>true</tt> if the filter is to be enabled or
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
enableVideoProblemFilter (enable) {
|
||||
enableLocalConnectionProblemFilter (enable) {
|
||||
this.$video.toggleClass("videoProblemFilter", enable);
|
||||
}
|
||||
|
||||
|
@ -251,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) {
|
||||
let [width, height]
|
||||
= this.getVideoSize(containerWidth, containerHeight);
|
||||
|
@ -263,6 +313,8 @@ export class VideoContainer extends LargeContainer {
|
|||
|
||||
this.$avatar.css('top', top);
|
||||
|
||||
this.positionRemoteConnectionMessage();
|
||||
|
||||
this.$wrapper.animate({
|
||||
width: width,
|
||||
height: height,
|
||||
|
@ -284,6 +336,14 @@ export class VideoContainer extends LargeContainer {
|
|||
* @param {string} videoType video type
|
||||
*/
|
||||
setStream (stream, videoType) {
|
||||
|
||||
if (this.stream === stream) {
|
||||
return;
|
||||
} else {
|
||||
// The stream has changed, so the image will be lost on detach
|
||||
this.wasVideoRendered = false;
|
||||
}
|
||||
|
||||
// detach old stream
|
||||
if (this.stream) {
|
||||
this.stream.detach(this.$video[0]);
|
||||
|
@ -339,10 +399,23 @@ export class VideoContainer extends LargeContainer {
|
|||
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
|
||||
|
||||
this.$avatar.css("visibility", show ? "visible" : "hidden");
|
||||
this.avatarDisplayed = 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
|
||||
// largeVideo, because when Temasys plugin is in use it replaces
|
||||
// <video> elements with plugin <object> tag. In Safari jQuery is
|
||||
|
|
|
@ -384,18 +384,30 @@ var VideoLayout = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Creates a participant container for the given id and smallVideo.
|
||||
* Creates or adds a participant container for the given id and smallVideo.
|
||||
*
|
||||
* @param id the id of the participant to add
|
||||
* @param {JitsiParticipant} user the participant to add
|
||||
* @param {SmallVideo} smallVideo optional small video instance to add as a
|
||||
* remote video, if undefined RemoteVideo will be created
|
||||
* remote video, if undefined <tt>RemoteVideo</tt> will be created
|
||||
*/
|
||||
addParticipantContainer (id, smallVideo) {
|
||||
addParticipantContainer (user, smallVideo) {
|
||||
let id = user.getId();
|
||||
let remoteVideo;
|
||||
if(smallVideo)
|
||||
remoteVideo = smallVideo;
|
||||
else
|
||||
remoteVideo = new RemoteVideo(id, VideoLayout, eventEmitter);
|
||||
remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
|
||||
this.addRemoteVideoContainer(id, remoteVideo);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds remote video container for the given id and <tt>SmallVideo</tt>.
|
||||
*
|
||||
* @param {string} the id of the video to add
|
||||
* @param {SmallVideo} smallVideo the small video instance to add as a
|
||||
* remote video
|
||||
*/
|
||||
addRemoteVideoContainer (id, remoteVideo) {
|
||||
remoteVideos[id] = remoteVideo;
|
||||
|
||||
let videoType = VideoLayout.getRemoteVideoType(id);
|
||||
|
@ -413,6 +425,8 @@ var VideoLayout = {
|
|||
} else {
|
||||
VideoLayout.resizeThumbnails(false, true);
|
||||
}
|
||||
// Initialize the view
|
||||
remoteVideo.updateView();
|
||||
},
|
||||
|
||||
videoactive (videoelem, resourceJid) {
|
||||
|
@ -487,6 +501,18 @@ var VideoLayout = {
|
|||
localVideoThumbnail.showAudioIndicator(isMuted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows/hides the indication about local connection being interrupted.
|
||||
*
|
||||
* @param {boolean} isInterrupted <tt>true</tt> if local connection is
|
||||
* currently in the interrupted state or <tt>false</tt> if the connection
|
||||
* is fine.
|
||||
*/
|
||||
showLocalConnectionInterrupted (isInterrupted) {
|
||||
localVideoThumbnail.connectionIndicator
|
||||
.updateConnectionStatusIndicator(!isInterrupted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resizes thumbnails.
|
||||
*/
|
||||
|
@ -618,6 +644,35 @@ var VideoLayout = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows/hides warning about remote user's connectivity issues.
|
||||
*
|
||||
* @param {string} id the ID of the remote participant(MUC nickname)
|
||||
* @param {boolean} isActive true if the connection is ok or false when
|
||||
* the user is having connectivity issues.
|
||||
*/
|
||||
onParticipantConnectionStatusChanged (id, isActive) {
|
||||
// Show/hide warning on the large video
|
||||
if (this.isCurrentlyOnLarge(id)) {
|
||||
if (largeVideo) {
|
||||
// We have to trigger full large video update to transition from
|
||||
// avatar to video on connectivity restored.
|
||||
this.updateLargeVideo(id, true /* force update */);
|
||||
}
|
||||
}
|
||||
// Show/hide warning on the thumbnail
|
||||
let remoteVideo = remoteVideos[id];
|
||||
if (remoteVideo) {
|
||||
// Updating only connection status indicator is not enough, because
|
||||
// when we the connection is restored while the avatar was displayed
|
||||
// (due to 'muted while disconnected' condition) we may want to show
|
||||
// the video stream again and in order to do that the display mode
|
||||
// must be updated.
|
||||
//remoteVideo.updateConnectionStatusIndicator(isActive);
|
||||
remoteVideo.updateView();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* On last N change event.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue