Kaynağa Gözat

Implements connection quality indicator.

j8
hristoterezov 10 yıl önce
ebeveyn
işleme
dc60cfc52a
16 değiştirilmiş dosya ile 1212 ekleme ve 174 silme
  1. 4
    1
      app.js
  2. 121
    0
      connectionquality.js
  3. 6
    0
      css/font.css
  4. 103
    0
      css/jitsi_popover.css
  5. 45
    1
      css/videolayout_default.css
  6. BIN
      fonts/jitsi.eot
  7. 1
    0
      fonts/jitsi.svg
  8. BIN
      fonts/jitsi.ttf
  9. BIN
      fonts/jitsi.woff
  10. 118
    48
      fonts/selection.json
  11. 8
    0
      index.html
  12. 115
    0
      jitsipopover.js
  13. 2
    2
      local_sts.js
  14. 23
    0
      muc.js
  15. 276
    121
      rtp_sts.js
  16. 390
    1
      videolayout.js

+ 4
- 1
app.js Dosyayı Görüntüle

@@ -497,7 +497,8 @@ function startRtpStatsCollector()
497 497
     if (config.enableRtpStats)
498 498
     {
499 499
         statsCollector = new StatsCollector(
500
-            getConferenceHandler().peerconnection, 200, audioLevelUpdated);
500
+            getConferenceHandler().peerconnection, 200, audioLevelUpdated, 2000,
501
+            ConnectionQuality.updateLocalStats);
501 502
         statsCollector.start();
502 503
     }
503 504
 }
@@ -511,6 +512,7 @@ function stopRTPStatsCollector()
511 512
     {
512 513
         statsCollector.stop();
513 514
         statsCollector = null;
515
+        ConnectionQuality.stopSendingStats();
514 516
     }
515 517
 }
516 518
 
