Implements connection quality indicator.
This commit is contained in:
parent
6a12d817f8
commit
dc60cfc52a
5
app.js
5
app.js
|
@ -497,7 +497,8 @@ function startRtpStatsCollector()
|
|||
if (config.enableRtpStats)
|
||||
{
|
||||
statsCollector = new StatsCollector(
|
||||
getConferenceHandler().peerconnection, 200, audioLevelUpdated);
|
||||
getConferenceHandler().peerconnection, 200, audioLevelUpdated, 2000,
|
||||
ConnectionQuality.updateLocalStats);
|
||||
statsCollector.start();
|
||||
}
|
||||
}
|
||||
|
@ -511,6 +512,7 @@ function stopRTPStatsCollector()
|
|||
{
|
||||
statsCollector.stop();
|
||||
statsCollector = null;
|
||||
ConnectionQuality.stopSendingStats();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -728,6 +730,7 @@ $(document).bind('left.muc', function (event, jid) {
|
|||
var container = document.getElementById(
|
||||
'participant_' + Strophe.getResourceFromJid(jid));
|
||||
if (container) {
|
||||
VideoLayout.removeConnectionIndicator(jid);
|
||||
// hide here, wait for video to close before removing
|
||||
$(container).hide();
|
||||
VideoLayout.resizeThumbnails();
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
var ConnectionQuality = (function () {
|
||||
|
||||
/**
|
||||
* Constructs new ConnectionQuality object
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionQuality() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* local stats
|
||||
* @type {{}}
|
||||
*/
|
||||
var stats = {};
|
||||
|
||||
/**
|
||||
* remote stats
|
||||
* @type {{}}
|
||||
*/
|
||||
var remoteStats = {};
|
||||
|
||||
/**
|
||||
* Interval for sending statistics to other participants
|
||||
* @type {null}
|
||||
*/
|
||||
var sendIntervalId = null;
|
||||
|
||||
/**
|
||||
* Updates the local statistics
|
||||
* @param data new statistics
|
||||
*/
|
||||
ConnectionQuality.updateLocalStats = function (data) {
|
||||
stats = data;
|
||||
VideoLayout.updateLocalConnectionStats(100 - stats.packetLoss.total,stats);
|
||||
if(sendIntervalId == null)
|
||||
{
|
||||
startSendingStats();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start statistics sending.
|
||||
*/
|
||||
function startSendingStats() {
|
||||
sendStats();
|
||||
sendIntervalId = setInterval(sendStats, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends statistics to other participants
|
||||
*/
|
||||
function sendStats() {
|
||||
connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
|
||||
connection.emuc.sendPresence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statistics to format for sending through XMPP
|
||||
* @param stats the statistics
|
||||
* @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
|
||||
*/
|
||||
function convertToMUCStats(stats) {
|
||||
return {
|
||||
"bitrate_donwload": stats.bitrate.download,
|
||||
"bitrate_uplpoad": stats.bitrate.upload,
|
||||
"packetLoss_total": stats.packetLoss.total,
|
||||
"packetLoss_download": stats.packetLoss.download,
|
||||
"packetLoss_upload": stats.packetLoss.upload
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statitistics to format used by VideoLayout
|
||||
* @param stats
|
||||
* @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
|
||||
*/
|
||||
function parseMUCStats(stats) {
|
||||
return {
|
||||
bitrate: {
|
||||
download: stats.bitrate_donwload,
|
||||
upload: stats.bitrate_uplpoad
|
||||
},
|
||||
packetLoss: {
|
||||
total: stats.packetLoss_total,
|
||||
download: stats.packetLoss_download,
|
||||
upload: stats.packetLoss_upload
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates remote statistics
|
||||
* @param jid the jid associated with the statistics
|
||||
* @param data the statistics
|
||||
*/
|
||||
ConnectionQuality.updateRemoteStats = function (jid, data) {
|
||||
if(data == null || data.packetLoss_total == null)
|
||||
{
|
||||
VideoLayout.updateConnectionStats(jid, null, null);
|
||||
return;
|
||||
}
|
||||
remoteStats[jid] = parseMUCStats(data);
|
||||
console.log(remoteStats[jid]);
|
||||
|
||||
VideoLayout.updateConnectionStats(jid, 100 - data.packetLoss_total,remoteStats[jid]);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops statistics sending.
|
||||
*/
|
||||
ConnectionQuality.stopSendingStats = function () {
|
||||
clearInterval(sendIntervalId);
|
||||
sendIntervalId = null;
|
||||
//notify UI about stopping statistics gathering
|
||||
VideoLayout.onStatsStop();
|
||||
};
|
||||
|
||||
return ConnectionQuality;
|
||||
})();
|
|
@ -103,6 +103,12 @@
|
|||
.icon-reload:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.icon-filmstrip:before {
|
||||
content: "\e619";
|
||||
}
|
||||
|
||||
.icon-connection:before {
|
||||
line-height: normal;
|
||||
content: "\e61a";
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
.jitsipopover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1010;
|
||||
display: none;
|
||||
max-width: 300px;
|
||||
min-width: 100px;
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
color: #333333;
|
||||
background-color: #ffffff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #cccccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 6px;
|
||||
/*-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);*/
|
||||
/*box-shadow: 0 5px 10px rgba(0, 0, 0, 0.4);*/
|
||||
white-space: normal;
|
||||
margin-top: -10px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.jitsipopover.black
|
||||
{
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.jitsipopover-content {
|
||||
padding: 9px 14px;
|
||||
font-size: 10pt;
|
||||
white-space:pre-wrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jitsipopover > .arrow,
|
||||
.jitsipopover > .arrow:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.jitsipopover > .arrow {
|
||||
border-width: 11px;
|
||||
left: 50%;
|
||||
margin-left: -11px;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: #999999;
|
||||
border-top-color: rgba(0, 0, 0, 0.25);
|
||||
bottom: -11px;
|
||||
}
|
||||
.jitsipopover > .arrow:after {
|
||||
border-width: 10px;
|
||||
content: " ";
|
||||
bottom: 1px;
|
||||
margin-left: -10px;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: #ffffff;
|
||||
}
|
||||
|
||||
.jitsipopover.black > .arrow:after
|
||||
{
|
||||
border-top-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.jitsiPopupmenuPadding {
|
||||
height: 35px;
|
||||
width: 100px;
|
||||
position: absolute;
|
||||
bottom: -35;
|
||||
}
|
||||
|
||||
.jitsipopover_green
|
||||
{
|
||||
color: #4abd04;
|
||||
}
|
||||
|
||||
.jitsipopover_orange
|
||||
{
|
||||
color: #ffa800;
|
||||
}
|
||||
|
||||
.jitsipopover_blue
|
||||
{
|
||||
color: #06a5df;
|
||||
}
|
||||
|
||||
.jitsipopover_showmore
|
||||
{
|
||||
background-color: #06a5df;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 90px;
|
||||
height: 16px;
|
||||
padding-top: 4px;
|
||||
margin: 15px auto 0px auto;
|
||||
}
|
|
@ -197,6 +197,50 @@
|
|||
border-radius:20px;
|
||||
}
|
||||
|
||||
.connectionindicator
|
||||
{
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 0;
|
||||
padding: 0px 5px;
|
||||
z-index: 3;
|
||||
width: 18px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.connection.connection_empty
|
||||
{
|
||||
color: #8B8B8B;/*#FFFFFF*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.connection.connection_full
|
||||
{
|
||||
color: #FFFFFF;/*#15A1ED*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.connection
|
||||
{
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
font-size: 8pt;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,1), -1px -1px -1px rgba(0,0,0,1);
|
||||
border: 0px;
|
||||
width: 18px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.connection_info
|
||||
{
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
white-space:nowrap;
|
||||
/*width: 260px;*/
|
||||
}
|
||||
|
||||
#localVideoContainer>span.status:hover,
|
||||
#localVideoContainer>span.displayname:hover {
|
||||
cursor: text;
|
||||
|
@ -233,7 +277,7 @@
|
|||
position: absolute;
|
||||
color: #FFFFFF;
|
||||
top: 0;
|
||||
right: 0;
|
||||
right: 23px;
|
||||
padding: 8px 5px;
|
||||
width: 25px;
|
||||
font-size: 8pt;
|
||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -33,4 +33,5 @@
|
|||
<glyph unicode="" d="M32.887 258.374c5.026 4.679 12.994 10.886 21.642 16.349 25.668 16.31 54.057 25.449 83.415 32.066 24.381 5.475 49.123 8.444 74.033 10.101 27.877 1.877 55.779 1.89 83.696 0.399 19.972-1.092 39.843-3.251 59.56-6.606 21.978-3.753 43.519-8.997 64.392-16.875 12.209-4.587 24.086-10.053 35.267-16.786 14.858-8.946 28.276-19.612 38.61-33.674 10.409-14.151 15.861-30.204 16.914-47.696 0.873-13.701 0.358-27.349-2.828-40.794-1.438-6.041-4.113-11.567-8.277-16.193-5.709-6.324-13.212-8.51-21.386-8.818-10.231-0.334-20.205 2.057-30.18 4.113-19.456 3.985-38.918 8.123-58.349 12.364-7.069 1.517-14.344 2.546-20.825 6.298-11.154 6.478-17.223 15.887-17.017 28.892 0.129 8.435 1.108 16.891 1.235 25.348 0.156 12.505-4.962 22.581-15.449 29.521-7.197 4.769-15.347 7.456-23.726 9.333-20.206 4.523-40.693 5.089-61.281 5.025-14.411-0.063-28.791-0.834-43.047-3.071-9.974-1.581-19.781-3.906-28.866-8.507-12.159-6.182-19.677-15.732-20.036-29.676-0.22-8.175 0.487-16.401 0.964-24.575 0.321-5.911-0.040-11.723-2.648-17.144-4.63-9.692-12.468-15.836-22.685-18.482-11.323-2.933-22.802-5.27-34.252-7.611-19.051-3.882-38.108-7.684-57.208-11.259-7.263-1.387-14.627-0.976-21.567 1.801-9.371 3.728-14.462 11.387-17.069 20.668-3.548 12.699-3.921 25.757-3.483 38.865 0.45 13.52 2.942 26.618 9.202 38.803 4.897 9.532 11.246 17.977 21.246 27.821z" horiz-adv-x="513" />
|
||||
<glyph unicode="" d="M398.543 56.151c-0.029 0.082-0.060 0.164-0.080 0.243-35.7-22.819-75.891-34.966-117.012-34.966-0.007 0-0.010 0-0.014 0-61.26 0-118.75 26.386-157.734 72.37-49.889 58.849-67.126 164.977-36.511 213.894 2.002-0.831 3.938-1.616 5.84-2.387 6.793-2.756 13.207-5.358 21.153-9.548 3.031-1.601 6.169-2.406 9.337-2.406 5.857 0 11.3 2.824 14.924 7.743 3.907 5.309 5.156 12.389 3.269 18.476l-1.762 5.705c-5.344 17.295-10.862 35.177-17.106 52.539-4.992 13.882-11.2 31.163-29.613 31.163-6.028 0-13.019-1.828-23.365-6.102-22.147-9.159-35.529-14.981-57.267-24.905-7.551-3.444-12.617-11.349-12.601-19.672 0.014-7.921 4.496-14.668 11.988-18.058 9.104-4.128 15.268-6.858 21.734-9.723l5.343-2.377c-50.969-129.551 12.401-263.229 105.657-319.606 41.749-25.237 89.25-38.57 137.385-38.57h0.021c51.36 0 102.781 15.55 142.25 42.599-15.865 14.401-22.783 34.584-25.836 43.586zM549.101 105.045c-9.057 4.288-15.178 7.129-21.611 10.122l-5.248 2.446c53.224 128.634-7.784 263.401-100.034 321.394-42.68 26.832-91.562 41.016-141.358 41.016-52.424 0-103.205-15.297-142.983-43.083l-2.692-1.882c15.798-13.782 22.93-33.394 26.459-43.205 36.463 23.97 77.838 36.704 119.947 36.704 62.704 0 121.071-27.392 160.147-75.158 48.841-59.724 64.219-166.128 32.749-214.508-1.995 0.868-3.908 1.692-5.812 2.499-6.736 2.88-13.102 5.59-20.977 9.911-3.101 1.712-6.322 2.577-9.606 2.577-5.793 0-11.2-2.779-14.845-7.634-3.906-5.217-5.239-12.216-3.483-18.257l1.639-5.651c5.048-17.423 10.265-35.428 16.206-52.921 4.794-14.119 10.757-31.691 29.589-31.691 5.921 0 12.788 1.712 22.94 5.7 22.175 8.719 35.66 14.3 57.704 23.889 7.595 3.312 12.801 11.126 12.929 19.447 0.14 7.911-4.222 14.75-11.663 18.284z" horiz-adv-x="561" />
|
||||
<glyph unicode="" d="M23.497 480.85c230.617 0 276.897 0 507.512 0 17.96 0 26.678-12.98 26.678-28.98-0.29-151.63-0.163-303.244-0.225-454.904 0-21.992-6.601-28.529-28.851-28.529-221.536-0.063-278.226-0.063-499.776 0-22.267 0-28.899 6.505-28.899 28.529-0.049 151.629 0.242 304.036-0.046 455.664-0.017 13.105 5.651 26.88 23.608 28.219zM155.702 225.149c0-59.522-0.036-86.084 0.029-145.625 0.018-25.022 5.604-30.525 31.060-30.525 116.676 0 68.537 0 185.261 0 23.538 0 29.625 5.991 29.625 29.048 0.063 119.555 0.063 173.169 0 292.695 0 24.069-5.344 29.495-28.884 29.495-117.661 0.050-70.422 0.050-188.078 0-23.522 0-28.965-5.522-28.983-29.445-0.065-59.554-0.029-86.105-0.029-145.643zM76.972 419.283c-37.465-0.031-33.343 2.979-33.422-33.343-0.1-31.975-3.527-31.767 31.264-31.686 36.499 0.097 33.6-1.882 33.651 33.777 0 33.861 2.043 31.298-31.493 31.251zM481.822 419.283c-35.579-0.017-32.78 3.092-32.875-32.682-0.065-33.651-2.254-32.346 32.264-32.346 36.544 0 32.649-1.015 32.649 33.119-0.001 34.323 3.478 31.955-32.038 31.909zM108.414 61.204c0.18 36.547 2.32 33.457-33.679 33.585-34.052 0.096-31.285 1.382-31.203-31.655 0.065-36.738-3.477-33.26 33.537-33.325 33.021-0.033 31.571-3.028 31.346 31.394zM513.859 62.2c0.067 34.167 3.221 32.652-31.649 32.589-35.066-0.066-33.328 2.897-33.264-32.652 0.065-35.322-2.192-32.361 31.878-32.329 35.998 0.066 33.101-3.349 33.034 32.392zM513.859 171.038c0 35.275 3.61 33.421-33.743 33.261-0.449 0-0.937 0-1.419 0-29.688 0-29.688 0-29.688-29.012 0-38.961-3.221-34.968 34.647-35.098 33.038-0.193 30.269-1.546 30.202 30.849zM75.653 244.936c34.147-0.082 32.907-2.784 32.812 31.491-0.097 35.564 2.448 32.459-33.007 32.505-34.953 0.050-31.907 2.352-31.942-31.989-0.031-33.715-2.85-32.231 32.138-32.007zM480.632 244.936c36.256-0.129 33.295-2.302 33.228 32.247 0 34.279 3.092 31.769-32.134 31.749-35.098-0.014-32.843 3.026-32.749-32.747 0.066-31.25 0.034-31.25 31.655-31.25zM75.2 140.19c35.502 0 33.329-3.284 33.233 32.264-0.082 31.847-0.018 31.75-32.507 31.878-35.403 0.129-32.411 1.337-32.411-31.878 0.018-34.584-2.959-32.394 31.684-32.264z" horiz-adv-x="558" />
|
||||
<glyph unicode="" d="M1.94 73.418h110.13v-105.418h-110.13v105.418zM154.409 175.072h110.135v-207.072h-110.135v207.072zM306.882 276.706h110.134v-308.706h-110.134v308.706zM459.342 378.358h110.132v-410.358h-110.132v410.358zM611.814 480h110.131v-512h-110.131v512z" horiz-adv-x="722" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
|
@ -1,6 +1,76 @@
|
|||
{
|
||||
"IcoMoonType": "selection",
|
||||
"icons": [
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M3.881 813.165h220.26v210.835h-220.26v-210.835z",
|
||||
"M308.817 609.857h220.27v414.143h-220.27v-414.143z",
|
||||
"M613.764 406.588h220.268v617.412h-220.268v-617.412z",
|
||||
"M918.685 203.285h220.265v820.715h-220.265v-820.715z",
|
||||
"M1223.629 0h220.263v1024h-220.263v-1024z"
|
||||
],
|
||||
"attrs": [
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
}
|
||||
],
|
||||
"width": 1444,
|
||||
"grid": 0,
|
||||
"tags": [
|
||||
"connection-2"
|
||||
]
|
||||
},
|
||||
"attrs": [
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
"visibility": false
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"order": 27,
|
||||
"id": 31,
|
||||
"prevSize": 32,
|
||||
"code": 58906,
|
||||
"name": "connection",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 0
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
|
@ -13,7 +83,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 26,
|
||||
"order": 25,
|
||||
"id": 29,
|
||||
"prevSize": 32,
|
||||
"code": 58905,
|
||||
|
@ -21,7 +91,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 0
|
||||
"iconIdx": 1
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -37,7 +107,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 25,
|
||||
"order": 24,
|
||||
"id": 28,
|
||||
"prevSize": 32,
|
||||
"code": 58904,
|
||||
|
@ -45,7 +115,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 1
|
||||
"iconIdx": 2
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -59,7 +129,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 24,
|
||||
"order": 23,
|
||||
"id": 27,
|
||||
"prevSize": 32,
|
||||
"code": 58903,
|
||||
|
@ -67,7 +137,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 2
|
||||
"iconIdx": 3
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -81,7 +151,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 22,
|
||||
"order": 21,
|
||||
"id": 26,
|
||||
"prevSize": 32,
|
||||
"code": 58901,
|
||||
|
@ -89,7 +159,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 3
|
||||
"iconIdx": 4
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -104,7 +174,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 23,
|
||||
"order": 22,
|
||||
"id": 25,
|
||||
"prevSize": 32,
|
||||
"code": 58902,
|
||||
|
@ -112,7 +182,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 4
|
||||
"iconIdx": 5
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -127,7 +197,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 18,
|
||||
"order": 17,
|
||||
"id": 24,
|
||||
"prevSize": 32,
|
||||
"code": 58897,
|
||||
|
@ -135,7 +205,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 5
|
||||
"iconIdx": 6
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -150,7 +220,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 19,
|
||||
"order": 18,
|
||||
"id": 23,
|
||||
"prevSize": 32,
|
||||
"code": 58898,
|
||||
|
@ -158,7 +228,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 6
|
||||
"iconIdx": 7
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -174,7 +244,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 20,
|
||||
"order": 19,
|
||||
"id": 22,
|
||||
"prevSize": 32,
|
||||
"code": 58899,
|
||||
|
@ -182,7 +252,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 7
|
||||
"iconIdx": 8
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -199,7 +269,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 21,
|
||||
"order": 20,
|
||||
"id": 21,
|
||||
"prevSize": 32,
|
||||
"code": 58900,
|
||||
|
@ -207,7 +277,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 8
|
||||
"iconIdx": 9
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -222,7 +292,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 17,
|
||||
"order": 16,
|
||||
"id": 20,
|
||||
"prevSize": 32,
|
||||
"code": 58895,
|
||||
|
@ -230,7 +300,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 9
|
||||
"iconIdx": 10
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -246,7 +316,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 16,
|
||||
"order": 15,
|
||||
"id": 19,
|
||||
"prevSize": 32,
|
||||
"code": 58896,
|
||||
|
@ -254,7 +324,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 10
|
||||
"iconIdx": 11
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -270,7 +340,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 15,
|
||||
"order": 14,
|
||||
"id": 18,
|
||||
"prevSize": 32,
|
||||
"code": 58882,
|
||||
|
@ -278,7 +348,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 11
|
||||
"iconIdx": 12
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -292,7 +362,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 14,
|
||||
"order": 13,
|
||||
"id": 17,
|
||||
"prevSize": 32,
|
||||
"code": 58886,
|
||||
|
@ -300,7 +370,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 12
|
||||
"iconIdx": 13
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -316,7 +386,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 13,
|
||||
"order": 12,
|
||||
"id": 16,
|
||||
"prevSize": 32,
|
||||
"code": 58893,
|
||||
|
@ -324,7 +394,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 13
|
||||
"iconIdx": 14
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -340,7 +410,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 12,
|
||||
"order": 11,
|
||||
"id": 15,
|
||||
"prevSize": 32,
|
||||
"code": 58894,
|
||||
|
@ -348,7 +418,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 14
|
||||
"iconIdx": 15
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -367,7 +437,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 11,
|
||||
"order": 10,
|
||||
"id": 14,
|
||||
"prevSize": 32,
|
||||
"code": 58892,
|
||||
|
@ -375,7 +445,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 15
|
||||
"iconIdx": 16
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -410,7 +480,7 @@
|
|||
}
|
||||
],
|
||||
"properties": {
|
||||
"order": 27,
|
||||
"order": 26,
|
||||
"id": 30,
|
||||
"prevSize": 32,
|
||||
"code": 58880,
|
||||
|
@ -418,7 +488,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 16
|
||||
"iconIdx": 17
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -442,7 +512,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 17
|
||||
"iconIdx": 18
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -467,7 +537,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 18
|
||||
"iconIdx": 19
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -489,7 +559,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 19
|
||||
"iconIdx": 20
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -512,7 +582,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 20
|
||||
"iconIdx": 21
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -526,7 +596,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 6,
|
||||
"order": 5,
|
||||
"id": 5,
|
||||
"prevSize": 32,
|
||||
"code": 58887,
|
||||
|
@ -534,7 +604,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 21
|
||||
"iconIdx": 22
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -549,7 +619,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 7,
|
||||
"order": 6,
|
||||
"id": 4,
|
||||
"prevSize": 32,
|
||||
"code": 58888,
|
||||
|
@ -557,7 +627,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 22
|
||||
"iconIdx": 23
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -572,7 +642,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 8,
|
||||
"order": 7,
|
||||
"id": 3,
|
||||
"prevSize": 32,
|
||||
"code": 58889,
|
||||
|
@ -580,7 +650,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 23
|
||||
"iconIdx": 24
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -596,7 +666,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 9,
|
||||
"order": 8,
|
||||
"id": 2,
|
||||
"prevSize": 32,
|
||||
"code": 58890,
|
||||
|
@ -604,7 +674,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 24
|
||||
"iconIdx": 25
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
|
@ -619,7 +689,7 @@
|
|||
"grid": 0
|
||||
},
|
||||
"properties": {
|
||||
"order": 10,
|
||||
"order": 9,
|
||||
"id": 1,
|
||||
"prevSize": 32,
|
||||
"code": 58891,
|
||||
|
@ -627,7 +697,7 @@
|
|||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 25
|
||||
"iconIdx": 26
|
||||
}
|
||||
],
|
||||
"height": 1024,
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
<script src="rtp_sts.js?v=1"></script><!-- RTP stats processing -->
|
||||
<script src="local_sts.js?v=1"></script><!-- Local stats processing -->
|
||||
<script src="videolayout.js?v=15"></script><!-- video ui -->
|
||||
<script src="connectionquality.js?v=1"></script>
|
||||
<script src="toolbar.js?v=6"></script><!-- toolbar ui -->
|
||||
<script src="toolbar_toggler.js?v=2"></script>
|
||||
<script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
|
||||
|
@ -57,6 +58,7 @@
|
|||
<script src="roomname_generator.js?v=1"></script><!-- generator for random room names -->
|
||||
<script src="keyboard_shortcut.js?v=2"></script>
|
||||
<script src="tracking.js?v=1"></script><!-- tracking -->
|
||||
<script src="jitsipopover.js?v=1"></script>
|
||||
<script src="message_handler.js?v=1"></script>
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/font.css?v=4"/>
|
||||
|
@ -66,6 +68,7 @@
|
|||
<link rel="stylesheet" href="css/modaldialog.css?v=3">
|
||||
<link rel="stylesheet" href="css/popup_menu.css?v=4">
|
||||
<link rel="stylesheet" href="css/popover.css?v=2">
|
||||
<link rel="stylesheet" href="css/jitsi_popover.css?v=2">
|
||||
<link rel="stylesheet" href="css/contact_list.css?v=3">
|
||||
<link rel="stylesheet" href="css/welcome_page.css?v=2">
|
||||
<!--
|
||||
|
@ -256,6 +259,11 @@
|
|||
</span>
|
||||
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
|
||||
<span class="focusindicator"></span>
|
||||
<!--<div class="connectionindicator">
|
||||
<span class="connection connection_empty"><i class="icon-connection"></i></span>
|
||||
<span class="connection connection_full"><i class="icon-connection"></i></span>
|
||||
</div>-->
|
||||
|
||||
</span>
|
||||
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
|
||||
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
var JitsiPopover = (function () {
|
||||
/**
|
||||
* Constructs new JitsiPopover and attaches it to the element
|
||||
* @param element jquery selector
|
||||
* @param options the options for the popover.
|
||||
* @constructor
|
||||
*/
|
||||
function JitsiPopover(element, options)
|
||||
{
|
||||
this.options = {
|
||||
skin: "white",
|
||||
content: ""
|
||||
};
|
||||
if(options)
|
||||
{
|
||||
if(options.skin)
|
||||
this.options.skin = options.skin;
|
||||
|
||||
if(options.content)
|
||||
this.options.content = options.content;
|
||||
}
|
||||
|
||||
this.elementIsHovered = false;
|
||||
this.popoverIsHovered = false;
|
||||
this.popoverShown = false;
|
||||
|
||||
element.data("jitsi_popover", this);
|
||||
this.element = element;
|
||||
this.template = ' <div class="jitsipopover ' + this.options.skin +
|
||||
'"><div class="arrow"></div><div class="jitsipopover-content"></div>' +
|
||||
'<div class="jitsiPopupmenuPadding"></div></div>';
|
||||
var self = this;
|
||||
this.element.on("mouseenter", function () {
|
||||
self.elementIsHovered = true;
|
||||
self.show();
|
||||
}).on("mouseleave", function () {
|
||||
self.elementIsHovered = false;
|
||||
setTimeout(function () {
|
||||
self.hide();
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover
|
||||
*/
|
||||
JitsiPopover.prototype.show = function () {
|
||||
this.createPopover();
|
||||
this.popoverShown = true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the popover
|
||||
*/
|
||||
JitsiPopover.prototype.hide = function () {
|
||||
if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
|
||||
{
|
||||
$(".jitsipopover").remove();
|
||||
this.popoverShown = false;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the popover html
|
||||
*/
|
||||
JitsiPopover.prototype.createPopover = function () {
|
||||
$("body").append(this.template);
|
||||
$(".jitsipopover > .jitsipopover-content").html(this.options.content);
|
||||
var self = this;
|
||||
$(".jitsipopover").on("mouseenter", function () {
|
||||
self.popoverIsHovered = true;
|
||||
}).on("mouseleave", function () {
|
||||
self.popoverIsHovered = false;
|
||||
self.hide();
|
||||
});
|
||||
|
||||
this.refreshPosition();
|
||||
};
|
||||
|
||||
/**
|
||||
* Refreshes the position of the popover
|
||||
*/
|
||||
JitsiPopover.prototype.refreshPosition = function () {
|
||||
$(".jitsipopover").position({
|
||||
my: "bottom",
|
||||
at: "top",
|
||||
collision: "fit",
|
||||
of: this.element,
|
||||
using: function (position, elements) {
|
||||
var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
|
||||
$(".jitsipopover").css({top: position.top, left: position.left, display: "block"});
|
||||
$(".jitsipopover > .arrow").css({left: calcLeft});
|
||||
$(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the content of popover.
|
||||
* @param content new content
|
||||
*/
|
||||
JitsiPopover.prototype.updateContent = function (content) {
|
||||
this.options.content = content;
|
||||
if(!this.popoverShown)
|
||||
return;
|
||||
$(".jitsipopover").remove();
|
||||
this.createPopover();
|
||||
};
|
||||
|
||||
return JitsiPopover;
|
||||
|
||||
|
||||
})();
|
|
@ -28,7 +28,7 @@ var LocalStatsCollector = (function() {
|
|||
this.stream = stream;
|
||||
this.intervalId = null;
|
||||
this.intervalMilis = interval;
|
||||
this.updateCallback = updateCallback;
|
||||
this.audioLevelsUpdateCallback = updateCallback;
|
||||
this.audioLevel = 0;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ var LocalStatsCollector = (function() {
|
|||
var audioLevel = TimeDomainDataToAudioLevel(array);
|
||||
if(audioLevel != self.audioLevel) {
|
||||
self.audioLevel = animateLevel(audioLevel, self.audioLevel);
|
||||
self.updateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
|
||||
self.audioLevelsUpdateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
|
||||
}
|
||||
},
|
||||
this.intervalMilis
|
||||
|
|
23
muc.js
23
muc.js
|
@ -94,6 +94,16 @@ Strophe.addConnectionPlugin('emuc', {
|
|||
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
|
||||
}
|
||||
|
||||
var stats = $(pres).find('>stats');
|
||||
if(stats.length)
|
||||
{
|
||||
var statsObj = {};
|
||||
Strophe.forEachChild(stats[0], "stat", function (el) {
|
||||
statsObj[el.getAttribute("name")] = el.getAttribute("value");
|
||||
});
|
||||
ConnectionQuality.updateRemoteStats(from, statsObj);
|
||||
}
|
||||
|
||||
// Parse status.
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
|
||||
// http://xmpp.org/extensions/xep-0045.html#createroom-instant
|
||||
|
@ -319,6 +329,15 @@ Strophe.addConnectionPlugin('emuc', {
|
|||
.t(this.presMap['videomuted']).up();
|
||||
}
|
||||
|
||||
if(this.presMap['statsns'])
|
||||
{
|
||||
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
|
||||
for(var stat in this.presMap["stats"])
|
||||
if(this.presMap["stats"][stat] != null)
|
||||
stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up();
|
||||
pres.up();
|
||||
}
|
||||
|
||||
if (this.presMap['prezins']) {
|
||||
pres.c('prezi',
|
||||
{xmlns: this.presMap['prezins'],
|
||||
|
@ -401,6 +420,10 @@ Strophe.addConnectionPlugin('emuc', {
|
|||
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
||||
this.presMap['videomuted'] = isMuted.toString();
|
||||
},
|
||||
addConnectionInfoToPresence: function(stats) {
|
||||
this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
|
||||
this.presMap['stats'] = stats;
|
||||
},
|
||||
findJidFromResource: function(resourceJid) {
|
||||
var peerJid = null;
|
||||
Object.keys(this.members).some(function (jid) {
|
||||
|
|
421
rtp_sts.js
421
rtp_sts.js
|
@ -1,34 +1,12 @@
|
|||
/* global ssrc2jid */
|
||||
|
||||
/**
|
||||
* Function object which once created can be used to calculate moving average of
|
||||
* given period. Example for SMA3:</br>
|
||||
* var sma3 = new SimpleMovingAverager(3);
|
||||
* while(true) // some update loop
|
||||
* {
|
||||
* var currentSma3Value = sma3(nextInputValue);
|
||||
* }
|
||||
*
|
||||
* @param period moving average period that will be used by created instance.
|
||||
* @returns {Function} SMA calculator function of given <tt>period</tt>.
|
||||
* @constructor
|
||||
* Calculates packet lost percent using the number of lost packets and the number of all packet.
|
||||
* @param lostPackets the number of lost packets
|
||||
* @param totalPackets the number of all packets.
|
||||
* @returns {number} packet loss percent
|
||||
*/
|
||||
function SimpleMovingAverager(period)
|
||||
{
|
||||
var nums = [];
|
||||
return function (num)
|
||||
{
|
||||
nums.push(num);
|
||||
if (nums.length > period)
|
||||
nums.splice(0, 1);
|
||||
var sum = 0;
|
||||
for (var i in nums)
|
||||
sum += nums[i];
|
||||
var n = period;
|
||||
if (nums.length < period)
|
||||
n = nums.length;
|
||||
return (sum / n);
|
||||
};
|
||||
function calculatePacketLoss(lostPackets, totalPackets) {
|
||||
return Math.round((lostPackets/totalPackets)*100);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,8 +17,30 @@ function PeerStats()
|
|||
{
|
||||
this.ssrc2Loss = {};
|
||||
this.ssrc2AudioLevel = {};
|
||||
this.ssrc2bitrate = {};
|
||||
this.resolution = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bandwidth
|
||||
* @type {{}}
|
||||
*/
|
||||
PeerStats.bandwidth = {};
|
||||
|
||||
/**
|
||||
* The bit rate
|
||||
* @type {{}}
|
||||
*/
|
||||
PeerStats.bitrate = {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The packet loss rate
|
||||
* @type {{}}
|
||||
*/
|
||||
PeerStats.packetLoss = null;
|
||||
|
||||
/**
|
||||
* Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
|
||||
* represented by this instance.
|
||||
|
@ -52,6 +52,17 @@ PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
|
|||
this.ssrc2Loss[ssrc] = lossRate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
|
||||
* represented by this instance.
|
||||
* @param ssrc audio or video RTP stream SSRC.
|
||||
* @param bitrate new bitrate value to be set.
|
||||
*/
|
||||
PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
|
||||
{
|
||||
this.ssrc2bitrate[ssrc] = bitrate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
|
||||
* the stream which belongs to the peer represented by this instance.
|
||||
|
@ -67,52 +78,42 @@ PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
|
|||
};
|
||||
|
||||
/**
|
||||
* Calculates average packet loss for all streams that belong to the peer
|
||||
* represented by this instance.
|
||||
* @returns {number} average packet loss for all streams that belong to the peer
|
||||
* represented by this instance.
|
||||
* Array with the transport information.
|
||||
* @type {Array}
|
||||
*/
|
||||
PeerStats.prototype.getAvgLoss = function ()
|
||||
{
|
||||
var self = this;
|
||||
var avg = 0;
|
||||
var count = Object.keys(this.ssrc2Loss).length;
|
||||
Object.keys(this.ssrc2Loss).forEach(
|
||||
function (ssrc)
|
||||
{
|
||||
avg += self.ssrc2Loss[ssrc];
|
||||
}
|
||||
);
|
||||
return count > 0 ? avg / count : 0;
|
||||
};
|
||||
PeerStats.transport = [];
|
||||
|
||||
/**
|
||||
* <tt>StatsCollector</tt> registers for stats updates of given
|
||||
* <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
|
||||
* stats are extracted and put in {@link PeerStats} objects. Once the processing
|
||||
* is done <tt>updateCallback</tt> is called with <tt>this</tt> instance as
|
||||
* is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt> instance as
|
||||
* an event source.
|
||||
*
|
||||
* @param peerconnection webRTC peer connection object.
|
||||
* @param interval stats refresh interval given in ms.
|
||||
* @param {function(StatsCollector)} updateCallback the callback called on stats
|
||||
* @param {function(StatsCollector)} audioLevelsUpdateCallback the callback called on stats
|
||||
* update.
|
||||
* @constructor
|
||||
*/
|
||||
function StatsCollector(peerconnection, interval, updateCallback)
|
||||
function StatsCollector(peerconnection, audioLevelsInterval, audioLevelsUpdateCallback, statsInterval, statsUpdateCallback)
|
||||
{
|
||||
this.peerconnection = peerconnection;
|
||||
this.baselineReport = null;
|
||||
this.currentReport = null;
|
||||
this.intervalId = null;
|
||||
this.baselineAudioLevelsReport = null;
|
||||
this.currentAudioLevelsReport = null;
|
||||
this.currentStatsReport = null;
|
||||
this.baselineStatsReport = null;
|
||||
this.audioLevelsIntervalId = null;
|
||||
// Updates stats interval
|
||||
this.intervalMilis = interval;
|
||||
// Use SMA 3 to average packet loss changes over time
|
||||
this.sma3 = new SimpleMovingAverager(3);
|
||||
this.audioLevelsIntervalMilis = audioLevelsInterval;
|
||||
|
||||
this.statsIntervalId = null;
|
||||
this.statsIntervalMilis = statsInterval;
|
||||
// Map of jids to PeerStats
|
||||
this.jid2stats = {};
|
||||
|
||||
this.updateCallback = updateCallback;
|
||||
this.audioLevelsUpdateCallback = audioLevelsUpdateCallback;
|
||||
this.statsUpdateCallback = statsUpdateCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,10 +121,12 @@ function StatsCollector(peerconnection, interval, updateCallback)
|
|||
*/
|
||||
StatsCollector.prototype.stop = function ()
|
||||
{
|
||||
if (this.intervalId)
|
||||
if (this.audioLevelsIntervalId)
|
||||
{
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
clearInterval(this.audioLevelsIntervalId);
|
||||
this.audioLevelsIntervalId = null;
|
||||
clearInterval(this.statsIntervalId);
|
||||
this.statsIntervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -143,7 +146,7 @@ StatsCollector.prototype.errorCallback = function (error)
|
|||
StatsCollector.prototype.start = function ()
|
||||
{
|
||||
var self = this;
|
||||
this.intervalId = setInterval(
|
||||
this.audioLevelsIntervalId = setInterval(
|
||||
function ()
|
||||
{
|
||||
// Interval updates
|
||||
|
@ -152,36 +155,252 @@ StatsCollector.prototype.start = function ()
|
|||
{
|
||||
var results = report.result();
|
||||
//console.error("Got interval report", results);
|
||||
self.currentReport = results;
|
||||
self.processReport();
|
||||
self.baselineReport = self.currentReport;
|
||||
self.currentAudioLevelsReport = results;
|
||||
self.processAudioLevelReport();
|
||||
self.baselineAudioLevelsReport = self.currentAudioLevelsReport;
|
||||
},
|
||||
self.errorCallback
|
||||
);
|
||||
},
|
||||
self.intervalMilis
|
||||
self.audioLevelsIntervalMilis
|
||||
);
|
||||
|
||||
this.statsIntervalId = setInterval(
|
||||
function () {
|
||||
// Interval updates
|
||||
self.peerconnection.getStats(
|
||||
function (report)
|
||||
{
|
||||
var results = report.result();
|
||||
//console.error("Got interval report", results);
|
||||
self.currentStatsReport = results;
|
||||
self.processStatsReport();
|
||||
self.baselineStatsReport = self.currentStatsReport;
|
||||
},
|
||||
self.errorCallback
|
||||
);
|
||||
},
|
||||
self.statsIntervalMilis
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stats processing logic.
|
||||
*/
|
||||
StatsCollector.prototype.processReport = function ()
|
||||
StatsCollector.prototype.processStatsReport = function () {
|
||||
if (!this.baselineStatsReport) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var idx in this.currentStatsReport) {
|
||||
var now = this.currentStatsReport[idx];
|
||||
if (now.stat('googAvailableReceiveBandwidth') || now.stat('googAvailableSendBandwidth')) {
|
||||
PeerStats.bandwidth = {
|
||||
"download": Math.round((now.stat('googAvailableReceiveBandwidth') * 8) / 1000),
|
||||
"upload": Math.round((now.stat('googAvailableSendBandwidth') * 8) / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
if(now.type == 'googCandidatePair')
|
||||
{
|
||||
var ip = now.stat('googRemoteAddress');
|
||||
var type = now.stat("googTransportType");
|
||||
if(!ip || !type)
|
||||
continue;
|
||||
var addressSaved = false;
|
||||
for(var i = 0; i < PeerStats.transport.length; i++)
|
||||
{
|
||||
if(PeerStats.transport[i].ip == ip && PeerStats.transport[i].type == type)
|
||||
{
|
||||
addressSaved = true;
|
||||
}
|
||||
}
|
||||
if(addressSaved)
|
||||
continue;
|
||||
PeerStats.transport.push({ip: ip, type: type});
|
||||
continue;
|
||||
}
|
||||
|
||||
// console.log("bandwidth: " + now.stat('googAvailableReceiveBandwidth') + " - " + now.stat('googAvailableSendBandwidth'));
|
||||
if (now.type != 'ssrc') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var before = this.baselineStatsReport[idx];
|
||||
if (!before) {
|
||||
console.warn(now.stat('ssrc') + ' not enough data');
|
||||
continue;
|
||||
}
|
||||
|
||||
var ssrc = now.stat('ssrc');
|
||||
var jid = ssrc2jid[ssrc];
|
||||
if (!jid) {
|
||||
console.warn("No jid for ssrc: " + ssrc);
|
||||
continue;
|
||||
}
|
||||
|
||||
var jidStats = this.jid2stats[jid];
|
||||
if (!jidStats) {
|
||||
jidStats = new PeerStats();
|
||||
this.jid2stats[jid] = jidStats;
|
||||
}
|
||||
|
||||
|
||||
var isDownloadStream = true;
|
||||
var key = 'packetsReceived';
|
||||
if (!now.stat(key))
|
||||
{
|
||||
isDownloadStream = false;
|
||||
key = 'packetsSent';
|
||||
if (!now.stat(key))
|
||||
{
|
||||
console.error("No packetsReceived nor packetSent stat found");
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
var packetsNow = now.stat(key);
|
||||
var packetsBefore = before.stat(key);
|
||||
var packetRate = packetsNow - packetsBefore;
|
||||
|
||||
var currentLoss = now.stat('packetsLost');
|
||||
var previousLoss = before.stat('packetsLost');
|
||||
var lossRate = currentLoss - previousLoss;
|
||||
|
||||
var packetsTotal = (packetRate + lossRate);
|
||||
|
||||
jidStats.setSsrcLoss(ssrc, {"packetsTotal": packetsTotal, "packetsLost": lossRate,
|
||||
"isDownloadStream": isDownloadStream});
|
||||
|
||||
var bytesReceived = 0, bytesSent = 0;
|
||||
if(now.stat("bytesReceived"))
|
||||
{
|
||||
bytesReceived = now.stat("bytesReceived") - before.stat("bytesReceived");
|
||||
}
|
||||
|
||||
if(now.stat("bytesSent"))
|
||||
{
|
||||
bytesSent = now.stat("bytesSent") - before.stat("bytesSent");
|
||||
}
|
||||
|
||||
if(bytesReceived < 0)
|
||||
bytesReceived = 0;
|
||||
if(bytesSent < 0)
|
||||
bytesSent = 0;
|
||||
|
||||
var time = Math.round((now.timestamp - before.timestamp) / 1000);
|
||||
jidStats.setSsrcBitrate(ssrc, {
|
||||
"download": Math.round(((bytesReceived * 8) / time) / 1000),
|
||||
"upload": Math.round(((bytesSent * 8) / time) / 1000)});
|
||||
var resolution = {height: null, width: null};
|
||||
if(now.stat("googFrameHeightReceived") && now.stat("googFrameWidthReceived"))
|
||||
{
|
||||
resolution.height = now.stat("googFrameHeightReceived");
|
||||
resolution.width = now.stat("googFrameWidthReceived");
|
||||
}
|
||||
else if(now.stat("googFrameHeightSent") && now.stat("googFrameWidthSent"))
|
||||
{
|
||||
resolution.height = now.stat("googFrameHeightSent");
|
||||
resolution.width = now.stat("googFrameWidthSent");
|
||||
}
|
||||
|
||||
if(!jidStats.resolution)
|
||||
jidStats.resolution = null;
|
||||
|
||||
console.log(jid + " - resolution: " + resolution.height + "x" + resolution.width);
|
||||
if(resolution.height && resolution.width)
|
||||
{
|
||||
if(!jidStats.resolution)
|
||||
jidStats.resolution = { hq: resolution, lq: resolution};
|
||||
else if(jidStats.resolution.hq.width > resolution.width &&
|
||||
jidStats.resolution.hq.height > resolution.height)
|
||||
{
|
||||
jidStats.resolution.lq = resolution;
|
||||
}
|
||||
else
|
||||
{
|
||||
jidStats.resolution.hq = resolution;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
var self = this;
|
||||
// Jid stats
|
||||
var totalPackets = {download: 0, upload: 0};
|
||||
var lostPackets = {download: 0, upload: 0};
|
||||
var bitrateDownload = 0;
|
||||
var bitrateUpload = 0;
|
||||
var resolution = {};
|
||||
Object.keys(this.jid2stats).forEach(
|
||||
function (jid)
|
||||
{
|
||||
Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
|
||||
function (ssrc)
|
||||
{
|
||||
var type = "upload";
|
||||
if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
|
||||
type = "download";
|
||||
totalPackets[type] += self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
|
||||
lostPackets[type] += self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
|
||||
}
|
||||
);
|
||||
Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
|
||||
function (ssrc) {
|
||||
bitrateDownload += self.jid2stats[jid].ssrc2bitrate[ssrc].download;
|
||||
bitrateUpload += self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
|
||||
}
|
||||
);
|
||||
resolution[jid] = self.jid2stats[jid].resolution;
|
||||
delete self.jid2stats[jid].resolution;
|
||||
}
|
||||
);
|
||||
|
||||
PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
|
||||
|
||||
PeerStats.packetLoss = {
|
||||
total:
|
||||
calculatePacketLoss(lostPackets.download + lostPackets.upload,
|
||||
totalPackets.download + totalPackets.upload),
|
||||
download:
|
||||
calculatePacketLoss(lostPackets.download, totalPackets.download),
|
||||
upload:
|
||||
calculatePacketLoss(lostPackets.upload, totalPackets.upload)
|
||||
};
|
||||
this.statsUpdateCallback(
|
||||
{
|
||||
"bitrate": PeerStats.bitrate,
|
||||
"packetLoss": PeerStats.packetLoss,
|
||||
"bandwidth": PeerStats.bandwidth,
|
||||
"resolution": resolution,
|
||||
"transport": PeerStats.transport
|
||||
});
|
||||
PeerStats.transport = [];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats processing logic.
|
||||
*/
|
||||
StatsCollector.prototype.processAudioLevelReport = function ()
|
||||
{
|
||||
if (!this.baselineReport)
|
||||
if (!this.baselineAudioLevelsReport)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var idx in this.currentReport)
|
||||
for (var idx in this.currentAudioLevelsReport)
|
||||
{
|
||||
var now = this.currentReport[idx];
|
||||
var now = this.currentAudioLevelsReport[idx];
|
||||
|
||||
if (now.type != 'ssrc')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var before = this.baselineReport[idx];
|
||||
var before = this.baselineAudioLevelsReport[idx];
|
||||
if (!before)
|
||||
{
|
||||
console.warn(now.stat('ssrc') + ' not enough data');
|
||||
|
@ -214,74 +433,10 @@ StatsCollector.prototype.processReport = function ()
|
|||
audioLevel = audioLevel / 32767;
|
||||
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
|
||||
if(jid != connection.emuc.myroomjid)
|
||||
this.updateCallback(jid, audioLevel);
|
||||
this.audioLevelsUpdateCallback(jid, audioLevel);
|
||||
}
|
||||
|
||||
var key = 'packetsReceived';
|
||||
if (!now.stat(key))
|
||||
{
|
||||
key = 'packetsSent';
|
||||
if (!now.stat(key))
|
||||
{
|
||||
console.error("No packetsReceived nor packetSent stat found");
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
var packetsNow = now.stat(key);
|
||||
var packetsBefore = before.stat(key);
|
||||
var packetRate = packetsNow - packetsBefore;
|
||||
|
||||
var currentLoss = now.stat('packetsLost');
|
||||
var previousLoss = before.stat('packetsLost');
|
||||
var lossRate = currentLoss - previousLoss;
|
||||
|
||||
var packetsTotal = (packetRate + lossRate);
|
||||
var lossPercent;
|
||||
|
||||
if (packetsTotal > 0)
|
||||
lossPercent = lossRate / packetsTotal;
|
||||
else
|
||||
lossPercent = 0;
|
||||
|
||||
//console.info(jid + " ssrc: " + ssrc + " " + key + ": " + packetsNow);
|
||||
|
||||
jidStats.setSsrcLoss(ssrc, lossPercent);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
// Jid stats
|
||||
var allPeersAvg = 0;
|
||||
var jids = Object.keys(this.jid2stats);
|
||||
jids.forEach(
|
||||
function (jid)
|
||||
{
|
||||
var peerAvg = self.jid2stats[jid].getAvgLoss(
|
||||
function (avg)
|
||||
{
|
||||
//console.info(jid + " stats: " + (avg * 100) + " %");
|
||||
allPeersAvg += avg;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (jids.length > 1)
|
||||
{
|
||||
// Our streams loss is reported as 0 always, so -1 to length
|
||||
allPeersAvg = allPeersAvg / (jids.length - 1);
|
||||
|
||||
/**
|
||||
* Calculates number of connection quality bars from 4(hi) to 0(lo).
|
||||
*/
|
||||
var outputAvg = self.sma3(allPeersAvg);
|
||||
// Linear from 4(0%) to 0(25%).
|
||||
var quality = Math.round(4 - outputAvg * 16);
|
||||
quality = Math.max(quality, 0); // lower limit 0
|
||||
quality = Math.min(quality, 4); // upper limit 4
|
||||
// TODO: quality can be used to indicate connection quality using 4 step
|
||||
// bar indicator
|
||||
//console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
391
videolayout.js
391
videolayout.js
|
@ -6,6 +6,7 @@ var VideoLayout = (function (my) {
|
|||
updateInProgress: false,
|
||||
newSrc: ''
|
||||
};
|
||||
my.connectionIndicators = {};
|
||||
|
||||
my.changeLocalAudio = function(stream) {
|
||||
connection.jingle.localAudio = stream;
|
||||
|
@ -30,6 +31,11 @@ var VideoLayout = (function (my) {
|
|||
// Set default display name.
|
||||
setDisplayName('localVideoContainer');
|
||||
|
||||
if(!VideoLayout.connectionIndicators["localVideoContainer"]) {
|
||||
VideoLayout.connectionIndicators["localVideoContainer"]
|
||||
= new ConnectionIndicator($("#localVideoContainer")[0]);
|
||||
}
|
||||
|
||||
AudioLevels.updateAudioLevelCanvas();
|
||||
|
||||
var localVideoSelector = $('#' + localVideo.id);
|
||||
|
@ -175,15 +181,42 @@ var VideoLayout = (function (my) {
|
|||
if (largeVideoState.oldJid) {
|
||||
var oldResourceJid = Strophe.getResourceFromJid(largeVideoState.oldJid);
|
||||
VideoLayout.enableDominantSpeaker(oldResourceJid, false);
|
||||
if(VideoLayout.connectionIndicators) {
|
||||
var videoContainerId = null;
|
||||
if (oldResourceJid == Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
||||
videoContainerId = 'localVideoContainer';
|
||||
}
|
||||
else {
|
||||
videoContainerId = 'participant_' + oldResourceJid;
|
||||
}
|
||||
if(VideoLayout.connectionIndicators[videoContainerId])
|
||||
VideoLayout.connectionIndicators[videoContainerId].setShowHQ(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Enable new dominant speaker in the remote videos section.
|
||||
if (largeVideoState.userJid) {
|
||||
var resourceJid = Strophe.getResourceFromJid(largeVideoState.userJid);
|
||||
VideoLayout.enableDominantSpeaker(resourceJid, true);
|
||||
if(VideoLayout.connectionIndicators)
|
||||
{
|
||||
var videoContainerId = null;
|
||||
if (resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
||||
videoContainerId = 'localVideoContainer';
|
||||
}
|
||||
else {
|
||||
videoContainerId = 'participant_' + resourceJid;
|
||||
}
|
||||
if(VideoLayout.connectionIndicators[videoContainerId])
|
||||
VideoLayout.connectionIndicators[videoContainerId].setShowHQ(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
largeVideoState.updateInProgress = false;
|
||||
|
||||
if (fade) {
|
||||
// using "this" should be ok because we're called
|
||||
// from within the fadeOut event.
|
||||
|
@ -344,6 +377,8 @@ var VideoLayout = (function (my) {
|
|||
// Set default display name.
|
||||
setDisplayName(videoSpanId);
|
||||
|
||||
VideoLayout.connectionIndicators[videoSpanId] = new ConnectionIndicator(container);
|
||||
|
||||
var nickfield = document.createElement('span');
|
||||
nickfield.className = "nick";
|
||||
nickfield.appendChild(document.createTextNode(resourceJid));
|
||||
|
@ -504,8 +539,11 @@ var VideoLayout = (function (my) {
|
|||
|
||||
if (!audioCount && !videoCount) {
|
||||
console.log("Remove whole user", container.id);
|
||||
if(VideoLayout.connectionIndicators[container.id])
|
||||
VideoLayout.connectionIndicators[container.id].remove();
|
||||
// Remove whole container
|
||||
container.remove();
|
||||
|
||||
Util.playSoundNotification('userLeft');
|
||||
VideoLayout.resizeThumbnails();
|
||||
}
|
||||
|
@ -526,7 +564,11 @@ var VideoLayout = (function (my) {
|
|||
if (!peerContainer.is(':visible') && isShow)
|
||||
peerContainer.show();
|
||||
else if (peerContainer.is(':visible') && !isShow)
|
||||
{
|
||||
peerContainer.hide();
|
||||
if(VideoLayout.connectionIndicators['participant_' + resourceJid])
|
||||
VideoLayout.connectionIndicators['participant_' + resourceJid].hide();
|
||||
}
|
||||
|
||||
VideoLayout.resizeThumbnails();
|
||||
|
||||
|
@ -758,7 +800,7 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
|
||||
videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
|
||||
videoMutedSpan.css({right: ((audioMutedSpan.length > 0)?'30px':'0px')});
|
||||
videoMutedSpan.css({right: ((audioMutedSpan.length > 0)?'50px':'30px')});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1431,5 +1473,352 @@ var VideoLayout = (function (my) {
|
|||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructs new connection indicator.
|
||||
* @param videoContainer the video container associated with the indicator.
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionIndicator(videoContainer)
|
||||
{
|
||||
this.videoContainer = videoContainer;
|
||||
this.bandwidth = null;
|
||||
this.packetLoss = null;
|
||||
this.bitrate = null;
|
||||
this.showMoreValue = false;
|
||||
this.resolution = null;
|
||||
this.transport = [];
|
||||
this.popover = null;
|
||||
this.showHQ = false;
|
||||
this.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Values for the connection quality
|
||||
* @type {{98: string, 81: string, 64: string, 47: string, 30: string, 0: string}}
|
||||
*/
|
||||
ConnectionIndicator.connectionQualityValues = {
|
||||
98: "18px", //full
|
||||
81: "15px",//4 bars
|
||||
64: "11px",//3 bars
|
||||
47: "7px",//2 bars
|
||||
30: "3px",//1 bar
|
||||
0: "0px"//empty
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the value of the property that indicates whether the displayed resolution si the
|
||||
* resolution of High Quality stream or Low Quality
|
||||
* @param value boolean.
|
||||
*/
|
||||
ConnectionIndicator.prototype.setShowHQ = function (value) {
|
||||
this.showHQ = value;
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the html content.
|
||||
* @returns {string} the html content.
|
||||
*/
|
||||
ConnectionIndicator.prototype.generateText = function () {
|
||||
var downloadBitrate, uploadBitrate, packetLoss, resolution;
|
||||
|
||||
if(this.bitrate === null)
|
||||
{
|
||||
downloadBitrate = "N/A";
|
||||
uploadBitrate = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadBitrate = this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
|
||||
uploadBitrate = this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
|
||||
}
|
||||
|
||||
if(this.packetLoss === null)
|
||||
{
|
||||
packetLoss = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
packetLoss = "<span class='jitsipopover_green'>↓</span>" +
|
||||
(this.packetLoss.download != null? this.packetLoss.download : "N/A") +
|
||||
"% <span class='jitsipopover_orange'>↑</span>" +
|
||||
(this.packetLoss.upload != null? this.packetLoss.upload : "N/A") + "%";
|
||||
}
|
||||
|
||||
var resolutionValue = null;
|
||||
if(this.resolution)
|
||||
{
|
||||
if(this.showHQ && this.resolution.hq)
|
||||
{
|
||||
resolutionValue = this.resolution.hq;
|
||||
}
|
||||
else if(!this.showHQ && this.resolution.lq)
|
||||
{
|
||||
resolutionValue = this.resolution.lq;
|
||||
}
|
||||
}
|
||||
|
||||
if(!resolutionValue ||
|
||||
!resolutionValue.height ||
|
||||
!resolutionValue.width)
|
||||
{
|
||||
resolution = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = resolutionValue.width + "x" + resolutionValue.height;
|
||||
}
|
||||
|
||||
var result = "<span class='jitsipopover_blue'>Bitrate:</span> <span class='jitsipopover_green'>↓</span>" +
|
||||
downloadBitrate + " <span class='jitsipopover_orange'>↑</span>" +
|
||||
uploadBitrate + "<br />" +
|
||||
"<span class='jitsipopover_blue'>Packet loss: </span>" + packetLoss + "<br />" +
|
||||
"<span class='jitsipopover_blue'>Resolution:</span> " + resolution + "<br />";
|
||||
|
||||
if(this.videoContainer.id == "localVideoContainer")
|
||||
result += "<div class=\"jitsipopover_showmore\" onclick = \"VideoLayout.connectionIndicators['" +
|
||||
this.videoContainer.id + "'].showMore()\">" + (this.showMoreValue? "Show less" : "Show More") + "</div><br />";
|
||||
|
||||
if(this.showMoreValue)
|
||||
{
|
||||
var downloadBandwidth, uploadBandwidth, transport;
|
||||
if(this.bandwidth === null)
|
||||
{
|
||||
downloadBandwidth = "N/A";
|
||||
uploadBandwidth = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadBandwidth = this.bandwidth.download? this.bandwidth.download + " Kbps" : "N/A";
|
||||
uploadBandwidth = this.bandwidth.upload? this.bandwidth.upload + " Kbps" : "N/A";
|
||||
}
|
||||
|
||||
if(!this.transport || this.transport.length === 0)
|
||||
{
|
||||
transport = "<span class='jitsipopover_blue'>Address:</span> N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
transport = "<span class='jitsipopover_blue'>Address:</span> " + this.transport[0].ip.substring(0,this.transport[0].ip.indexOf(":")) + "<br />";
|
||||
if(this.transport.length > 1)
|
||||
{
|
||||
transport += "<span class='jitsipopover_blue'>Ports:</span> ";
|
||||
}
|
||||
else
|
||||
{
|
||||
transport += "<span class='jitsipopover_blue'>Port:</span> ";
|
||||
}
|
||||
for(var i = 0; i < this.transport.length; i++)
|
||||
{
|
||||
transport += ((i !== 0)? ", " : "") +
|
||||
this.transport[i].ip.substring(this.transport[i].ip.indexOf(":")+1,
|
||||
this.transport[i].ip.length);
|
||||
}
|
||||
transport += "<br /><span class='jitsipopover_blue'>Transport:</span> " + this.transport[0].type + "<br />";
|
||||
}
|
||||
|
||||
result += "<span class='jitsipopover_blue'>Estimated bandwidth:</span> " +
|
||||
"<span class='jitsipopover_green'>↓</span>" + downloadBandwidth +
|
||||
" <span class='jitsipopover_orange'>↑</span>" +
|
||||
uploadBandwidth + "<br />";
|
||||
|
||||
result += transport;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows or hide the additional information.
|
||||
*/
|
||||
ConnectionIndicator.prototype.showMore = function () {
|
||||
this.showMoreValue = !this.showMoreValue;
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.create = function () {
|
||||
this.connectionIndicatorContainer = document.createElement("div");
|
||||
this.connectionIndicatorContainer.className = "connectionindicator";
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
this.videoContainer.appendChild(this.connectionIndicatorContainer);
|
||||
this.popover = new JitsiPopover($("#" + this.videoContainer.id + " > .connectionindicator"),
|
||||
{content: "<div class=\"connection_info\">Come back here for " +
|
||||
"connection information once the conference starts</div>", skin: "black"});
|
||||
|
||||
function createIcon(classes)
|
||||
{
|
||||
var icon = document.createElement("span");
|
||||
for(var i in classes)
|
||||
{
|
||||
icon.classList.add(classes[i]);
|
||||
}
|
||||
icon.appendChild(document.createElement("i")).classList.add("icon-connection");
|
||||
return icon;
|
||||
}
|
||||
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_empty"]));
|
||||
this.fullIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_full"]));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.remove = function()
|
||||
{
|
||||
this.popover.hide();
|
||||
this.connectionIndicatorContainer.remove();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 === null)
|
||||
{
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
for(var quality in ConnectionIndicator.connectionQualityValues)
|
||||
{
|
||||
if(percent >= quality)
|
||||
{
|
||||
this.fullIcon.style.width = ConnectionIndicator.connectionQualityValues[quality];
|
||||
}
|
||||
}
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the resolution
|
||||
* @param resolution the new resolution
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateResolution = function (resolution) {
|
||||
this.resolution = resolution;
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the content of the popover
|
||||
*/
|
||||
ConnectionIndicator.prototype.updatePopoverData = function () {
|
||||
this.popover.updateContent("<div class=\"connection_info\">" + this.generateText() + "</div>");
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the popover
|
||||
*/
|
||||
ConnectionIndicator.prototype.hide = function () {
|
||||
this.popover.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.hideIndicator = function () {
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the data for the indicator
|
||||
* @param id the id of the indicator
|
||||
* @param percent the percent for connection quality
|
||||
* @param object the data
|
||||
*/
|
||||
function updateStatsIndicator(id, percent, object) {
|
||||
if(VideoLayout.connectionIndicators[id])
|
||||
VideoLayout.connectionIndicators[id].updateConnectionQuality(percent, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates local stats
|
||||
* @param percent
|
||||
* @param object
|
||||
*/
|
||||
my.updateLocalConnectionStats = function (percent, object) {
|
||||
var resolution = null;
|
||||
if(object.resolution !== null)
|
||||
{
|
||||
resolution = object.resolution;
|
||||
object.resolution = resolution[connection.emuc.myroomjid];
|
||||
delete resolution[connection.emuc.myroomjid];
|
||||
}
|
||||
updateStatsIndicator("localVideoContainer", percent, object);
|
||||
for(var jid in resolution)
|
||||
{
|
||||
if(resolution[jid] === null)
|
||||
continue;
|
||||
var id = 'participant_' + Strophe.getResourceFromJid(jid);
|
||||
if(VideoLayout.connectionIndicators[id])
|
||||
{
|
||||
VideoLayout.connectionIndicators[id].updateResolution(resolution[jid]);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates remote stats.
|
||||
* @param jid the jid associated with the stats
|
||||
* @param percent the connection quality percent
|
||||
* @param object the stats data
|
||||
*/
|
||||
my.updateConnectionStats = function (jid, percent, object) {
|
||||
var resourceJid = Strophe.getResourceFromJid(jid);
|
||||
|
||||
var videoSpanId = 'participant_' + resourceJid;
|
||||
updateStatsIndicator(videoSpanId, percent, object);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the connection
|
||||
* @param jid
|
||||
*/
|
||||
my.removeConnectionIndicator = function (jid) {
|
||||
if(VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)])
|
||||
VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)].remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the connection indicator
|
||||
* @param jid
|
||||
*/
|
||||
my.hideConnectionIndicator = function (jid) {
|
||||
if(VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)])
|
||||
VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)].hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides all the indicators
|
||||
*/
|
||||
my.onStatsStop = function () {
|
||||
for(var indicator in VideoLayout.connectionIndicators)
|
||||
{
|
||||
VideoLayout.connectionIndicators[indicator].hideIndicator();
|
||||
}
|
||||
};
|
||||
|
||||
return my;
|
||||
}(VideoLayout || {}));
|
||||
|
|
Loading…
Reference in New Issue