feat(connection-indicator): convert to react
- Create a new ConnectionIndicator component for displaying an icon for connection quality and for triggering a popover. The popover handling has been left in ConnectionIndicator for now, which follows the existing implementation. - Remove the unused method "connectionIndicatorShowMore" - Change the implementation of existing methods that update the connection indicator to call the same method which will rerender the indicator completely.
This commit is contained in:
parent
35f79dd2b4
commit
4ce5888b4c
@ -58,14 +58,27 @@
|
|||||||
padding: $toolbarPadding;
|
padding: $toolbarPadding;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
.connection-indicator-container,
|
||||||
|
.connection-indicator,
|
||||||
|
span.indicator {
|
||||||
|
margin-right: em(5, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.indicator {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-indicator,
|
||||||
span.indicator {
|
span.indicator {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: $thumbnailIndicatorSize;
|
line-height: $thumbnailIndicatorSize;
|
||||||
display: none;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-right: em(5, 8);
|
|
||||||
float: left;
|
float: left;
|
||||||
@include circle($thumbnailIndicatorSize);
|
@include circle($thumbnailIndicatorSize);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -74,10 +87,6 @@
|
|||||||
color: $thumbnailPictogramColor;
|
color: $thumbnailPictogramColor;
|
||||||
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
|
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicatoricon {
|
.indicatoricon {
|
||||||
@include absoluteAligning();
|
@include absoluteAligning();
|
||||||
}
|
}
|
||||||
@ -297,7 +306,7 @@
|
|||||||
background: $raiseHandBg;
|
background: $raiseHandBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
#connectionindicator {
|
.connection-indicator {
|
||||||
background: $connectionIndicatorBg;
|
background: $connectionIndicatorBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -665,11 +665,6 @@ UI.getRemoteVideoType = function (jid) {
|
|||||||
return VideoLayout.getRemoteVideoType(jid);
|
return VideoLayout.getRemoteVideoType(jid);
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.connectionIndicatorShowMore = function(id) {
|
|
||||||
VideoLayout.showMore(id);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME check if someone user this
|
// FIXME check if someone user this
|
||||||
UI.showLoginPopup = function(callback) {
|
UI.showLoginPopup = function(callback) {
|
||||||
logger.log('password is required');
|
logger.log('password is required');
|
||||||
|
|||||||
@ -1,273 +0,0 @@
|
|||||||
/* global $, interfaceConfig, JitsiMeetJS */
|
|
||||||
/* jshint -W101 */
|
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
import { ConnectionStatsTable } from '../../../react/features/connection-stats';
|
|
||||||
/* eslint-enable no-unused-vars */
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
const qualityToWidth = [
|
|
||||||
// Full (5 bars)
|
|
||||||
{percent: 80, width: "100%"},
|
|
||||||
// 4 bars
|
|
||||||
{percent: 60, width: "80%"},
|
|
||||||
// 3 bars
|
|
||||||
{percent: 40, width: "55%"},
|
|
||||||
// 2 bars
|
|
||||||
{percent: 20, width: "40%"},
|
|
||||||
// 1 bar
|
|
||||||
{percent: 0, width: "20%"}
|
|
||||||
// Note: we never show 0 bars.
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs new connection indicator.
|
|
||||||
* @param videoContainer the video container associated with the indicator.
|
|
||||||
* @param videoId the identifier of the video
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConnectionIndicator(videoContainer, videoId) {
|
|
||||||
this.videoContainer = videoContainer;
|
|
||||||
this.bandwidth = null;
|
|
||||||
this.packetLoss = null;
|
|
||||||
this.bitrate = null;
|
|
||||||
this.showMoreValue = false;
|
|
||||||
this.resolution = null;
|
|
||||||
this.transport = [];
|
|
||||||
this.framerate = null;
|
|
||||||
this.popover = null;
|
|
||||||
this.id = videoId;
|
|
||||||
this.create();
|
|
||||||
|
|
||||||
this.isLocalVideo
|
|
||||||
= this.videoContainer.videoSpanId === 'localVideoContainer';
|
|
||||||
this.showMore = this.showMore.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the html content.
|
|
||||||
* @returns {string} the html content.
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.generateText = function () {
|
|
||||||
/* jshint ignore:start */
|
|
||||||
return (
|
|
||||||
<ConnectionStatsTable
|
|
||||||
bandwidth = { this.bandwidth }
|
|
||||||
bitrate = { this.bitrate }
|
|
||||||
isLocalVideo = { this.isLocalVideo }
|
|
||||||
framerate = { this.framerate }
|
|
||||||
onShowMore = { this.showMore }
|
|
||||||
packetLoss = { this.packetLoss}
|
|
||||||
resolution = { this.resolution }
|
|
||||||
shouldShowMore = { this.showMoreValue }
|
|
||||||
transport = { this.transport } />
|
|
||||||
);
|
|
||||||
/* jshint ignore:end */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows or hide the additional information.
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.showMore = function () {
|
|
||||||
this.showMoreValue = !this.showMoreValue;
|
|
||||||
this.updatePopoverData();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
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(iconClass);
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the indicator
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.create = function () {
|
|
||||||
let indicatorId = 'connectionindicator';
|
|
||||||
let element = UIUtil.getVideoThumbnailIndicatorSpan({
|
|
||||||
videoSpanId: this.videoContainer.videoSpanId,
|
|
||||||
indicatorId
|
|
||||||
});
|
|
||||||
element.classList.add('show');
|
|
||||||
this.connectionIndicatorContainer = element;
|
|
||||||
|
|
||||||
let popoverContent = (
|
|
||||||
`<div class="connection-info" data-i18n="${indicatorId}.na"></div>`
|
|
||||||
);
|
|
||||||
this.popover = new JitsiPopover($(element), {
|
|
||||||
content: popoverContent,
|
|
||||||
skin: "black",
|
|
||||||
position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
|
|
||||||
});
|
|
||||||
|
|
||||||
// override popover show method to make sure we will update the content
|
|
||||||
// before showing the popover
|
|
||||||
var origShowFunc = this.popover.show;
|
|
||||||
this.popover.show = function () {
|
|
||||||
// update content by forcing it, to finish even if popover
|
|
||||||
// is not visible
|
|
||||||
this.updatePopoverData(true);
|
|
||||||
// call the original show, passing its actual this
|
|
||||||
origShowFunc.call(this.popover);
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
let connectionIconContainer = document.createElement('div');
|
|
||||||
connectionIconContainer.className = 'connection indicatoricon';
|
|
||||||
|
|
||||||
|
|
||||||
this.emptyIcon = connectionIconContainer.appendChild(
|
|
||||||
createIcon(["connection_empty"], "icon-connection"));
|
|
||||||
this.fullIcon = connectionIconContainer.appendChild(
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the indicator
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.remove = function() {
|
|
||||||
if (this.connectionIndicatorContainer.parentNode) {
|
|
||||||
this.connectionIndicatorContainer.parentNode.removeChild(
|
|
||||||
this.connectionIndicatorContainer);
|
|
||||||
}
|
|
||||||
this.popover.forceHide();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the UI which displays or not a warning about user's connectivity
|
|
||||||
* problems.
|
|
||||||
*
|
|
||||||
* @param {ParticipantConnectionStatus} connectionStatus
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.updateConnectionStatusIndicator
|
|
||||||
= 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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the data of the indicator
|
|
||||||
* @param percent the percent of connection quality
|
|
||||||
* @param object the statistics data.
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.updateConnectionQuality =
|
|
||||||
function (percent, object) {
|
|
||||||
if (!percent) {
|
|
||||||
this.connectionIndicatorContainer.style.display = "none";
|
|
||||||
this.popover.forceHide();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if(this.connectionIndicatorContainer.style.display == "none") {
|
|
||||||
this.connectionIndicatorContainer.style.display = "block";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (object.framerate)
|
|
||||||
this.framerate = object.framerate;
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = qualityToWidth.find(x => percent >= x.percent);
|
|
||||||
this.fullIcon.style.width = width.width;
|
|
||||||
|
|
||||||
this.updatePopoverData();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the resolution
|
|
||||||
* @param resolution the new resolution
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.updateResolution = function (resolution) {
|
|
||||||
this.resolution = resolution;
|
|
||||||
this.updatePopoverData();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the framerate
|
|
||||||
* @param framerate the new resolution
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.updateFramerate = function (framerate) {
|
|
||||||
this.framerate = framerate;
|
|
||||||
this.updatePopoverData();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the content of the popover if its visible
|
|
||||||
* @param force to work even if popover is not visible
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.updatePopoverData = function (force) {
|
|
||||||
// generate content, translate it and add it to document only if
|
|
||||||
// popover is visible or we force to do so.
|
|
||||||
if(this.popover.popoverShown || force) {
|
|
||||||
this.popover.updateContent(this.generateText());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the popover
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.hide = function () {
|
|
||||||
this.popover.forceHide();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the indicator
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.hideIndicator = function () {
|
|
||||||
this.connectionIndicatorContainer.style.display = "none";
|
|
||||||
if(this.popover)
|
|
||||||
this.popover.forceHide();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a hover listener to the popover.
|
|
||||||
*/
|
|
||||||
ConnectionIndicator.prototype.addPopoverHoverListener = function (listener) {
|
|
||||||
this.popover.addOnHoverPopover(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConnectionIndicator;
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
/* global $, config, interfaceConfig, APP, JitsiMeetJS */
|
/* global $, config, interfaceConfig, APP, JitsiMeetJS */
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
|
|
||||||
import ConnectionIndicator from "./ConnectionIndicator";
|
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import SmallVideo from "./SmallVideo";
|
import SmallVideo from "./SmallVideo";
|
||||||
@ -13,7 +12,6 @@ function LocalVideo(VideoLayout, emitter) {
|
|||||||
this.videoSpanId = "localVideoContainer";
|
this.videoSpanId = "localVideoContainer";
|
||||||
this.container = $("#localVideoContainer").get(0);
|
this.container = $("#localVideoContainer").get(0);
|
||||||
this.localVideoId = null;
|
this.localVideoId = null;
|
||||||
this.createConnectionIndicator();
|
|
||||||
this.bindHoverHandler();
|
this.bindHoverHandler();
|
||||||
if(config.enableLocalVideoFlip)
|
if(config.enableLocalVideoFlip)
|
||||||
this._buildContextMenu();
|
this._buildContextMenu();
|
||||||
@ -32,6 +30,7 @@ function LocalVideo(VideoLayout, emitter) {
|
|||||||
this.setDisplayName();
|
this.setDisplayName();
|
||||||
|
|
||||||
this.addAudioLevelIndicator();
|
this.addAudioLevelIndicator();
|
||||||
|
this.updateConnectionIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
||||||
@ -148,13 +147,6 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalVideo.prototype.createConnectionIndicator = function() {
|
|
||||||
if(this.connectionIndicator)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.connectionIndicator = new ConnectionIndicator(this, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
LocalVideo.prototype.changeVideo = function (stream) {
|
LocalVideo.prototype.changeVideo = function (stream) {
|
||||||
this.videoStream = stream;
|
this.videoStream = stream;
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import {
|
|||||||
|
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
|
|
||||||
import ConnectionIndicator from './ConnectionIndicator';
|
|
||||||
|
|
||||||
import SmallVideo from "./SmallVideo";
|
import SmallVideo from "./SmallVideo";
|
||||||
import UIUtils from "../util/UIUtil";
|
import UIUtils from "../util/UIUtil";
|
||||||
@ -48,7 +47,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
|
|||||||
this.hasRemoteVideoMenu = false;
|
this.hasRemoteVideoMenu = false;
|
||||||
this._supportsRemoteControl = false;
|
this._supportsRemoteControl = false;
|
||||||
this.addRemoteVideoContainer();
|
this.addRemoteVideoContainer();
|
||||||
this.connectionIndicator = new ConnectionIndicator(this, this.id);
|
this.updateConnectionIndicator();
|
||||||
this.setDisplayName();
|
this.setDisplayName();
|
||||||
this.bindHoverHandler();
|
this.bindHoverHandler();
|
||||||
this.flipX = false;
|
this.flipX = false;
|
||||||
@ -497,10 +496,7 @@ RemoteVideo.prototype.updateConnectionStatusIndicator = function () {
|
|||||||
// FIXME rename 'mutedWhileDisconnected' to 'mutedWhileNotRendering'
|
// FIXME rename 'mutedWhileDisconnected' to 'mutedWhileNotRendering'
|
||||||
// Update 'mutedWhileDisconnected' flag
|
// Update 'mutedWhileDisconnected' flag
|
||||||
this._figureOutMutedWhileDisconnected();
|
this._figureOutMutedWhileDisconnected();
|
||||||
if(this.connectionIndicator) {
|
this.updateConnectionStatus(connectionStatus);
|
||||||
this.connectionIndicator.updateConnectionStatusIndicator(
|
|
||||||
connectionStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isInterrupted
|
const isInterrupted
|
||||||
= connectionStatus === ParticipantConnectionStatus.INTERRUPTED;
|
= connectionStatus === ParticipantConnectionStatus.INTERRUPTED;
|
||||||
@ -621,9 +617,7 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RemoteVideo.prototype.updateResolution = function (resolution) {
|
RemoteVideo.prototype.updateResolution = function (resolution) {
|
||||||
if (this.connectionIndicator) {
|
this.updateConnectionIndicator({ resolution });
|
||||||
this.connectionIndicator.updateResolution(resolution);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -631,19 +625,7 @@ RemoteVideo.prototype.updateResolution = function (resolution) {
|
|||||||
* @param framerate the value to update
|
* @param framerate the value to update
|
||||||
*/
|
*/
|
||||||
RemoteVideo.prototype.updateFramerate = function (framerate) {
|
RemoteVideo.prototype.updateFramerate = function (framerate) {
|
||||||
if (this.connectionIndicator) {
|
this.updateConnectionIndicator({ framerate });
|
||||||
this.connectionIndicator.updateFramerate(framerate);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
RemoteVideo.prototype.removeConnectionIndicator = function () {
|
|
||||||
if (this.connectionIndicator)
|
|
||||||
this.connectionIndicator.remove();
|
|
||||||
};
|
|
||||||
|
|
||||||
RemoteVideo.prototype.hideConnectionIndicator = function () {
|
|
||||||
if (this.connectionIndicator)
|
|
||||||
this.connectionIndicator.hide();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -713,6 +695,10 @@ RemoteVideo.createContainer = function (spanId) {
|
|||||||
indicatorBar.className = "videocontainer__toptoolbar";
|
indicatorBar.className = "videocontainer__toptoolbar";
|
||||||
container.appendChild(indicatorBar);
|
container.appendChild(indicatorBar);
|
||||||
|
|
||||||
|
const connectionIndicatorContainer = document.createElement('span');
|
||||||
|
connectionIndicatorContainer.className = 'connection-indicator-container';
|
||||||
|
indicatorBar.appendChild(connectionIndicatorContainer);
|
||||||
|
|
||||||
let toolbar = document.createElement('div');
|
let toolbar = document.createElement('div');
|
||||||
toolbar.className = "videocontainer__toolbar";
|
toolbar.className = "videocontainer__toolbar";
|
||||||
container.appendChild(toolbar);
|
container.appendChild(toolbar);
|
||||||
|
|||||||
@ -6,6 +6,9 @@ import ReactDOM from 'react-dom';
|
|||||||
|
|
||||||
import { AudioLevelIndicator }
|
import { AudioLevelIndicator }
|
||||||
from '../../../react/features/audio-level-indicator';
|
from '../../../react/features/audio-level-indicator';
|
||||||
|
import {
|
||||||
|
ConnectionIndicator
|
||||||
|
} from '../../../react/features/connection-indicator';
|
||||||
/* eslint-enable no-unused-vars */
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
@ -64,6 +67,28 @@ function SmallVideo(VideoLayout) {
|
|||||||
this.hideDisplayName = false;
|
this.hideDisplayName = false;
|
||||||
// we can stop updating the thumbnail
|
// we can stop updating the thumbnail
|
||||||
this.disableUpdateView = false;
|
this.disableUpdateView = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics to display within the connection indicator. With new updates,
|
||||||
|
* only changed values are updated through assignment to a new reference.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {object}
|
||||||
|
*/
|
||||||
|
this._cachedConnectionStats = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the ConnectionIndicator's popover is hovered. Modifies
|
||||||
|
* how the video overlays display based on hover state.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this._popoverIsHovered = false;
|
||||||
|
|
||||||
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
|
this._onPopoverHover = this._onPopoverHover.bind(this);
|
||||||
|
this.updateView = this.updateView.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,12 +219,6 @@ SmallVideo.prototype.bindHoverHandler = function () {
|
|||||||
this.updateView();
|
this.updateView();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (this.connectionIndicator) {
|
|
||||||
this.connectionIndicator.addPopoverHoverListener(
|
|
||||||
() => {
|
|
||||||
this.updateView();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,16 +227,34 @@ SmallVideo.prototype.bindHoverHandler = function () {
|
|||||||
* @param percent the percent for connection quality
|
* @param percent the percent for connection quality
|
||||||
* @param object the data
|
* @param object the data
|
||||||
*/
|
*/
|
||||||
SmallVideo.prototype.updateStatsIndicator = function (percent, object) {
|
SmallVideo.prototype.updateConnectionStats = function (percent, object) {
|
||||||
if(this.connectionIndicator)
|
const newStats = Object.assign({}, object, { percent });
|
||||||
this.connectionIndicator.updateConnectionQuality(percent, object);
|
|
||||||
|
this.updateConnectionIndicator(newStats);
|
||||||
};
|
};
|
||||||
|
|
||||||
SmallVideo.prototype.hideIndicator = function () {
|
/**
|
||||||
if(this.connectionIndicator)
|
* Unmounts the ConnectionIndicator component.
|
||||||
this.connectionIndicator.hideIndicator();
|
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.removeConnectionIndicator = function () {
|
||||||
|
const connectionIndicatorContainer
|
||||||
|
= this.container.querySelector('.connection-indicator-container');
|
||||||
|
|
||||||
|
if (connectionIndicatorContainer) {
|
||||||
|
ReactDOM.unmountComponentAtNode(connectionIndicatorContainer);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the connectionStatus stat which displays in the ConnectionIndicator.
|
||||||
|
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.updateConnectionStatus = function (connectionStatus) {
|
||||||
|
this.updateConnectionIndicator({ connectionStatus });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows / hides the audio muted indicator over small videos.
|
* Shows / hides the audio muted indicator over small videos.
|
||||||
@ -524,9 +561,7 @@ SmallVideo.prototype.selectDisplayMode = function() {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
SmallVideo.prototype._isHovered = function () {
|
SmallVideo.prototype._isHovered = function () {
|
||||||
return this.videoIsHovered
|
return this.videoIsHovered || this._popoverIsHovered;
|
||||||
|| (this.connectionIndicator
|
|
||||||
&& this.connectionIndicator.popover.popoverIsHovered);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -700,4 +735,44 @@ SmallVideo.prototype.initBrowserSpecificProperties = function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or updates the connection indicator. Updates the previously known
|
||||||
|
* statistics about the participant's connection.
|
||||||
|
*
|
||||||
|
* @param {Object} newStats - New statistics to merge with previously known
|
||||||
|
* statistics about the participant's connection.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.updateConnectionIndicator = function (newStats = {}) {
|
||||||
|
this._cachedConnectionStats
|
||||||
|
= Object.assign({}, this._cachedConnectionStats, newStats);
|
||||||
|
|
||||||
|
const connectionIndicatorContainer
|
||||||
|
= this.container.querySelector('.connection-indicator-container');
|
||||||
|
|
||||||
|
/* jshint ignore:start */
|
||||||
|
ReactDOM.render(
|
||||||
|
<ConnectionIndicator
|
||||||
|
isLocalVideo = { this.isLocal }
|
||||||
|
onHover = { this._onPopoverHover }
|
||||||
|
showMoreLink = { this.isLocal }
|
||||||
|
stats = { this._cachedConnectionStats } />,
|
||||||
|
connectionIndicatorContainer
|
||||||
|
);
|
||||||
|
/* jshint ignore:end */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current state of the connection indicator popover being hovered.
|
||||||
|
* If hovered, display the small video as if it is hovered.
|
||||||
|
*
|
||||||
|
* @param {boolean} popoverIsHovered - Whether or not the mouse cursor is
|
||||||
|
* currently over the connection indicator popover.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype._onPopoverHover = function (popoverIsHovered) {
|
||||||
|
this._popoverIsHovered = popoverIsHovered;
|
||||||
|
this.updateView();
|
||||||
|
};
|
||||||
|
|
||||||
export default SmallVideo;
|
export default SmallVideo;
|
||||||
|
|||||||
@ -570,8 +570,7 @@ var VideoLayout = {
|
|||||||
? ParticipantConnectionStatus.INTERRUPTED
|
? ParticipantConnectionStatus.INTERRUPTED
|
||||||
: ParticipantConnectionStatus.ACTIVE;
|
: ParticipantConnectionStatus.ACTIVE;
|
||||||
|
|
||||||
localVideoThumbnail
|
localVideoThumbnail.updateConnectionStatus(status);
|
||||||
.connectionIndicator.updateConnectionStatusIndicator(status);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -779,7 +778,7 @@ var VideoLayout = {
|
|||||||
// Why library internal objects are passed as event's args ?
|
// Why library internal objects are passed as event's args ?
|
||||||
object.resolution = resolution[APP.conference.getMyUserId()];
|
object.resolution = resolution[APP.conference.getMyUserId()];
|
||||||
object.framerate = framerate[APP.conference.getMyUserId()];
|
object.framerate = framerate[APP.conference.getMyUserId()];
|
||||||
localVideoThumbnail.updateStatsIndicator(percent, object);
|
localVideoThumbnail.updateConnectionStats(percent, object);
|
||||||
|
|
||||||
Object.keys(resolution).forEach(function (id) {
|
Object.keys(resolution).forEach(function (id) {
|
||||||
if (APP.conference.isLocalId(id)) {
|
if (APP.conference.isLocalId(id)) {
|
||||||
@ -817,7 +816,7 @@ var VideoLayout = {
|
|||||||
updateConnectionStats (id, percent, object) {
|
updateConnectionStats (id, percent, object) {
|
||||||
let remoteVideo = remoteVideos[id];
|
let remoteVideo = remoteVideos[id];
|
||||||
if (remoteVideo) {
|
if (remoteVideo) {
|
||||||
remoteVideo.updateStatsIndicator(percent, object);
|
remoteVideo.updateConnectionStats(percent, object);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -828,7 +827,7 @@ var VideoLayout = {
|
|||||||
hideConnectionIndicator (id) {
|
hideConnectionIndicator (id) {
|
||||||
let remoteVideo = remoteVideos[id];
|
let remoteVideo = remoteVideos[id];
|
||||||
if (remoteVideo)
|
if (remoteVideo)
|
||||||
remoteVideo.hideConnectionIndicator();
|
remoteVideo.removeConnectionIndicator();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -894,19 +893,6 @@ var VideoLayout = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showMore (id) {
|
|
||||||
if (id === 'local') {
|
|
||||||
localVideoThumbnail.connectionIndicator.showMore();
|
|
||||||
} else {
|
|
||||||
let remoteVideo = remoteVideos[id];
|
|
||||||
if (remoteVideo) {
|
|
||||||
remoteVideo.connectionIndicator.showMore();
|
|
||||||
} else {
|
|
||||||
logger.info("Error - no remote video for id: " + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes the video area.
|
* Resizes the video area.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,284 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import JitsiPopover from '../../../../modules/UI/util/JitsiPopover';
|
||||||
|
|
||||||
|
import { JitsiParticipantConnectionStatus } from '../../base/lib-jitsi-meet';
|
||||||
|
import { ConnectionStatsTable } from '../../connection-stats';
|
||||||
|
|
||||||
|
declare var $: Object;
|
||||||
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
// Converts the percent for connection quality into a string recognized for CSS.
|
||||||
|
const QUALITY_TO_WIDTH = [
|
||||||
|
|
||||||
|
// Full (5 bars)
|
||||||
|
{
|
||||||
|
percent: 80,
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 4 bars
|
||||||
|
{
|
||||||
|
percent: 60,
|
||||||
|
width: '80%'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 3 bars
|
||||||
|
{
|
||||||
|
percent: 40,
|
||||||
|
width: '55%'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2 bars
|
||||||
|
{
|
||||||
|
percent: 20,
|
||||||
|
width: '40%'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 1 bar
|
||||||
|
{
|
||||||
|
percent: 0,
|
||||||
|
width: '20%'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we never show 0 bars.
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link Component} which displays the current connection
|
||||||
|
* quality percentage and has a popover to show more detailed connection stats.
|
||||||
|
*
|
||||||
|
* @extends {Component}
|
||||||
|
*/
|
||||||
|
class ConnectionIndicator extends Component {
|
||||||
|
/**
|
||||||
|
* {@code ConnectionIndicator} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Whether or not the displays stats are for local video.
|
||||||
|
*/
|
||||||
|
isLocalVideo: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback to invoke when the hover state over the popover changes.
|
||||||
|
*/
|
||||||
|
onHover: React.PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the popover should display a link that can toggle
|
||||||
|
* a more detailed view of the stats.
|
||||||
|
*/
|
||||||
|
showMoreLink: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that contains statistics related to connection quality.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* bandwidth: Object,
|
||||||
|
* bitrate: Object,
|
||||||
|
* connectionStatus: String,
|
||||||
|
* framerate: Object,
|
||||||
|
* packetLoss: Object,
|
||||||
|
* percent: Number,
|
||||||
|
* resolution: Object,
|
||||||
|
* transport: Array
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
stats: React.PropTypes.object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code ConnectionIndicator} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal reference to topmost DOM/HTML element backing the React
|
||||||
|
* {@code Component}. Accessed directly for associating an element as
|
||||||
|
* the trigger for a popover.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {HTMLDivElement}
|
||||||
|
*/
|
||||||
|
this._rootElement = null;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
/**
|
||||||
|
* Whether or not the popover content should display additional
|
||||||
|
* statistics.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
showMoreStats: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
|
this._onToggleShowMore = this._onToggleShowMore.bind(this);
|
||||||
|
this._setRootElement = this._setRootElement.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a popover instance to display when the component is hovered.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
this.popover = new JitsiPopover($(this._rootElement), {
|
||||||
|
content: this._renderStatisticsTable(),
|
||||||
|
skin: 'black',
|
||||||
|
position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.popover.addOnHoverPopover(this.props.onHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the contents of the popover. This is done manually because the
|
||||||
|
* popover is not a React Component yet and so is not automatiucally aware
|
||||||
|
* of changed data.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* returns {void}
|
||||||
|
*/
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.popover.updateContent(this._renderStatisticsTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up any popover instance that is linked to the component.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* returns {void}
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.popover.forceHide();
|
||||||
|
this.popover.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className = 'connection-indicator indicator'
|
||||||
|
ref = { this._setRootElement }>
|
||||||
|
<div className = 'connection indicatoricon'>
|
||||||
|
{ this._renderIcon() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the show more link in the popover content is
|
||||||
|
* clicked. Sets the state which will determine if the popover should show
|
||||||
|
* additional statistics about the connection.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onToggleShowMore() {
|
||||||
|
this.setState({ showMoreStats: !this.state.showMoreStats });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ReactElement for displaying an icon that represents the current
|
||||||
|
* connection quality.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderIcon() {
|
||||||
|
switch (this.props.stats.connectionStatus) {
|
||||||
|
case JitsiParticipantConnectionStatus.INTERRUPTED:
|
||||||
|
return (
|
||||||
|
<span className = 'connection_lost'>
|
||||||
|
<i className = 'icon-connection-lost' />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case JitsiParticipantConnectionStatus.INACTIVE:
|
||||||
|
return (
|
||||||
|
<span className = 'connection_ninja'>
|
||||||
|
<i className = 'icon-ninja' />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
default: {
|
||||||
|
const { percent } = this.props.stats;
|
||||||
|
const width = QUALITY_TO_WIDTH.find(x => percent >= x.percent);
|
||||||
|
const iconWidth = width && width.width
|
||||||
|
? { width: width && width.width } : {};
|
||||||
|
|
||||||
|
return [
|
||||||
|
<span
|
||||||
|
className = 'connection_empty'
|
||||||
|
key = 'icon-empty'>
|
||||||
|
<i className = 'icon-connection' />
|
||||||
|
</span>,
|
||||||
|
<span
|
||||||
|
className = 'connection_full'
|
||||||
|
key = 'icon-full'
|
||||||
|
style = { iconWidth }>
|
||||||
|
<i className = 'icon-connection' />
|
||||||
|
</span>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code ConnectionStatisticsTable} instance.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderStatisticsTable() {
|
||||||
|
const {
|
||||||
|
bandwidth,
|
||||||
|
bitrate,
|
||||||
|
framerate,
|
||||||
|
packetLoss,
|
||||||
|
resolution,
|
||||||
|
transport
|
||||||
|
} = this.props.stats;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConnectionStatsTable
|
||||||
|
bandwidth = { bandwidth }
|
||||||
|
bitrate = { bitrate }
|
||||||
|
framerate = { framerate }
|
||||||
|
isLocalVideo = { this.props.isLocalVideo }
|
||||||
|
onShowMore = { this._onToggleShowMore }
|
||||||
|
packetLoss = { packetLoss }
|
||||||
|
resolution = { resolution }
|
||||||
|
shouldShowMore = { this.state.showMoreStats }
|
||||||
|
transport = { transport } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an internal reference to the component's root element.
|
||||||
|
*
|
||||||
|
* @param {Object} element - The highest DOM element in the component.
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_setRootElement(element) {
|
||||||
|
this._rootElement = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectionIndicator;
|
||||||
1
react/features/connection-indicator/components/index.js
Normal file
1
react/features/connection-indicator/components/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as ConnectionIndicator } from './ConnectionIndicator';
|
||||||
1
react/features/connection-indicator/index.js
Normal file
1
react/features/connection-indicator/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './components';
|
||||||
@ -34,7 +34,11 @@ export default class Filmstrip extends Component {
|
|||||||
id = 'localAudio'
|
id = 'localAudio'
|
||||||
muted = { true } />
|
muted = { true } />
|
||||||
<div className = 'videocontainer__toolbar' />
|
<div className = 'videocontainer__toolbar' />
|
||||||
<div className = 'videocontainer__toptoolbar' />
|
<div className = 'videocontainer__toptoolbar'>
|
||||||
|
<span
|
||||||
|
className
|
||||||
|
= 'connection-indicator-container' />
|
||||||
|
</div>
|
||||||
<div className = 'videocontainer__hoverOverlay' />
|
<div className = 'videocontainer__hoverOverlay' />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user