@@ -728,6 +730,7 @@ $(document).bind('left.muc', function (event, jid) {
728 730
         var container = document.getElementById(
729 731
                 'participant_' + Strophe.getResourceFromJid(jid));
730 732
         if (container) {
733
+            VideoLayout.removeConnectionIndicator(jid);
731 734
             // hide here, wait for video to close before removing
732 735
             $(container).hide();
733 736
             VideoLayout.resizeThumbnails();

+ 121
- 0
connectionquality.js Dosyayı Görüntüle

@@ -0,0 +1,121 @@
1
+var ConnectionQuality = (function () {
2
+
3
+    /**
4
+     * Constructs new ConnectionQuality object
5
+     * @constructor
6
+     */
7
+    function ConnectionQuality() {
8
+
9
+    }
10
+
11
+    /**
12
+     * local stats
13
+     * @type {{}}
14
+     */
15
+    var stats = {};
16
+
17
+    /**
18
+     * remote stats
19
+     * @type {{}}
20
+     */
21
+    var remoteStats = {};
22
+
23
+    /**
24
+     * Interval for sending statistics to other participants
25
+     * @type {null}
26
+     */
27
+    var sendIntervalId = null;
28
+
29
+    /**
30
+     * Updates the local statistics
31
+     * @param data new statistics
32
+     */
33
+    ConnectionQuality.updateLocalStats = function (data) {
34
+        stats = data;
35
+        VideoLayout.updateLocalConnectionStats(100 - stats.packetLoss.total,stats);
36
+        if(sendIntervalId == null)
37
+        {
38
+            startSendingStats();
39
+        }
40
+    };
41
+
42
+    /**
43
+     * Start statistics sending.
44
+     */
45
+    function startSendingStats() {
46
+        sendStats();
47
+        sendIntervalId = setInterval(sendStats, 10000);
48
+    }
49
+
50
+    /**
51
+     * Sends statistics to other participants
52
+     */
53
+    function sendStats() {
54
+        connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
55
+        connection.emuc.sendPresence();
56
+    }
57
+
58
+    /**
59
+     * Converts statistics to format for sending through XMPP
60
+     * @param stats the statistics
61
+     * @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
62
+     */
63
+    function convertToMUCStats(stats) {
64
+        return {
65
+            "bitrate_donwload": stats.bitrate.download,
66
+            "bitrate_uplpoad": stats.bitrate.upload,
67
+            "packetLoss_total": stats.packetLoss.total,
68
+            "packetLoss_download": stats.packetLoss.download,
69
+            "packetLoss_upload": stats.packetLoss.upload
70
+        };
71
+    }
72
+
73
+    /**
74
+     * Converts statitistics to format used by VideoLayout
75
+     * @param stats
76
+     * @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
77
+     */
78
+    function parseMUCStats(stats) {
79
+        return {
80
+            bitrate: {
81
+                download: stats.bitrate_donwload,
82
+                upload: stats.bitrate_uplpoad
83
+            },
84
+            packetLoss: {
85
+                total: stats.packetLoss_total,
86
+                download: stats.packetLoss_download,
87
+                upload: stats.packetLoss_upload
88
+            }
89
+        };
90
+    }
91
+
92
+    /**
93
+     * Updates remote statistics
94
+     * @param jid the jid associated with the statistics
95
+     * @param data the statistics
96
+     */
97
+    ConnectionQuality.updateRemoteStats = function (jid, data) {
98
+        if(data == null || data.packetLoss_total == null)
99
+        {
100
+            VideoLayout.updateConnectionStats(jid, null, null);
101
+            return;
102
+        }
103
+        remoteStats[jid] = parseMUCStats(data);
104
+        console.log(remoteStats[jid]);
105
+
106
+        VideoLayout.updateConnectionStats(jid, 100 - data.packetLoss_total,remoteStats[jid]);
107
+
108
+    };
109
+
110
+    /**
111
+     * Stops statistics sending.
112
+     */
113
+    ConnectionQuality.stopSendingStats = function () {
114
+        clearInterval(sendIntervalId);
115
+        sendIntervalId = null;
116
+        //notify UI about stopping statistics gathering
117
+        VideoLayout.onStatsStop();
118
+    };
119
+
120
+    return ConnectionQuality;
121
+})();

+ 6
- 0
css/font.css Dosyayı Görüntüle

@@ -103,6 +103,12 @@
103 103
 .icon-reload:before {
104 104
     content: "\e618";
105 105
 }
106
+
106 107
 .icon-filmstrip:before {
107 108
     content: "\e619";
109
+}
110
+
111
+.icon-connection:before {
112
+    line-height: normal;
113
+    content: "\e61a";
108 114
 }

+ 103
- 0
css/jitsi_popover.css Dosyayı Görüntüle

@@ -0,0 +1,103 @@
1
+.jitsipopover {
2
+    position: absolute;
3
+    top: 0;
4
+    left: 0;
5
+    z-index: 1010;
6
+    display: none;
7
+    max-width: 300px;
8
+    min-width: 100px;
9
+    padding: 1px;
10
+    text-align: left;
11
+    color: #333333;
12
+    background-color: #ffffff;
13
+    background-clip: padding-box;
14
+    border: 1px solid #cccccc;
15
+    border: 1px solid rgba(0, 0, 0, 0.2);
16
+    border-radius: 6px;
17
+    /*-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);*/
18
+    /*box-shadow: 0 5px 10px rgba(0, 0, 0, 0.4);*/
19
+    white-space: normal;
20
+    margin-top: -10px;
21
+    margin-bottom: 35px;
22
+}
23
+
24
+.jitsipopover.black
25
+{
26
+    background-color: rgba(0,0,0,0.8);
27
+    color: #ffffff;
28
+}
29
+
30
+.jitsipopover-content {
31
+    padding: 9px 14px;
32
+    font-size: 10pt;
33
+    white-space:pre-wrap;
34
+    text-align: center;
35
+}
36
+
37
+.jitsipopover > .arrow,
38
+.jitsipopover > .arrow:after {
39
+    position: absolute;
40
+    display: block;
41
+    width: 0;
42
+    height: 0;
43
+    border-color: transparent;
44
+    border-style: solid;
45
+}
46
+
47
+.jitsipopover > .arrow {
48
+    border-width: 11px;
49
+    left: 50%;
50
+    margin-left: -11px;
51
+    border-bottom-width: 0;
52
+    border-top-color: #999999;
53
+    border-top-color: rgba(0, 0, 0, 0.25);
54
+    bottom: -11px;
55
+}
56
+.jitsipopover > .arrow:after {
57
+    border-width: 10px;
58
+    content: " ";
59
+    bottom: 1px;
60
+    margin-left: -10px;
61
+    border-bottom-width: 0;
62
+    border-top-color: #ffffff;
63
+}
64
+
65
+.jitsipopover.black > .arrow:after
66
+{
67
+    border-top-color: rgba(0, 0, 0, 0.8);
68
+}
69
+
70
+.jitsiPopupmenuPadding {
71
+    height: 35px;
72
+    width: 100px;
73
+    position: absolute;
74
+    bottom: -35;
75
+}
76
+
77
+.jitsipopover_green
78
+{
79
+    color: #4abd04;
80
+}
81
+
82
+.jitsipopover_orange
83
+{
84
+    color: #ffa800;
85
+}
86
+
87
+.jitsipopover_blue
88
+{
89
+    color: #06a5df;
90
+}
91
+
92
+.jitsipopover_showmore
93
+{
94
+    background-color: #06a5df;
95
+    color: #ffffff;
96
+    cursor: pointer;
97
+    border-radius: 3px;
98
+    text-align: center;
99
+    width: 90px;
100
+    height: 16px;
101
+    padding-top: 4px;
102
+    margin: 15px auto 0px auto;
103
+}

+ 45
- 1
css/videolayout_default.css Dosyayı Görüntüle

@@ -197,6 +197,50 @@
197 197
     border-radius:20px;
198 198
 }
199 199
 
200
+.connectionindicator
201
+{
202
+    display: inline-block;
203
+    position: absolute;
204
+    top: 7px;
205
+    right: 0;
206
+    padding: 0px 5px;
207
+    z-index: 3;
208
+    width: 18px;
209
+    height: 13px;
210
+}
211
+
212
+.connection.connection_empty
213
+{
214
+    color: #8B8B8B;/*#FFFFFF*/
215
+    overflow: hidden;
216
+}
217
+
218
+.connection.connection_full
219
+{
220
+    color: #FFFFFF;/*#15A1ED*/
221
+    overflow: hidden;
222
+}
223
+
224
+.connection
225
+{
226
+    position: absolute;
227
+    left: 0px;
228
+    top: 0px;
229
+    font-size: 8pt;
230
+    text-shadow: 1px 1px 1px rgba(0,0,0,1), -1px -1px -1px rgba(0,0,0,1);
231
+    border: 0px;
232
+    width: 18px;
233
+    height: 13px;
234
+}
235
+
236
+.connection_info
237
+{
238
+    text-align: left;
239
+    font-size: 11px;
240
+    white-space:nowrap;
241
+    /*width: 260px;*/
242
+}
243
+
200 244
 #localVideoContainer>span.status:hover,
201 245
 #localVideoContainer>span.displayname:hover {
202 246
     cursor: text;
@@ -233,7 +277,7 @@
233 277
     position: absolute;
234 278
     color: #FFFFFF;
235 279
     top: 0;
236
-    right: 0;
280
+    right: 23px;
237 281
     padding: 8px 5px;
238 282
     width: 25px;
239 283
     font-size: 8pt;

BIN
fonts/jitsi.eot Dosyayı Görüntüle


+ 1
- 0
fonts/jitsi.svg Dosyayı Görüntüle

@@ -33,4 +33,5 @@
33 33
 <glyph unicode="&#xe617;" 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" />
34 34
 <glyph unicode="&#xe618;" 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" />
35 35
 <glyph unicode="&#xe619;" 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" />
36
+<glyph unicode="&#xe61a;" 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" />
36 37
 </font></defs></svg>

BIN
fonts/jitsi.ttf Dosyayı Görüntüle


BIN
fonts/jitsi.woff Dosyayı Görüntüle


+ 118
- 48
fonts/selection.json Dosyayı Görüntüle

@@ -1,6 +1,76 @@
1 1
 {
2 2
 	"IcoMoonType": "selection",
3 3
 	"icons": [
4
+		{
5
+			"icon": {
6
+				"paths": [
7
+					"M3.881 813.165h220.26v210.835h-220.26v-210.835z",
8
+					"M308.817 609.857h220.27v414.143h-220.27v-414.143z",
9
+					"M613.764 406.588h220.268v617.412h-220.268v-617.412z",
10
+					"M918.685 203.285h220.265v820.715h-220.265v-820.715z",
11
+					"M1223.629 0h220.263v1024h-220.263v-1024z"
12
+				],
13
+				"attrs": [
14
+					{
15
+						"opacity": 1,
16
+						"visibility": false
17
+					},
18
+					{
19
+						"opacity": 1,
20
+						"visibility": false
21
+					},
22
+					{
23
+						"opacity": 1,
24
+						"visibility": false
25
+					},
26
+					{
27
+						"opacity": 1,
28
+						"visibility": false
29
+					},
30
+					{
31
+						"opacity": 1,
32
+						"visibility": false
33
+					}
34
+				],
35
+				"width": 1444,
36
+				"grid": 0,
37
+				"tags": [
38
+					"connection-2"
39
+				]
40
+			},
41
+			"attrs": [
42
+				{
43
+					"opacity": 1,
44
+					"visibility": false
45
+				},
46
+				{
47
+					"opacity": 1,
48
+					"visibility": false
49
+				},
50
+				{
51
+					"opacity": 1,
52
+					"visibility": false
53
+				},
54
+				{
55
+					"opacity": 1,
56
+					"visibility": false
57
+				},
58
+				{
59
+					"opacity": 1,
60
+					"visibility": false
61
+				}
62
+			],
63
+			"properties": {
64
+				"order": 27,
65
+				"id": 31,
66
+				"prevSize": 32,
67
+				"code": 58906,
68
+				"name": "connection",
69
+				"ligatures": ""
70
+			},
71
+			"setIdx": 0,
72
+			"iconIdx": 0
73
+		},
4 74
 		{
5 75
 			"icon": {
6 76
 				"paths": [
@@ -13,7 +83,7 @@
13 83
 				"grid": 0
14 84
 			},
15 85
 			"properties": {
16
-				"order": 26,
86
+				"order": 25,
17 87
 				"id": 29,
18 88
 				"prevSize": 32,
19 89
 				"code": 58905,
@@ -21,7 +91,7 @@
21 91
 				"ligatures": ""
22 92
 			},
23 93
 			"setIdx": 0,
24
-			"iconIdx": 0
94
+			"iconIdx": 1
25 95
 		},
26 96
 		{
27 97
 			"icon": {
@@ -37,7 +107,7 @@
37 107
 				"grid": 0
38 108
 			},
39 109
 			"properties": {
40
-				"order": 25,
110
+				"order": 24,
41 111
 				"id": 28,
42 112
 				"prevSize": 32,
43 113
 				"code": 58904,
@@ -45,7 +115,7 @@
45 115
 				"ligatures": ""
46 116
 			},
47 117
 			"setIdx": 0,
48
-			"iconIdx": 1
118
+			"iconIdx": 2
49 119
 		},
50 120
 		{
51 121
 			"icon": {
@@ -59,7 +129,7 @@
59 129
 				"grid": 0
60 130
 			},
61 131
 			"properties": {
62
-				"order": 24,
132
+				"order": 23,
63 133
 				"id": 27,
64 134
 				"prevSize": 32,
65 135
 				"code": 58903,
@@ -67,7 +137,7 @@
67 137
 				"ligatures": ""
68 138
 			},
69 139
 			"setIdx": 0,
70
-			"iconIdx": 2
140
+			"iconIdx": 3
71 141
 		},
72 142
 		{
73 143
 			"icon": {
@@ -81,7 +151,7 @@
81 151
 				"grid": 0
82 152
 			},
83 153
 			"properties": {
84
-				"order": 22,
154
+				"order": 21,
85 155
 				"id": 26,
86 156
 				"prevSize": 32,
87 157
 				"code": 58901,
@@ -89,7 +159,7 @@
89 159
 				"ligatures": ""
90 160
 			},
91 161
 			"setIdx": 0,
92
-			"iconIdx": 3
162
+			"iconIdx": 4
93 163
 		},
94 164
 		{
95 165
 			"icon": {
@@ -104,7 +174,7 @@
104 174
 				"grid": 0
105 175
 			},
106 176
 			"properties": {
107
-				"order": 23,
177
+				"order": 22,
108 178
 				"id": 25,
109 179
 				"prevSize": 32,
110 180
 				"code": 58902,
@@ -112,7 +182,7 @@
112 182
 				"ligatures": ""
113 183
 			},
114 184
 			"setIdx": 0,
115
-			"iconIdx": 4
185
+			"iconIdx": 5
116 186
 		},
117 187
 		{
118 188
 			"icon": {
@@ -127,7 +197,7 @@
127 197
 				"grid": 0
128 198
 			},
129 199
 			"properties": {
130
-				"order": 18,
200
+				"order": 17,
131 201
 				"id": 24,
132 202
 				"prevSize": 32,
133 203
 				"code": 58897,
@@ -135,7 +205,7 @@
135 205
 				"ligatures": ""
136 206
 			},
137 207
 			"setIdx": 0,
138
-			"iconIdx": 5
208
+			"iconIdx": 6
139 209
 		},
140 210
 		{
141 211
 			"icon": {
@@ -150,7 +220,7 @@
150 220
 				"grid": 0
151 221
 			},
152 222
 			"properties": {
153
-				"order": 19,
223
+				"order": 18,
154 224
 				"id": 23,
155 225
 				"prevSize": 32,
156 226
 				"code": 58898,
@@ -158,7 +228,7 @@
158 228
 				"ligatures": ""
159 229
 			},
160 230
 			"setIdx": 0,
161
-			"iconIdx": 6
231
+			"iconIdx": 7
162 232
 		},
163 233
 		{
164 234
 			"icon": {
@@ -174,7 +244,7 @@
174 244
 				"grid": 0
175 245
 			},
176 246
 			"properties": {
177
-				"order": 20,
247
+				"order": 19,
178 248
 				"id": 22,
179 249
 				"prevSize": 32,
180 250
 				"code": 58899,
@@ -182,7 +252,7 @@
182 252
 				"ligatures": ""
183 253
 			},
184 254
 			"setIdx": 0,
185
-			"iconIdx": 7
255
+			"iconIdx": 8
186 256
 		},
187 257
 		{
188 258
 			"icon": {
@@ -199,7 +269,7 @@
199 269
 				"grid": 0
200 270
 			},
201 271
 			"properties": {
202
-				"order": 21,
272
+				"order": 20,
203 273
 				"id": 21,
204 274
 				"prevSize": 32,
205 275
 				"code": 58900,
@@ -207,7 +277,7 @@
207 277
 				"ligatures": ""
208 278
 			},
209 279
 			"setIdx": 0,
210
-			"iconIdx": 8
280
+			"iconIdx": 9
211 281
 		},
212 282
 		{
213 283
 			"icon": {
@@ -222,7 +292,7 @@
222 292
 				"grid": 0
223 293
 			},
224 294
 			"properties": {
225
-				"order": 17,
295
+				"order": 16,
226 296
 				"id": 20,
227 297
 				"prevSize": 32,
228 298
 				"code": 58895,
@@ -230,7 +300,7 @@
230 300
 				"ligatures": ""
231 301
 			},
232 302
 			"setIdx": 0,
233
-			"iconIdx": 9
303
+			"iconIdx": 10
234 304
 		},
235 305
 		{
236 306
 			"icon": {
@@ -246,7 +316,7 @@
246 316
 				"grid": 0
247 317
 			},
248 318
 			"properties": {
249
-				"order": 16,
319
+				"order": 15,
250 320
 				"id": 19,
251 321
 				"prevSize": 32,
252 322
 				"code": 58896,
@@ -254,7 +324,7 @@
254 324
 				"ligatures": ""
255 325
 			},
256 326
 			"setIdx": 0,
257
-			"iconIdx": 10
327
+			"iconIdx": 11
258 328
 		},
259 329
 		{
260 330
 			"icon": {
@@ -270,7 +340,7 @@
270 340
 				"grid": 0
271 341
 			},
272 342
 			"properties": {
273
-				"order": 15,
343
+				"order": 14,
274 344
 				"id": 18,
275 345
 				"prevSize": 32,
276 346
 				"code": 58882,
@@ -278,7 +348,7 @@
278 348
 				"ligatures": ""
279 349
 			},
280 350
 			"setIdx": 0,
281
-			"iconIdx": 11
351
+			"iconIdx": 12
282 352
 		},
283 353
 		{
284 354
 			"icon": {
@@ -292,7 +362,7 @@
292 362
 				"grid": 0
293 363
 			},
294 364
 			"properties": {
295
-				"order": 14,
365
+				"order": 13,
296 366
 				"id": 17,
297 367
 				"prevSize": 32,
298 368
 				"code": 58886,
@@ -300,7 +370,7 @@
300 370
 				"ligatures": ""
301 371
 			},
302 372
 			"setIdx": 0,
303
-			"iconIdx": 12
373
+			"iconIdx": 13
304 374
 		},
305 375
 		{
306 376
 			"icon": {
@@ -316,7 +386,7 @@
316 386
 				"grid": 0
317 387
 			},
318 388
 			"properties": {
319
-				"order": 13,
389
+				"order": 12,
320 390
 				"id": 16,
321 391
 				"prevSize": 32,
322 392
 				"code": 58893,
@@ -324,7 +394,7 @@
324 394
 				"ligatures": ""
325 395
 			},
326 396
 			"setIdx": 0,
327
-			"iconIdx": 13
397
+			"iconIdx": 14
328 398
 		},
329 399
 		{
330 400
 			"icon": {
@@ -340,7 +410,7 @@
340 410
 				"grid": 0
341 411
 			},
342 412
 			"properties": {
343
-				"order": 12,
413
+				"order": 11,
344 414
 				"id": 15,
345 415
 				"prevSize": 32,
346 416
 				"code": 58894,
@@ -348,7 +418,7 @@
348 418
 				"ligatures": ""
349 419
 			},
350 420
 			"setIdx": 0,
351
-			"iconIdx": 14
421
+			"iconIdx": 15
352 422
 		},
353 423
 		{
354 424
 			"icon": {
@@ -367,7 +437,7 @@
367 437
 				"grid": 0
368 438
 			},
369 439
 			"properties": {
370
-				"order": 11,
440
+				"order": 10,
371 441
 				"id": 14,
372 442
 				"prevSize": 32,
373 443
 				"code": 58892,
@@ -375,7 +445,7 @@
375 445
 				"ligatures": ""
376 446
 			},
377 447
 			"setIdx": 0,
378
-			"iconIdx": 15
448
+			"iconIdx": 16
379 449
 		},
380 450
 		{
381 451
 			"icon": {
@@ -410,7 +480,7 @@
410 480
 				}
411 481
 			],
412 482
 			"properties": {
413
-				"order": 27,
483
+				"order": 26,
414 484
 				"id": 30,
415 485
 				"prevSize": 32,
416 486
 				"code": 58880,
@@ -418,7 +488,7 @@
418 488
 				"ligatures": ""
419 489
 			},
420 490
 			"setIdx": 0,
421
-			"iconIdx": 16
491
+			"iconIdx": 17
422 492
 		},
423 493
 		{
424 494
 			"icon": {
@@ -442,7 +512,7 @@
442 512
 				"ligatures": ""
443 513
 			},
444 514
 			"setIdx": 0,
445
-			"iconIdx": 17
515
+			"iconIdx": 18
446 516
 		},
447 517
 		{
448 518
 			"icon": {
@@ -467,7 +537,7 @@
467 537
 				"ligatures": ""
468 538
 			},
469 539
 			"setIdx": 0,
470
-			"iconIdx": 18
540
+			"iconIdx": 19
471 541
 		},
472 542
 		{
473 543
 			"icon": {
@@ -489,7 +559,7 @@
489 559
 				"ligatures": ""
490 560
 			},
491 561
 			"setIdx": 0,
492
-			"iconIdx": 19
562
+			"iconIdx": 20
493 563
 		},
494 564
 		{
495 565
 			"icon": {
@@ -512,7 +582,7 @@
512 582
 				"ligatures": ""
513 583
 			},
514 584
 			"setIdx": 0,
515
-			"iconIdx": 20
585
+			"iconIdx": 21
516 586
 		},
517 587
 		{
518 588
 			"icon": {
@@ -526,7 +596,7 @@
526 596
 				"grid": 0
527 597
 			},
528 598
 			"properties": {
529
-				"order": 6,
599
+				"order": 5,
530 600
 				"id": 5,
531 601
 				"prevSize": 32,
532 602
 				"code": 58887,
@@ -534,7 +604,7 @@
534 604
 				"ligatures": ""
535 605
 			},
536 606
 			"setIdx": 0,
537
-			"iconIdx": 21
607
+			"iconIdx": 22
538 608
 		},
539 609
 		{
540 610
 			"icon": {
@@ -549,7 +619,7 @@
549 619
 				"grid": 0
550 620
 			},
551 621
 			"properties": {
552
-				"order": 7,
622
+				"order": 6,
553 623
 				"id": 4,
554 624
 				"prevSize": 32,
555 625
 				"code": 58888,
@@ -557,7 +627,7 @@
557 627
 				"ligatures": ""
558 628
 			},
559 629
 			"setIdx": 0,
560
-			"iconIdx": 22
630
+			"iconIdx": 23
561 631
 		},
562 632
 		{
563 633
 			"icon": {
@@ -572,7 +642,7 @@
572 642
 				"grid": 0
573 643
 			},
574 644
 			"properties": {
575
-				"order": 8,
645
+				"order": 7,
576 646
 				"id": 3,
577 647
 				"prevSize": 32,
578 648
 				"code": 58889,
@@ -580,7 +650,7 @@
580 650
 				"ligatures": ""
581 651
 			},
582 652
 			"setIdx": 0,
583
-			"iconIdx": 23
653
+			"iconIdx": 24
584 654
 		},
585 655
 		{
586 656
 			"icon": {
@@ -596,7 +666,7 @@
596 666
 				"grid": 0
597 667
 			},
598 668
 			"properties": {
599
-				"order": 9,
669
+				"order": 8,
600 670
 				"id": 2,
601 671
 				"prevSize": 32,
602 672
 				"code": 58890,
@@ -604,7 +674,7 @@
604 674
 				"ligatures": ""
605 675
 			},
606 676
 			"setIdx": 0,
607
-			"iconIdx": 24
677
+			"iconIdx": 25
608 678
 		},
609 679
 		{
610 680
 			"icon": {
@@ -619,7 +689,7 @@
619 689
 				"grid": 0
620 690
 			},
621 691
 			"properties": {
622
-				"order": 10,
692
+				"order": 9,
623 693
 				"id": 1,
624 694
 				"prevSize": 32,
625 695
 				"code": 58891,
@@ -627,7 +697,7 @@
627 697
 				"ligatures": ""
628 698
 			},
629 699
 			"setIdx": 0,
630
-			"iconIdx": 25
700
+			"iconIdx": 26
631 701
 		}
632 702
 	],
633 703
 	"height": 1024,

+ 8
- 0
index.html Dosyayı Görüntüle

@@ -48,6 +48,7 @@
48 48
     <script src="rtp_sts.js?v=1"></script><!-- RTP stats processing -->
49 49
     <script src="local_sts.js?v=1"></script><!-- Local stats processing -->
50 50
     <script src="videolayout.js?v=15"></script><!-- video ui -->
51
+    <script src="connectionquality.js?v=1"></script>
51 52
     <script src="toolbar.js?v=6"></script><!-- toolbar ui -->
52 53
     <script src="toolbar_toggler.js?v=2"></script>
53 54
     <script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
@@ -57,6 +58,7 @@
57 58
     <script src="roomname_generator.js?v=1"></script><!-- generator for random room names -->
58 59
     <script src="keyboard_shortcut.js?v=2"></script>
59 60
     <script src="tracking.js?v=1"></script><!-- tracking -->
61
+    <script src="jitsipopover.js?v=1"></script>
60 62
     <script src="message_handler.js?v=1"></script>
61 63
     <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
62 64
     <link rel="stylesheet" href="css/font.css?v=4"/>
@@ -66,6 +68,7 @@
66 68
     <link rel="stylesheet" href="css/modaldialog.css?v=3">
67 69
     <link rel="stylesheet" href="css/popup_menu.css?v=4">
68 70
     <link rel="stylesheet" href="css/popover.css?v=2">
71
+      <link rel="stylesheet" href="css/jitsi_popover.css?v=2">
69 72
     <link rel="stylesheet" href="css/contact_list.css?v=3">
70 73
     <link rel="stylesheet" href="css/welcome_page.css?v=2">
71 74
     <!--
@@ -256,6 +259,11 @@
256 259
                     </span>
257 260
                     <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
258 261
                     <span class="focusindicator"></span>
262
+                    <!--<div class="connectionindicator">
263
+                        <span class="connection connection_empty"><i class="icon-connection"></i></span>
264
+                        <span class="connection connection_full"><i class="icon-connection"></i></span>
265
+                    </div>-->
266
+
259 267
                 </span>
260 268
                 <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
261 269
                 <audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>

+ 115
- 0
jitsipopover.js Dosyayı Görüntüle

@@ -0,0 +1,115 @@
1
+var JitsiPopover = (function () {
2
+    /**
3
+     * Constructs new JitsiPopover and attaches it to the element
4
+     * @param element jquery selector
5
+     * @param options the options for the popover.
6
+     * @constructor
7
+     */
8
+    function JitsiPopover(element, options)
9
+    {
10
+        this.options = {
11
+            skin: "white",
12
+            content: ""
13
+        };
14
+        if(options)
15
+        {
16
+            if(options.skin)
17
+                this.options.skin = options.skin;
18
+
19
+            if(options.content)
20
+                this.options.content = options.content;
21
+        }
22
+
23
+        this.elementIsHovered = false;
24
+        this.popoverIsHovered = false;
25
+        this.popoverShown = false;
26
+
27
+        element.data("jitsi_popover", this);
28
+        this.element = element;
29
+        this.template = ' <div class="jitsipopover ' + this.options.skin +
30
+            '"><div class="arrow"></div><div class="jitsipopover-content"></div>' +
31
+            '<div class="jitsiPopupmenuPadding"></div></div>';
32
+        var self = this;
33
+        this.element.on("mouseenter", function () {
34
+            self.elementIsHovered = true;
35
+            self.show();
36
+        }).on("mouseleave", function () {
37
+            self.elementIsHovered = false;
38
+            setTimeout(function () {
39
+                self.hide();
40
+            }, 10);
41
+        });
42
+    }
43
+
44
+    /**
45
+     * Shows the popover
46
+     */
47
+    JitsiPopover.prototype.show = function () {
48
+        this.createPopover();
49
+        this.popoverShown = true;
50
+
51
+    };
52
+
53
+    /**
54
+     * Hides the popover
55
+     */
56
+    JitsiPopover.prototype.hide = function () {
57
+        if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
58
+        {
59
+            $(".jitsipopover").remove();
60
+            this.popoverShown = false;
61
+
62
+        }
63
+    };
64
+
65
+    /**
66
+     * Creates the popover html
67
+     */
68
+    JitsiPopover.prototype.createPopover = function () {
69
+        $("body").append(this.template);
70
+        $(".jitsipopover > .jitsipopover-content").html(this.options.content);
71
+        var self = this;
72
+        $(".jitsipopover").on("mouseenter", function () {
73
+            self.popoverIsHovered = true;
74
+        }).on("mouseleave", function () {
75
+            self.popoverIsHovered = false;
76
+            self.hide();
77
+        });
78
+
79
+        this.refreshPosition();
80
+    };
81
+
82
+    /**
83
+     * Refreshes the position of the popover
84
+     */
85
+    JitsiPopover.prototype.refreshPosition = function () {
86
+        $(".jitsipopover").position({
87
+            my: "bottom",
88
+            at: "top",
89
+            collision: "fit",
90
+            of: this.element,
91
+            using: function (position, elements) {
92
+                var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
93
+                $(".jitsipopover").css({top: position.top, left: position.left, display: "block"});
94
+                $(".jitsipopover > .arrow").css({left: calcLeft});
95
+                $(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
96
+            }
97
+        });
98
+    };
99
+
100
+    /**
101
+     * Updates the content of popover.
102
+     * @param content new content
103
+     */
104
+    JitsiPopover.prototype.updateContent = function (content) {
105
+        this.options.content = content;
106
+        if(!this.popoverShown)
107
+            return;
108
+        $(".jitsipopover").remove();
109
+        this.createPopover();
110
+    };
111
+
112
+    return JitsiPopover;
113
+
114
+
115
+})();

+ 2
- 2
local_sts.js Dosyayı Görüntüle

@@ -28,7 +28,7 @@ var LocalStatsCollector = (function() {
28 28
         this.stream = stream;
29 29
         this.intervalId = null;
30 30
         this.intervalMilis = interval;
31
-        this.updateCallback = updateCallback;
31
+        this.audioLevelsUpdateCallback = updateCallback;
32 32
         this.audioLevel = 0;
33 33
     }
34 34
 
@@ -58,7 +58,7 @@ var LocalStatsCollector = (function() {
58 58
                 var audioLevel = TimeDomainDataToAudioLevel(array);
59 59
                 if(audioLevel != self.audioLevel) {
60 60
                     self.audioLevel = animateLevel(audioLevel, self.audioLevel);
61
-                    self.updateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
61
+                    self.audioLevelsUpdateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
62 62
                 }
63 63
             },
64 64
             this.intervalMilis

+ 23
- 0
muc.js Dosyayı Görüntüle

@@ -94,6 +94,16 @@ Strophe.addConnectionPlugin('emuc', {
94 94
             $(document).trigger('videomuted.muc', [from, videoMuted.text()]);
95 95
         }
96 96
 
97
+        var stats = $(pres).find('>stats');
98
+        if(stats.length)
99
+        {
100
+            var statsObj = {};
101
+            Strophe.forEachChild(stats[0], "stat", function (el) {
102
+                statsObj[el.getAttribute("name")] = el.getAttribute("value");
103
+            });
104
+            ConnectionQuality.updateRemoteStats(from, statsObj);
105
+        }
106
+
97 107
         // Parse status.
98 108
         if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
99 109
             // http://xmpp.org/extensions/xep-0045.html#createroom-instant
@@ -319,6 +329,15 @@ Strophe.addConnectionPlugin('emuc', {
319 329
                 .t(this.presMap['videomuted']).up();
320 330
         }
321 331
 
332
+        if(this.presMap['statsns'])
333
+        {
334
+            var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
335
+            for(var stat in this.presMap["stats"])
336
+                if(this.presMap["stats"][stat] != null)
337
+                    stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up();
338
+            pres.up();
339
+        }
340
+
322 341
         if (this.presMap['prezins']) {
323 342
             pres.c('prezi',
324 343
                     {xmlns: this.presMap['prezins'],
@@ -401,6 +420,10 @@ Strophe.addConnectionPlugin('emuc', {
401 420
         this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
402 421
         this.presMap['videomuted'] = isMuted.toString();
403 422
     },
423
+    addConnectionInfoToPresence: function(stats) {
424
+        this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
425
+        this.presMap['stats'] = stats;
426
+    },
404 427
     findJidFromResource: function(resourceJid) {
405 428
         var peerJid = null;
406 429
         Object.keys(this.members).some(function (jid) {

+ 276
- 121
rtp_sts.js Dosyayı Görüntüle

@@ -1,34 +1,12 @@
1 1
 /* global ssrc2jid */
2
-
3 2
 /**
4
- * Function object which once created can be used to calculate moving average of
5
- * given period. Example for SMA3:</br>
6
- * var sma3 = new SimpleMovingAverager(3);
7
- * while(true) // some update loop
8
- * {
9
- *   var currentSma3Value = sma3(nextInputValue);
10
- * }
11
- *
12
- * @param period moving average period that will be used by created instance.
13
- * @returns {Function} SMA calculator function of given <tt>period</tt>.
14
- * @constructor
3
+ * Calculates packet lost percent using the number of lost packets and the number of all packet.
4
+ * @param lostPackets the number of lost packets
5
+ * @param totalPackets the number of all packets.
6
+ * @returns {number} packet loss percent
15 7
  */
16
-function SimpleMovingAverager(period)
17
-{
18
-    var nums = [];
19
-    return function (num)
20
-    {
21
-        nums.push(num);
22
-        if (nums.length > period)
23
-            nums.splice(0, 1);
24
-        var sum = 0;
25
-        for (var i in nums)
26
-            sum += nums[i];
27
-        var n = period;
28
-        if (nums.length < period)
29
-            n = nums.length;
30
-        return (sum / n);
31
-    };
8
+function calculatePacketLoss(lostPackets, totalPackets) {
9
+    return Math.round((lostPackets/totalPackets)*100);
32 10
 }
33 11
 
34 12
 /**
@@ -39,8 +17,30 @@ function PeerStats()
39 17
 {
40 18
     this.ssrc2Loss = {};
41 19
     this.ssrc2AudioLevel = {};
20
+    this.ssrc2bitrate = {};
21
+    this.resolution = null;
42 22
 }
43 23
 
24
+/**
25
+ * The bandwidth
26
+ * @type {{}}
27
+ */
28
+PeerStats.bandwidth = {};
29
+
30
+/**
31
+ * The bit rate
32
+ * @type {{}}
33
+ */
34
+PeerStats.bitrate = {};
35
+
36
+
37
+
38
+/**
39
+ * The packet loss rate
40
+ * @type {{}}
41
+ */
42
+PeerStats.packetLoss = null;
43
+
44 44
 /**
45 45
  * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
46 46
  * represented by this instance.
@@ -52,6 +52,17 @@ PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
52 52
     this.ssrc2Loss[ssrc] = lossRate;
53 53
 };
54 54
 
55
+/**
56
+ * Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
57
+ * represented by this instance.
58
+ * @param ssrc audio or video RTP stream SSRC.
59
+ * @param bitrate new bitrate value to be set.
60
+ */
61
+PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
62
+{
63
+    this.ssrc2bitrate[ssrc] = bitrate;
64
+};
65
+
55 66
 /**
56 67
  * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
57 68
  * the stream which belongs to the peer represented by this instance.
@@ -67,52 +78,42 @@ PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
67 78
 };
68 79
 
69 80
 /**
70
- * Calculates average packet loss for all streams that belong to the peer
71
- * represented by this instance.
72
- * @returns {number} average packet loss for all streams that belong to the peer
73
- *                   represented by this instance.
81
+ * Array with the transport information.
82
+ * @type {Array}
74 83
  */
75
-PeerStats.prototype.getAvgLoss = function ()
76
-{
77
-    var self = this;
78
-    var avg = 0;
79
-    var count = Object.keys(this.ssrc2Loss).length;
80
-    Object.keys(this.ssrc2Loss).forEach(
81
-        function (ssrc)
82
-        {
83
-            avg += self.ssrc2Loss[ssrc];
84
-        }
85
-    );
86
-    return count > 0 ? avg / count : 0;
87
-};
84
+PeerStats.transport = [];
88 85
 
89 86
 /**
90 87
  * <tt>StatsCollector</tt> registers for stats updates of given
91 88
  * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
92 89
  * stats are extracted and put in {@link PeerStats} objects. Once the processing
93
- * is done <tt>updateCallback</tt> is called with <tt>this</tt> instance as
90
+ * is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt> instance as
94 91
  * an event source.
95 92
  *
96 93
  * @param peerconnection webRTC peer connection object.
97 94
  * @param interval stats refresh interval given in ms.
98
- * @param {function(StatsCollector)} updateCallback the callback called on stats
95
+ * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback called on stats
99 96
  *                                   update.
100 97
  * @constructor
101 98
  */
102
-function StatsCollector(peerconnection, interval, updateCallback)
99
+function StatsCollector(peerconnection, audioLevelsInterval, audioLevelsUpdateCallback, statsInterval, statsUpdateCallback)
103 100
 {
104 101
     this.peerconnection = peerconnection;
105
-    this.baselineReport = null;
106
-    this.currentReport = null;
107
-    this.intervalId = null;
102
+    this.baselineAudioLevelsReport = null;
103
+    this.currentAudioLevelsReport = null;
104
+    this.currentStatsReport = null;
105
+    this.baselineStatsReport = null;
106
+    this.audioLevelsIntervalId = null;
108 107
     // Updates stats interval
109
-    this.intervalMilis = interval;
110
-    // Use SMA 3 to average packet loss changes over time
111
-    this.sma3 = new SimpleMovingAverager(3);
108
+    this.audioLevelsIntervalMilis = audioLevelsInterval;
109
+
110
+    this.statsIntervalId = null;
111
+    this.statsIntervalMilis = statsInterval;
112 112
     // Map of jids to PeerStats
113 113
     this.jid2stats = {};
114 114
 
115
-    this.updateCallback = updateCallback;
115
+    this.audioLevelsUpdateCallback = audioLevelsUpdateCallback;
116
+    this.statsUpdateCallback = statsUpdateCallback;
116 117
 }
117 118
 
118 119
 /**
@@ -120,10 +121,12 @@ function StatsCollector(peerconnection, interval, updateCallback)
120 121
  */
121 122
 StatsCollector.prototype.stop = function ()
122 123
 {
123
-    if (this.intervalId)
124
+    if (this.audioLevelsIntervalId)
124 125
     {
125
-        clearInterval(this.intervalId);
126
-        this.intervalId = null;
126
+        clearInterval(this.audioLevelsIntervalId);
127
+        this.audioLevelsIntervalId = null;
128
+        clearInterval(this.statsIntervalId);
129
+        this.statsIntervalId = null;
127 130
     }
128 131
 };
129 132
 
@@ -143,7 +146,7 @@ StatsCollector.prototype.errorCallback = function (error)
143 146
 StatsCollector.prototype.start = function ()
144 147
 {
145 148
     var self = this;
146
-    this.intervalId = setInterval(
149
+    this.audioLevelsIntervalId = setInterval(
147 150
         function ()
148 151
         {
149 152
             // Interval updates
@@ -152,74 +155,103 @@ StatsCollector.prototype.start = function ()
152 155
                 {
153 156
                     var results = report.result();
154 157
                     //console.error("Got interval report", results);
155
-                    self.currentReport = results;
156
-                    self.processReport();
157
-                    self.baselineReport = self.currentReport;
158
+                    self.currentAudioLevelsReport = results;
159
+                    self.processAudioLevelReport();
160
+                    self.baselineAudioLevelsReport = self.currentAudioLevelsReport;
161
+                },
162
+                self.errorCallback
163
+            );
164
+        },
165
+        self.audioLevelsIntervalMilis
166
+    );
167
+
168
+    this.statsIntervalId = setInterval(
169
+        function () {
170
+            // Interval updates
171
+            self.peerconnection.getStats(
172
+                function (report)
173
+                {
174
+                    var results = report.result();
175
+                    //console.error("Got interval report", results);
176
+                    self.currentStatsReport = results;
177
+                    self.processStatsReport();
178
+                    self.baselineStatsReport = self.currentStatsReport;
158 179
                 },
159 180
                 self.errorCallback
160 181
             );
161 182
         },
162
-        self.intervalMilis
183
+        self.statsIntervalMilis
163 184
     );
164 185
 };
165 186
 
187
+
166 188
 /**
167 189
  * Stats processing logic.
168 190
  */
169
-StatsCollector.prototype.processReport = function ()
170
-{
171
-    if (!this.baselineReport)
172
-    {
191
+StatsCollector.prototype.processStatsReport = function () {
192
+    if (!this.baselineStatsReport) {
173 193
         return;
174 194
     }
175 195
 
176
-    for (var idx in this.currentReport)
177
-    {
178
-        var now = this.currentReport[idx];
179
-        if (now.type != 'ssrc')
196
+    for (var idx in this.currentStatsReport) {
197
+        var now = this.currentStatsReport[idx];
198
+        if (now.stat('googAvailableReceiveBandwidth') || now.stat('googAvailableSendBandwidth')) {
199
+            PeerStats.bandwidth = {
200
+                "download": Math.round((now.stat('googAvailableReceiveBandwidth') * 8) / 1000),
201
+                "upload": Math.round((now.stat('googAvailableSendBandwidth') * 8) / 1000)
202
+            };
203
+        }
204
+
205
+        if(now.type == 'googCandidatePair')
180 206
         {
207
+            var ip = now.stat('googRemoteAddress');
208
+            var type = now.stat("googTransportType");
209
+            if(!ip || !type)
210
+                continue;
211
+            var addressSaved = false;
212
+            for(var i = 0; i < PeerStats.transport.length; i++)
213
+            {
214
+                if(PeerStats.transport[i].ip == ip && PeerStats.transport[i].type == type)
215
+                {
216
+                    addressSaved = true;
217
+                }
218
+            }
219
+            if(addressSaved)
220
+                continue;
221
+            PeerStats.transport.push({ip: ip, type: type});
181 222
             continue;
182 223
         }
183 224
 
184
-        var before = this.baselineReport[idx];
185
-        if (!before)
186
-        {
225
+//            console.log("bandwidth: " + now.stat('googAvailableReceiveBandwidth') + " - " + now.stat('googAvailableSendBandwidth'));
226
+        if (now.type != 'ssrc') {
227
+            continue;
228
+        }
229
+
230
+        var before = this.baselineStatsReport[idx];
231
+        if (!before) {
187 232
             console.warn(now.stat('ssrc') + ' not enough data');
188 233
             continue;
189 234
         }
190 235
 
191 236
         var ssrc = now.stat('ssrc');
192 237
         var jid = ssrc2jid[ssrc];
193
-        if (!jid)
194
-        {
238
+        if (!jid) {
195 239
             console.warn("No jid for ssrc: " + ssrc);
196 240
             continue;
197 241
         }
198 242
 
199 243
         var jidStats = this.jid2stats[jid];
200
-        if (!jidStats)
201
-        {
244
+        if (!jidStats) {
202 245
             jidStats = new PeerStats();
203 246
             this.jid2stats[jid] = jidStats;
204 247
         }
205 248
 
206
-        // Audio level
207
-        var audioLevel = now.stat('audioInputLevel');
208
-        if (!audioLevel)
209
-            audioLevel = now.stat('audioOutputLevel');
210
-        if (audioLevel)
211
-        {
212
-            // TODO: can't find specs about what this value really is,
213
-            // but it seems to vary between 0 and around 32k.
214
-            audioLevel = audioLevel / 32767;
215
-            jidStats.setSsrcAudioLevel(ssrc, audioLevel);
216
-            if(jid != connection.emuc.myroomjid)
217
-                this.updateCallback(jid, audioLevel);
218
-        }
219 249
 
250
+        var isDownloadStream = true;
220 251
         var key = 'packetsReceived';
221 252
         if (!now.stat(key))
222 253
         {
254
+            isDownloadStream = false;
223 255
             key = 'packetsSent';
224 256
             if (!now.stat(key))
225 257
             {
@@ -237,51 +269,174 @@ StatsCollector.prototype.processReport = function ()
237 269
         var lossRate = currentLoss - previousLoss;
238 270
 
239 271
         var packetsTotal = (packetRate + lossRate);
240
-        var lossPercent;
241 272
 
242
-        if (packetsTotal > 0)
243
-            lossPercent = lossRate / packetsTotal;
244
-        else
245
-            lossPercent = 0;
273
+        jidStats.setSsrcLoss(ssrc, {"packetsTotal": packetsTotal, "packetsLost": lossRate,
274
+            "isDownloadStream": isDownloadStream});
275
+
276
+        var bytesReceived = 0, bytesSent = 0;
277
+        if(now.stat("bytesReceived"))
278
+        {
279
+            bytesReceived = now.stat("bytesReceived") - before.stat("bytesReceived");
280
+        }
281
+
282
+        if(now.stat("bytesSent"))
283
+        {
284
+            bytesSent = now.stat("bytesSent") - before.stat("bytesSent");
285
+        }
286
+
287
+        if(bytesReceived < 0)
288
+            bytesReceived = 0;
289
+        if(bytesSent < 0)
290
+            bytesSent = 0;
291
+
292
+        var time = Math.round((now.timestamp - before.timestamp) / 1000);
293
+        jidStats.setSsrcBitrate(ssrc, {
294
+            "download": Math.round(((bytesReceived * 8) / time) / 1000),
295
+            "upload": Math.round(((bytesSent * 8) / time) / 1000)});
296
+        var resolution = {height: null, width: null};
297
+        if(now.stat("googFrameHeightReceived") && now.stat("googFrameWidthReceived"))
298
+        {
299
+            resolution.height = now.stat("googFrameHeightReceived");
300
+            resolution.width = now.stat("googFrameWidthReceived");
301
+        }
302
+        else if(now.stat("googFrameHeightSent") && now.stat("googFrameWidthSent"))
303
+        {
304
+            resolution.height = now.stat("googFrameHeightSent");
305
+            resolution.width = now.stat("googFrameWidthSent");
306
+        }
307
+
308
+        if(!jidStats.resolution)
309
+            jidStats.resolution = null;
310
+
311
+        console.log(jid + " - resolution: " + resolution.height + "x" + resolution.width);
312
+        if(resolution.height && resolution.width)
313
+        {
314
+            if(!jidStats.resolution)
315
+                jidStats.resolution = { hq: resolution, lq: resolution};
316
+            else if(jidStats.resolution.hq.width > resolution.width &&
317
+                jidStats.resolution.hq.height > resolution.height)
318
+            {
319
+                jidStats.resolution.lq = resolution;
320
+            }
321
+            else
322
+            {
323
+                jidStats.resolution.hq = resolution;
324
+            }
325
+        }
246 326
 
247
-        //console.info(jid + " ssrc: " + ssrc + " " + key + ": " + packetsNow);
248 327
 
249
-        jidStats.setSsrcLoss(ssrc, lossPercent);
250 328
     }
251 329
 
252 330
     var self = this;
253 331
     // Jid stats
254
-    var allPeersAvg = 0;
255
-    var jids = Object.keys(this.jid2stats);
256
-    jids.forEach(
332
+    var totalPackets = {download: 0, upload: 0};
333
+    var lostPackets = {download: 0, upload: 0};
334
+    var bitrateDownload = 0;
335
+    var bitrateUpload = 0;
336
+    var resolution = {};
337
+    Object.keys(this.jid2stats).forEach(
257 338
         function (jid)
258 339
         {
259
-            var peerAvg = self.jid2stats[jid].getAvgLoss(
260
-                function (avg)
340
+            Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
341
+                function (ssrc)
261 342
                 {
262
-                    //console.info(jid + " stats: " + (avg * 100) + " %");
263
-                    allPeersAvg += avg;
343
+                    var type = "upload";
344
+                    if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
345
+                        type = "download";
346
+                    totalPackets[type] += self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
347
+                    lostPackets[type] += self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
348
+                }
349
+            );
350
+            Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
351
+                function (ssrc) {
352
+                    bitrateDownload += self.jid2stats[jid].ssrc2bitrate[ssrc].download;
353
+                    bitrateUpload += self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
264 354
                 }
265 355
             );
356
+            resolution[jid] = self.jid2stats[jid].resolution;
357
+            delete self.jid2stats[jid].resolution;
266 358
         }
267 359
     );
268 360
 
269
-    if (jids.length > 1)
361
+    PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
362
+
363
+    PeerStats.packetLoss = {
364
+        total:
365
+            calculatePacketLoss(lostPackets.download + lostPackets.upload,
366
+                totalPackets.download + totalPackets.upload),
367
+        download:
368
+            calculatePacketLoss(lostPackets.download, totalPackets.download),
369
+        upload:
370
+            calculatePacketLoss(lostPackets.upload, totalPackets.upload)
371
+    };
372
+    this.statsUpdateCallback(
373
+        {
374
+            "bitrate": PeerStats.bitrate,
375
+            "packetLoss": PeerStats.packetLoss,
376
+            "bandwidth": PeerStats.bandwidth,
377
+            "resolution": resolution,
378
+            "transport": PeerStats.transport
379
+        });
380
+    PeerStats.transport = [];
381
+
382
+}
383
+
384
+/**
385
+ * Stats processing logic.
386
+ */
387
+StatsCollector.prototype.processAudioLevelReport = function ()
388
+{
389
+    if (!this.baselineAudioLevelsReport)
270 390
     {
271
-        // Our streams loss is reported as 0 always, so -1 to length
272
-        allPeersAvg = allPeersAvg / (jids.length - 1);
273
-
274
-        /**
275
-         * Calculates number of connection quality bars from 4(hi) to 0(lo).
276
-         */
277
-        var outputAvg = self.sma3(allPeersAvg);
278
-        // Linear from 4(0%) to 0(25%).
279
-        var quality = Math.round(4 - outputAvg * 16);
280
-        quality = Math.max(quality, 0); // lower limit 0
281
-        quality = Math.min(quality, 4); // upper limit 4
282
-        // TODO: quality can be used to indicate connection quality using 4 step
283
-        // bar indicator
284
-        //console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
391
+        return;
285 392
     }
286
-};
287 393
 
394
+    for (var idx in this.currentAudioLevelsReport)
395
+    {
396
+        var now = this.currentAudioLevelsReport[idx];
397
+
398
+        if (now.type != 'ssrc')
399
+        {
400
+            continue;
401
+        }
402
+
403
+        var before = this.baselineAudioLevelsReport[idx];
404
+        if (!before)
405
+        {
406
+            console.warn(now.stat('ssrc') + ' not enough data');
407
+            continue;
408
+        }
409
+
410
+        var ssrc = now.stat('ssrc');
411
+        var jid = ssrc2jid[ssrc];
412
+        if (!jid)
413
+        {
414
+            console.warn("No jid for ssrc: " + ssrc);
415
+            continue;
416
+        }
417
+
418
+        var jidStats = this.jid2stats[jid];
419
+        if (!jidStats)
420
+        {
421
+            jidStats = new PeerStats();
422
+            this.jid2stats[jid] = jidStats;
423
+        }
424
+
425
+        // Audio level
426
+        var audioLevel = now.stat('audioInputLevel');
427
+        if (!audioLevel)
428
+            audioLevel = now.stat('audioOutputLevel');
429
+        if (audioLevel)
430
+        {
431
+            // TODO: can't find specs about what this value really is,
432
+            // but it seems to vary between 0 and around 32k.
433
+            audioLevel = audioLevel / 32767;
434
+            jidStats.setSsrcAudioLevel(ssrc, audioLevel);
435
+            if(jid != connection.emuc.myroomjid)
436
+                this.audioLevelsUpdateCallback(jid, audioLevel);
437
+        }
438
+
439
+    }
440
+
441
+
442
+};

+ 390
- 1
videolayout.js Dosyayı Görüntüle

@@ -6,6 +6,7 @@ var VideoLayout = (function (my) {
6 6
         updateInProgress: false,
7 7
         newSrc: ''
8 8
     };
9
+    my.connectionIndicators = {};
9 10
 
10 11
     my.changeLocalAudio = function(stream) {
11 12
         connection.jingle.localAudio = stream;
@@ -30,6 +31,11 @@ var VideoLayout = (function (my) {
30 31
         // Set default display name.
31 32
         setDisplayName('localVideoContainer');
32 33
 
34
+        if(!VideoLayout.connectionIndicators["localVideoContainer"]) {
35
+            VideoLayout.connectionIndicators["localVideoContainer"]
36
+                = new ConnectionIndicator($("#localVideoContainer")[0]);
37
+        }
38
+
33 39
         AudioLevels.updateAudioLevelCanvas();
34 40
 
35 41
         var localVideoSelector = $('#' + localVideo.id);
@@ -175,15 +181,42 @@ var VideoLayout = (function (my) {
175 181
                         if (largeVideoState.oldJid) {
176 182
                             var oldResourceJid = Strophe.getResourceFromJid(largeVideoState.oldJid);
177 183
                             VideoLayout.enableDominantSpeaker(oldResourceJid, false);
184
+                            if(VideoLayout.connectionIndicators) {
185
+                                var videoContainerId = null;
186
+                                if (oldResourceJid == Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
187
+                                    videoContainerId = 'localVideoContainer';
188
+                                }
189
+                                else {
190
+                                    videoContainerId = 'participant_' + oldResourceJid;
191
+                                }
192
+                                if(VideoLayout.connectionIndicators[videoContainerId])
193
+                                    VideoLayout.connectionIndicators[videoContainerId].setShowHQ(false);
194
+                            }
195
+
178 196
                         }
179 197
 
180 198
                         // Enable new dominant speaker in the remote videos section.
181 199
                         if (largeVideoState.userJid) {
182 200
                             var resourceJid = Strophe.getResourceFromJid(largeVideoState.userJid);
183 201
                             VideoLayout.enableDominantSpeaker(resourceJid, true);
202
+                            if(VideoLayout.connectionIndicators)
203
+                            {
204
+                                var videoContainerId = null;
205
+                                if (resourceJid
206
+                                    === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
207
+                                    videoContainerId = 'localVideoContainer';
208
+                                }
209
+                                else {
210
+                                    videoContainerId = 'participant_' + resourceJid;
211
+                                }
212
+                                if(VideoLayout.connectionIndicators[videoContainerId])
213
+                                    VideoLayout.connectionIndicators[videoContainerId].setShowHQ(true);
214
+                            }
215
+
184 216
                         }
185 217
 
186 218
                         largeVideoState.updateInProgress = false;
219
+
187 220
                         if (fade) {
188 221
                             // using "this" should be ok because we're called
189 222
                             // from within the fadeOut event.
@@ -344,6 +377,8 @@ var VideoLayout = (function (my) {
344 377
             // Set default display name.
345 378
             setDisplayName(videoSpanId);
346 379
 
380
+            VideoLayout.connectionIndicators[videoSpanId] = new ConnectionIndicator(container);
381
+
347 382
             var nickfield = document.createElement('span');
348 383
             nickfield.className = "nick";
349 384
             nickfield.appendChild(document.createTextNode(resourceJid));
@@ -504,8 +539,11 @@ var VideoLayout = (function (my) {
504 539
 
505 540
         if (!audioCount && !videoCount) {
506 541
             console.log("Remove whole user", container.id);
542
+            if(VideoLayout.connectionIndicators[container.id])
543
+                VideoLayout.connectionIndicators[container.id].remove();
507 544
             // Remove whole container
508 545
             container.remove();
546
+
509 547
             Util.playSoundNotification('userLeft');
510 548
             VideoLayout.resizeThumbnails();
511 549
         }
@@ -526,7 +564,11 @@ var VideoLayout = (function (my) {
526 564
         if (!peerContainer.is(':visible') && isShow)
527 565
             peerContainer.show();
528 566
         else if (peerContainer.is(':visible') && !isShow)
567
+        {
529 568
             peerContainer.hide();
569
+            if(VideoLayout.connectionIndicators['participant_' + resourceJid])
570
+                VideoLayout.connectionIndicators['participant_' + resourceJid].hide();
571
+        }
530 572
 
531 573
         VideoLayout.resizeThumbnails();
532 574
 
@@ -758,7 +800,7 @@ var VideoLayout = (function (my) {
758 800
             }
759 801
             var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
760 802
             videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
761
-            videoMutedSpan.css({right: ((audioMutedSpan.length > 0)?'30px':'0px')});
803
+            videoMutedSpan.css({right: ((audioMutedSpan.length > 0)?'50px':'30px')});
762 804
         }
763 805
     };
764 806
 
@@ -1431,5 +1473,352 @@ var VideoLayout = (function (my) {
1431 1473
         });
1432 1474
     });
1433 1475
 
1476
+    /**
1477
+     * Constructs new connection indicator.
1478
+     * @param videoContainer the video container associated with the indicator.
1479
+     * @constructor
1480
+     */
1481
+    function ConnectionIndicator(videoContainer)
1482
+    {
1483
+        this.videoContainer = videoContainer;
1484
+        this.bandwidth = null;
1485
+        this.packetLoss = null;
1486
+        this.bitrate = null;
1487
+        this.showMoreValue = false;
1488
+        this.resolution = null;
1489
+        this.transport = [];
1490
+        this.popover = null;
1491
+        this.showHQ = false;
1492
+        this.create();
1493
+    }
1494
+
1495
+    /**
1496
+     * Values for the connection quality
1497
+     * @type {{98: string, 81: string, 64: string, 47: string, 30: string, 0: string}}
1498
+     */
1499
+    ConnectionIndicator.connectionQualityValues = {
1500
+        98: "18px", //full
1501
+        81: "15px",//4 bars
1502
+        64: "11px",//3 bars
1503
+        47: "7px",//2 bars
1504
+        30: "3px",//1 bar
1505
+        0: "0px"//empty
1506
+    };
1507
+
1508
+    /**
1509
+     * Sets the value of the property that indicates whether the displayed resolution si the
1510
+     * resolution of High Quality stream or Low Quality
1511
+     * @param value boolean.
1512
+     */
1513
+    ConnectionIndicator.prototype.setShowHQ = function (value) {
1514
+        this.showHQ = value;
1515
+        this.updatePopoverData();
1516
+    };
1517
+
1518
+    /**
1519
+     * Generates the html content.
1520
+     * @returns {string} the html content.
1521
+     */
1522
+    ConnectionIndicator.prototype.generateText = function () {
1523
+        var downloadBitrate, uploadBitrate, packetLoss, resolution;
1524
+
1525
+        if(this.bitrate === null)
1526
+        {
1527
+            downloadBitrate = "N/A";
1528
+            uploadBitrate = "N/A";
1529
+        }
1530
+        else
1531
+        {
1532
+            downloadBitrate = this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
1533
+            uploadBitrate = this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
1534
+        }
1535
+
1536
+        if(this.packetLoss === null)
1537
+        {
1538
+            packetLoss = "N/A";
1539
+        }
1540
+        else
1541
+        {
1542
+
1543
+            packetLoss = "<span class='jitsipopover_green'>&darr;</span>" +
1544
+                (this.packetLoss.download != null? this.packetLoss.download : "N/A") +
1545
+                "% <span class='jitsipopover_orange'>&uarr;</span>" +
1546
+                (this.packetLoss.upload != null? this.packetLoss.upload : "N/A") + "%";
1547
+        }
1548
+
1549
+        var resolutionValue = null;
1550
+        if(this.resolution)
1551
+        {
1552
+            if(this.showHQ && this.resolution.hq)
1553
+            {
1554
+                resolutionValue = this.resolution.hq;
1555
+            }
1556
+            else if(!this.showHQ && this.resolution.lq)
1557
+            {
1558
+                resolutionValue = this.resolution.lq;
1559
+            }
1560
+        }
1561
+
1562
+        if(!resolutionValue ||
1563
+            !resolutionValue.height ||
1564
+            !resolutionValue.width)
1565
+        {
1566
+            resolution = "N/A";
1567
+        }
1568
+        else
1569
+        {
1570
+            resolution = resolutionValue.width + "x" + resolutionValue.height;
1571
+        }
1572
+
1573
+        var result = "<span class='jitsipopover_blue'>Bitrate:</span> <span class='jitsipopover_green'>&darr;</span>" +
1574
+            downloadBitrate + " <span class='jitsipopover_orange'>&uarr;</span>" +
1575
+            uploadBitrate + "<br />" +
1576
+            "<span class='jitsipopover_blue'>Packet loss: </span>" + packetLoss  + "<br />" +
1577
+            "<span class='jitsipopover_blue'>Resolution:</span> " + resolution + "<br />";
1578
+
1579
+        if(this.videoContainer.id == "localVideoContainer")
1580
+            result += "<div class=\"jitsipopover_showmore\" onclick = \"VideoLayout.connectionIndicators['" +
1581
+                 this.videoContainer.id + "'].showMore()\">" + (this.showMoreValue? "Show less" : "Show More") + "</div><br />";
1582
+
1583
+        if(this.showMoreValue)
1584
+        {
1585
+            var downloadBandwidth, uploadBandwidth, transport;
1586
+            if(this.bandwidth === null)
1587
+            {
1588
+                downloadBandwidth = "N/A";
1589
+                uploadBandwidth = "N/A";
1590
+            }
1591
+            else
1592
+            {
1593
+                downloadBandwidth = this.bandwidth.download? this.bandwidth.download + " Kbps" : "N/A";
1594
+                uploadBandwidth = this.bandwidth.upload? this.bandwidth.upload + " Kbps" : "N/A";
1595
+            }
1596
+
1597
+            if(!this.transport || this.transport.length === 0)
1598
+            {
1599
+                transport = "<span class='jitsipopover_blue'>Address:</span> N/A";
1600
+            }
1601
+            else
1602
+            {
1603
+                transport = "<span class='jitsipopover_blue'>Address:</span> " + this.transport[0].ip.substring(0,this.transport[0].ip.indexOf(":")) + "<br />";
1604
+                if(this.transport.length > 1)
1605
+                {
1606
+                    transport += "<span class='jitsipopover_blue'>Ports:</span> ";
1607
+                }
1608
+                else
1609
+                {
1610
+                    transport += "<span class='jitsipopover_blue'>Port:</span> ";
1611
+                }
1612
+                for(var i = 0; i < this.transport.length; i++)
1613
+                {
1614
+                    transport += ((i !== 0)? ", " : "") +
1615
+                        this.transport[i].ip.substring(this.transport[i].ip.indexOf(":")+1,
1616
+                                this.transport[i].ip.length);
1617
+                }
1618
+                transport += "<br /><span class='jitsipopover_blue'>Transport:</span> " + this.transport[0].type + "<br />";
1619
+            }
1620
+
1621
+            result += "<span class='jitsipopover_blue'>Estimated bandwidth:</span> " +
1622
+                "<span class='jitsipopover_green'>&darr;</span>" + downloadBandwidth +
1623
+                " <span class='jitsipopover_orange'>&uarr;</span>" +
1624
+                uploadBandwidth + "<br />";
1625
+
1626
+            result += transport;
1627
+
1628
+        }
1629
+
1630
+        return result;
1631
+    };
1632
+
1633
+    /**
1634
+     * Shows or hide the additional information.
1635
+     */
1636
+    ConnectionIndicator.prototype.showMore = function () {
1637
+        this.showMoreValue = !this.showMoreValue;
1638
+        this.updatePopoverData();
1639
+    };
1640
+
1641
+    /**
1642
+     * Creates the indicator
1643
+     */
1644
+    ConnectionIndicator.prototype.create = function () {
1645
+        this.connectionIndicatorContainer = document.createElement("div");
1646
+        this.connectionIndicatorContainer.className = "connectionindicator";
1647
+        this.connectionIndicatorContainer.style.display = "none";
1648
+        this.videoContainer.appendChild(this.connectionIndicatorContainer);
1649
+        this.popover = new JitsiPopover($("#" + this.videoContainer.id + " > .connectionindicator"),
1650
+            {content: "<div class=\"connection_info\">Come back here for " +
1651
+                "connection information once the conference starts</div>", skin: "black"});
1652
+
1653
+        function createIcon(classes)
1654
+        {
1655
+            var icon = document.createElement("span");
1656
+            for(var i in classes)
1657
+            {
1658
+                icon.classList.add(classes[i]);
1659
+            }
1660
+            icon.appendChild(document.createElement("i")).classList.add("icon-connection");
1661
+            return icon;
1662
+        }
1663
+        this.emptyIcon = this.connectionIndicatorContainer.appendChild(
1664
+            createIcon(["connection", "connection_empty"]));
1665
+        this.fullIcon = this.connectionIndicatorContainer.appendChild(
1666
+            createIcon(["connection", "connection_full"]));
1667
+
1668
+    };
1669
+
1670
+    /**
1671
+     * Removes the indicator
1672
+     */
1673
+    ConnectionIndicator.prototype.remove = function()
1674
+    {
1675
+        this.popover.hide();
1676
+        this.connectionIndicatorContainer.remove();
1677
+
1678
+    };
1679
+
1680
+    /**
1681
+     * Updates the data of the indicator
1682
+     * @param percent the percent of connection quality
1683
+     * @param object the statistics data.
1684
+     */
1685
+    ConnectionIndicator.prototype.updateConnectionQuality = function (percent, object) {
1686
+
1687
+        if(percent === null)
1688
+        {
1689
+            this.connectionIndicatorContainer.style.display = "none";
1690
+            return;
1691
+        }
1692
+        else
1693
+        {
1694
+            this.connectionIndicatorContainer.style.display = "block";
1695
+        }
1696
+        this.bandwidth = object.bandwidth;
1697
+        this.bitrate = object.bitrate;
1698
+        this.packetLoss = object.packetLoss;
1699
+        this.transport = object.transport;
1700
+        if(object.resolution)
1701
+        {
1702
+            this.resolution = object.resolution;
1703
+        }
1704
+        for(var quality in ConnectionIndicator.connectionQualityValues)
1705
+        {
1706
+            if(percent >= quality)
1707
+            {
1708
+                this.fullIcon.style.width = ConnectionIndicator.connectionQualityValues[quality];
1709
+            }
1710
+        }
1711
+        this.updatePopoverData();
1712
+    };
1713
+
1714
+    /**
1715
+     * Updates the resolution
1716
+     * @param resolution the new resolution
1717
+     */
1718
+    ConnectionIndicator.prototype.updateResolution = function (resolution) {
1719
+        this.resolution = resolution;
1720
+        this.updatePopoverData();
1721
+    };
1722
+
1723
+    /**
1724
+     * Updates the content of the popover
1725
+     */
1726
+    ConnectionIndicator.prototype.updatePopoverData = function () {
1727
+        this.popover.updateContent("<div class=\"connection_info\">" + this.generateText() + "</div>");
1728
+    };
1729
+
1730
+    /**
1731
+     * Hides the popover
1732
+     */
1733
+    ConnectionIndicator.prototype.hide = function () {
1734
+        this.popover.hide();
1735
+    };
1736
+
1737
+    /**
1738
+     * Hides the indicator
1739
+     */
1740
+    ConnectionIndicator.prototype.hideIndicator = function () {
1741
+        this.connectionIndicatorContainer.style.display = "none";
1742
+    };
1743
+
1744
+    /**
1745
+     * Updates the data for the indicator
1746
+     * @param id the id of the indicator
1747
+     * @param percent the percent for connection quality
1748
+     * @param object the data
1749
+     */
1750
+    function updateStatsIndicator(id, percent, object) {
1751
+        if(VideoLayout.connectionIndicators[id])
1752
+            VideoLayout.connectionIndicators[id].updateConnectionQuality(percent, object);
1753
+    }
1754
+
1755
+    /**
1756
+     * Updates local stats
1757
+     * @param percent
1758
+     * @param object
1759
+     */
1760
+    my.updateLocalConnectionStats = function (percent, object) {
1761
+        var resolution = null;
1762
+        if(object.resolution !== null)
1763
+        {
1764
+            resolution = object.resolution;
1765
+            object.resolution = resolution[connection.emuc.myroomjid];
1766
+            delete resolution[connection.emuc.myroomjid];
1767
+        }
1768
+        updateStatsIndicator("localVideoContainer", percent, object);
1769
+        for(var jid in resolution)
1770
+        {
1771
+            if(resolution[jid] === null)
1772
+                continue;
1773
+            var id = 'participant_' + Strophe.getResourceFromJid(jid);
1774
+            if(VideoLayout.connectionIndicators[id])
1775
+            {
1776
+                VideoLayout.connectionIndicators[id].updateResolution(resolution[jid]);
1777
+            }
1778
+        }
1779
+
1780
+    };
1781
+
1782
+    /**
1783
+     * Updates remote stats.
1784
+     * @param jid the jid associated with the stats
1785
+     * @param percent the connection quality percent
1786
+     * @param object the stats data
1787
+     */
1788
+    my.updateConnectionStats = function (jid, percent, object) {
1789
+        var resourceJid = Strophe.getResourceFromJid(jid);
1790
+
1791
+        var videoSpanId = 'participant_' + resourceJid;
1792
+        updateStatsIndicator(videoSpanId, percent, object);
1793
+    };
1794
+
1795
+    /**
1796
+     * Removes the connection
1797
+     * @param jid
1798
+     */
1799
+    my.removeConnectionIndicator = function (jid) {
1800
+        if(VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)])
1801
+            VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)].remove();
1802
+    };
1803
+
1804
+    /**
1805
+     * Hides the connection indicator
1806
+     * @param jid
1807
+     */
1808
+    my.hideConnectionIndicator = function (jid) {
1809
+        if(VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)])
1810
+            VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)].hide();
1811
+    };
1812
+
1813
+    /**
1814
+     * Hides all the indicators
1815
+     */
1816
+    my.onStatsStop = function () {
1817
+        for(var indicator in VideoLayout.connectionIndicators)
1818
+        {
1819
+            VideoLayout.connectionIndicators[indicator].hideIndicator();
1820
+        }
1821
+    };
1822
+
1434 1823
     return my;
1435 1824
 }(VideoLayout || {}));

Loading…
İptal
Kaydet