|
@@ -1,664 +0,0 @@
|
1
|
|
-/* global require, ssrc2jid */
|
2
|
|
-/* jshint -W117 */
|
3
|
|
-/* jshint -W101 */
|
4
|
|
-var RTCBrowserType = require("../RTC/RTCBrowserType");
|
5
|
|
-var StatisticsEvents = require("../../service/statistics/Events");
|
6
|
|
-
|
7
|
|
-/* Whether we support the browser we are running into for logging statistics */
|
8
|
|
-var browserSupported = RTCBrowserType.isChrome() ||
|
9
|
|
- RTCBrowserType.isOpera() || RTCBrowserType.isFirefox();
|
10
|
|
-/**
|
11
|
|
- * Calculates packet lost percent using the number of lost packets and the
|
12
|
|
- * number of all packet.
|
13
|
|
- * @param lostPackets the number of lost packets
|
14
|
|
- * @param totalPackets the number of all packets.
|
15
|
|
- * @returns {number} packet loss percent
|
16
|
|
- */
|
17
|
|
-function calculatePacketLoss(lostPackets, totalPackets) {
|
18
|
|
- if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
|
19
|
|
- return 0;
|
20
|
|
- return Math.round((lostPackets/totalPackets)*100);
|
21
|
|
-}
|
22
|
|
-
|
23
|
|
-function getStatValue(item, name) {
|
24
|
|
- var browserType = RTCBrowserType.getBrowserType();
|
25
|
|
- if (!keyMap[browserType][name])
|
26
|
|
- throw "The property isn't supported!";
|
27
|
|
- var key = keyMap[browserType][name];
|
28
|
|
- return (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) ?
|
29
|
|
- item.stat(key) : item[key];
|
30
|
|
-}
|
31
|
|
-
|
32
|
|
-/**
|
33
|
|
- * Peer statistics data holder.
|
34
|
|
- * @constructor
|
35
|
|
- */
|
36
|
|
-function PeerStats()
|
37
|
|
-{
|
38
|
|
- this.ssrc2Loss = {};
|
39
|
|
- this.ssrc2AudioLevel = {};
|
40
|
|
- this.ssrc2bitrate = {};
|
41
|
|
- this.ssrc2resolution = {};
|
42
|
|
-}
|
43
|
|
-
|
44
|
|
-/**
|
45
|
|
- * The bandwidth
|
46
|
|
- * @type {{}}
|
47
|
|
- */
|
48
|
|
-PeerStats.bandwidth = {};
|
49
|
|
-
|
50
|
|
-/**
|
51
|
|
- * The bit rate
|
52
|
|
- * @type {{}}
|
53
|
|
- */
|
54
|
|
-PeerStats.bitrate = {};
|
55
|
|
-
|
56
|
|
-/**
|
57
|
|
- * The packet loss rate
|
58
|
|
- * @type {{}}
|
59
|
|
- */
|
60
|
|
-PeerStats.packetLoss = null;
|
61
|
|
-
|
62
|
|
-/**
|
63
|
|
- * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
|
64
|
|
- * represented by this instance.
|
65
|
|
- * @param ssrc audio or video RTP stream SSRC.
|
66
|
|
- * @param lossRate new packet loss rate value to be set.
|
67
|
|
- */
|
68
|
|
-PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
|
69
|
|
-{
|
70
|
|
- this.ssrc2Loss[ssrc] = lossRate;
|
71
|
|
-};
|
72
|
|
-
|
73
|
|
-/**
|
74
|
|
- * Sets resolution for given <tt>ssrc</tt> that belong to the peer
|
75
|
|
- * represented by this instance.
|
76
|
|
- * @param ssrc audio or video RTP stream SSRC.
|
77
|
|
- * @param resolution new resolution value to be set.
|
78
|
|
- */
|
79
|
|
-PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
|
80
|
|
-{
|
81
|
|
- if(resolution === null && this.ssrc2resolution[ssrc])
|
82
|
|
- {
|
83
|
|
- delete this.ssrc2resolution[ssrc];
|
84
|
|
- }
|
85
|
|
- else if(resolution !== null)
|
86
|
|
- this.ssrc2resolution[ssrc] = resolution;
|
87
|
|
-};
|
88
|
|
-
|
89
|
|
-/**
|
90
|
|
- * Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
|
91
|
|
- * represented by this instance.
|
92
|
|
- * @param ssrc audio or video RTP stream SSRC.
|
93
|
|
- * @param bitrate new bitrate value to be set.
|
94
|
|
- */
|
95
|
|
-PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
|
96
|
|
-{
|
97
|
|
- if(this.ssrc2bitrate[ssrc])
|
98
|
|
- {
|
99
|
|
- this.ssrc2bitrate[ssrc].download += bitrate.download;
|
100
|
|
- this.ssrc2bitrate[ssrc].upload += bitrate.upload;
|
101
|
|
- }
|
102
|
|
- else {
|
103
|
|
- this.ssrc2bitrate[ssrc] = bitrate;
|
104
|
|
- }
|
105
|
|
-};
|
106
|
|
-
|
107
|
|
-/**
|
108
|
|
- * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
|
109
|
|
- * the stream which belongs to the peer represented by this instance.
|
110
|
|
- * @param ssrc RTP stream SSRC for which current audio level value will be
|
111
|
|
- * updated.
|
112
|
|
- * @param audioLevel the new audio level value to be set. Value is truncated to
|
113
|
|
- * fit the range from 0 to 1.
|
114
|
|
- */
|
115
|
|
-PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
|
116
|
|
-{
|
117
|
|
- // Range limit 0 - 1
|
118
|
|
- this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
|
119
|
|
-};
|
120
|
|
-
|
121
|
|
-function formatAudioLevel(audioLevel) {
|
122
|
|
- return Math.min(Math.max(audioLevel, 0), 1);
|
123
|
|
-}
|
124
|
|
-
|
125
|
|
-/**
|
126
|
|
- * Array with the transport information.
|
127
|
|
- * @type {Array}
|
128
|
|
- */
|
129
|
|
-PeerStats.transport = [];
|
130
|
|
-
|
131
|
|
-
|
132
|
|
-/**
|
133
|
|
- * <tt>StatsCollector</tt> registers for stats updates of given
|
134
|
|
- * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
|
135
|
|
- * stats are extracted and put in {@link PeerStats} objects. Once the processing
|
136
|
|
- * is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt>
|
137
|
|
- * instance as an event source.
|
138
|
|
- *
|
139
|
|
- * @param peerconnection webRTC peer connection object.
|
140
|
|
- * @param interval stats refresh interval given in ms.
|
141
|
|
- * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
|
142
|
|
- * called on stats update.
|
143
|
|
- * @constructor
|
144
|
|
- */
|
145
|
|
-function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
|
146
|
|
-{
|
147
|
|
- this.peerconnection = peerconnection;
|
148
|
|
- this.baselineAudioLevelsReport = null;
|
149
|
|
- this.currentStatsReport = null;
|
150
|
|
- this.baselineStatsReport = null;
|
151
|
|
- this.audioLevelsIntervalId = null;
|
152
|
|
- this.eventEmitter = eventEmitter;
|
153
|
|
-
|
154
|
|
- /**
|
155
|
|
- * Gather PeerConnection stats once every this many milliseconds.
|
156
|
|
- */
|
157
|
|
- this.GATHER_INTERVAL = 15000;
|
158
|
|
-
|
159
|
|
- /**
|
160
|
|
- * Log stats via the focus once every this many milliseconds.
|
161
|
|
- */
|
162
|
|
- this.LOG_INTERVAL = 60000;
|
163
|
|
-
|
164
|
|
- /**
|
165
|
|
- * Gather stats and store them in this.statsToBeLogged.
|
166
|
|
- */
|
167
|
|
- this.gatherStatsIntervalId = null;
|
168
|
|
-
|
169
|
|
- /**
|
170
|
|
- * Send the stats already saved in this.statsToBeLogged to be logged via
|
171
|
|
- * the focus.
|
172
|
|
- */
|
173
|
|
- this.logStatsIntervalId = null;
|
174
|
|
-
|
175
|
|
- /**
|
176
|
|
- * Stores the statistics which will be send to the focus to be logged.
|
177
|
|
- */
|
178
|
|
- this.statsToBeLogged =
|
179
|
|
- {
|
180
|
|
- timestamps: [],
|
181
|
|
- stats: {}
|
182
|
|
- };
|
183
|
|
-
|
184
|
|
- // Updates stats interval
|
185
|
|
- this.audioLevelsIntervalMilis = audioLevelsInterval;
|
186
|
|
-
|
187
|
|
- this.statsIntervalId = null;
|
188
|
|
- this.statsIntervalMilis = statsInterval;
|
189
|
|
- // Map of jids to PeerStats
|
190
|
|
- this.jid2stats = {};
|
191
|
|
-}
|
192
|
|
-
|
193
|
|
-module.exports = StatsCollector;
|
194
|
|
-
|
195
|
|
-/**
|
196
|
|
- * Stops stats updates.
|
197
|
|
- */
|
198
|
|
-StatsCollector.prototype.stop = function () {
|
199
|
|
- if (this.audioLevelsIntervalId) {
|
200
|
|
- clearInterval(this.audioLevelsIntervalId);
|
201
|
|
- this.audioLevelsIntervalId = null;
|
202
|
|
- }
|
203
|
|
-
|
204
|
|
- if (this.statsIntervalId)
|
205
|
|
- {
|
206
|
|
- clearInterval(this.statsIntervalId);
|
207
|
|
- this.statsIntervalId = null;
|
208
|
|
- }
|
209
|
|
-
|
210
|
|
- if(this.logStatsIntervalId)
|
211
|
|
- {
|
212
|
|
- clearInterval(this.logStatsIntervalId);
|
213
|
|
- this.logStatsIntervalId = null;
|
214
|
|
- }
|
215
|
|
-
|
216
|
|
- if(this.gatherStatsIntervalId)
|
217
|
|
- {
|
218
|
|
- clearInterval(this.gatherStatsIntervalId);
|
219
|
|
- this.gatherStatsIntervalId = null;
|
220
|
|
- }
|
221
|
|
-};
|
222
|
|
-
|
223
|
|
-/**
|
224
|
|
- * Callback passed to <tt>getStats</tt> method.
|
225
|
|
- * @param error an error that occurred on <tt>getStats</tt> call.
|
226
|
|
- */
|
227
|
|
-StatsCollector.prototype.errorCallback = function (error)
|
228
|
|
-{
|
229
|
|
- console.error("Get stats error", error);
|
230
|
|
- this.stop();
|
231
|
|
-};
|
232
|
|
-
|
233
|
|
-/**
|
234
|
|
- * Starts stats updates.
|
235
|
|
- */
|
236
|
|
-StatsCollector.prototype.start = function ()
|
237
|
|
-{
|
238
|
|
- var self = this;
|
239
|
|
- if (!config.disableAudioLevels) {
|
240
|
|
- this.audioLevelsIntervalId = setInterval(
|
241
|
|
- function () {
|
242
|
|
- // Interval updates
|
243
|
|
- self.peerconnection.getStats(
|
244
|
|
- function (report) {
|
245
|
|
- var results = null;
|
246
|
|
- if (!report || !report.result ||
|
247
|
|
- typeof report.result != 'function') {
|
248
|
|
- results = report;
|
249
|
|
- }
|
250
|
|
- else {
|
251
|
|
- results = report.result();
|
252
|
|
- }
|
253
|
|
- //console.error("Got interval report", results);
|
254
|
|
- self.baselineAudioLevelsReport = results;
|
255
|
|
- },
|
256
|
|
- self.errorCallback
|
257
|
|
- );
|
258
|
|
- },
|
259
|
|
- self.audioLevelsIntervalMilis
|
260
|
|
- );
|
261
|
|
- }
|
262
|
|
-
|
263
|
|
- if (!config.disableStats && browserSupported) {
|
264
|
|
- this.statsIntervalId = setInterval(
|
265
|
|
- function () {
|
266
|
|
- // Interval updates
|
267
|
|
- self.peerconnection.getStats(
|
268
|
|
- function (report) {
|
269
|
|
- var results = null;
|
270
|
|
- if (!report || !report.result ||
|
271
|
|
- typeof report.result != 'function') {
|
272
|
|
- //firefox
|
273
|
|
- results = report;
|
274
|
|
- }
|
275
|
|
- else {
|
276
|
|
- //chrome
|
277
|
|
- results = report.result();
|
278
|
|
- }
|
279
|
|
- //console.error("Got interval report", results);
|
280
|
|
- self.currentStatsReport = results;
|
281
|
|
- try {
|
282
|
|
- self.processStatsReport();
|
283
|
|
- }
|
284
|
|
- catch (e) {
|
285
|
|
- console.error("Unsupported key:" + e, e);
|
286
|
|
- }
|
287
|
|
-
|
288
|
|
- self.baselineStatsReport = self.currentStatsReport;
|
289
|
|
- },
|
290
|
|
- self.errorCallback
|
291
|
|
- );
|
292
|
|
- },
|
293
|
|
- self.statsIntervalMilis
|
294
|
|
- );
|
295
|
|
- }
|
296
|
|
-
|
297
|
|
- // Logging statistics does not support firefox
|
298
|
|
- if (config.logStats && (browserSupported && !RTCBrowserType.isFirefox())) {
|
299
|
|
- this.gatherStatsIntervalId = setInterval(
|
300
|
|
- function () {
|
301
|
|
- self.peerconnection.getStats(
|
302
|
|
- function (report) {
|
303
|
|
- self.addStatsToBeLogged(report.result());
|
304
|
|
- },
|
305
|
|
- function () {
|
306
|
|
- }
|
307
|
|
- );
|
308
|
|
- },
|
309
|
|
- this.GATHER_INTERVAL
|
310
|
|
- );
|
311
|
|
-
|
312
|
|
- this.logStatsIntervalId = setInterval(
|
313
|
|
- function() { self.logStats(); },
|
314
|
|
- this.LOG_INTERVAL);
|
315
|
|
- }
|
316
|
|
-};
|
317
|
|
-
|
318
|
|
-/**
|
319
|
|
- * Checks whether a certain record should be included in the logged statistics.
|
320
|
|
- */
|
321
|
|
-function acceptStat(reportId, reportType, statName) {
|
322
|
|
- if (reportType == "googCandidatePair" && statName == "googChannelId")
|
323
|
|
- return false;
|
324
|
|
-
|
325
|
|
- if (reportType == "ssrc") {
|
326
|
|
- if (statName == "googTrackId" ||
|
327
|
|
- statName == "transportId" ||
|
328
|
|
- statName == "ssrc")
|
329
|
|
- return false;
|
330
|
|
- }
|
331
|
|
-
|
332
|
|
- return true;
|
333
|
|
-}
|
334
|
|
-
|
335
|
|
-/**
|
336
|
|
- * Checks whether a certain record should be included in the logged statistics.
|
337
|
|
- */
|
338
|
|
-function acceptReport(id, type) {
|
339
|
|
- if (id.substring(0, 15) == "googCertificate" ||
|
340
|
|
- id.substring(0, 9) == "googTrack" ||
|
341
|
|
- id.substring(0, 20) == "googLibjingleSession")
|
342
|
|
- return false;
|
343
|
|
-
|
344
|
|
- if (type == "googComponent")
|
345
|
|
- return false;
|
346
|
|
-
|
347
|
|
- return true;
|
348
|
|
-}
|
349
|
|
-
|
350
|
|
-/**
|
351
|
|
- * Converts the stats to the format used for logging, and saves the data in
|
352
|
|
- * this.statsToBeLogged.
|
353
|
|
- * @param reports Reports as given by webkitRTCPerConnection.getStats.
|
354
|
|
- */
|
355
|
|
-StatsCollector.prototype.addStatsToBeLogged = function (reports) {
|
356
|
|
- var self = this;
|
357
|
|
- var num_records = this.statsToBeLogged.timestamps.length;
|
358
|
|
- this.statsToBeLogged.timestamps.push(new Date().getTime());
|
359
|
|
- reports.map(function (report) {
|
360
|
|
- if (!acceptReport(report.id, report.type))
|
361
|
|
- return;
|
362
|
|
- var stat = self.statsToBeLogged.stats[report.id];
|
363
|
|
- if (!stat) {
|
364
|
|
- stat = self.statsToBeLogged.stats[report.id] = {};
|
365
|
|
- }
|
366
|
|
- stat.type = report.type;
|
367
|
|
- report.names().map(function (name) {
|
368
|
|
- if (!acceptStat(report.id, report.type, name))
|
369
|
|
- return;
|
370
|
|
- var values = stat[name];
|
371
|
|
- if (!values) {
|
372
|
|
- values = stat[name] = [];
|
373
|
|
- }
|
374
|
|
- while (values.length < num_records) {
|
375
|
|
- values.push(null);
|
376
|
|
- }
|
377
|
|
- values.push(report.stat(name));
|
378
|
|
- });
|
379
|
|
- });
|
380
|
|
-};
|
381
|
|
-
|
382
|
|
-StatsCollector.prototype.logStats = function () {
|
383
|
|
-
|
384
|
|
- if(!APP.conference._room.xmpp.sendLogs(this.statsToBeLogged))
|
385
|
|
- return;
|
386
|
|
- // Reset the stats
|
387
|
|
- this.statsToBeLogged.stats = {};
|
388
|
|
- this.statsToBeLogged.timestamps = [];
|
389
|
|
-};
|
390
|
|
-var keyMap = {};
|
391
|
|
-keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
|
392
|
|
- "ssrc": "ssrc",
|
393
|
|
- "packetsReceived": "packetsReceived",
|
394
|
|
- "packetsLost": "packetsLost",
|
395
|
|
- "packetsSent": "packetsSent",
|
396
|
|
- "bytesReceived": "bytesReceived",
|
397
|
|
- "bytesSent": "bytesSent"
|
398
|
|
-};
|
399
|
|
-keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
|
400
|
|
- "receiveBandwidth": "googAvailableReceiveBandwidth",
|
401
|
|
- "sendBandwidth": "googAvailableSendBandwidth",
|
402
|
|
- "remoteAddress": "googRemoteAddress",
|
403
|
|
- "transportType": "googTransportType",
|
404
|
|
- "localAddress": "googLocalAddress",
|
405
|
|
- "activeConnection": "googActiveConnection",
|
406
|
|
- "ssrc": "ssrc",
|
407
|
|
- "packetsReceived": "packetsReceived",
|
408
|
|
- "packetsSent": "packetsSent",
|
409
|
|
- "packetsLost": "packetsLost",
|
410
|
|
- "bytesReceived": "bytesReceived",
|
411
|
|
- "bytesSent": "bytesSent",
|
412
|
|
- "googFrameHeightReceived": "googFrameHeightReceived",
|
413
|
|
- "googFrameWidthReceived": "googFrameWidthReceived",
|
414
|
|
- "googFrameHeightSent": "googFrameHeightSent",
|
415
|
|
- "googFrameWidthSent": "googFrameWidthSent",
|
416
|
|
- "audioInputLevel": "audioInputLevel",
|
417
|
|
- "audioOutputLevel": "audioOutputLevel"
|
418
|
|
-};
|
419
|
|
-keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
|
420
|
|
- keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
|
421
|
|
-
|
422
|
|
-
|
423
|
|
-/**
|
424
|
|
- * Stats processing logic.
|
425
|
|
- */
|
426
|
|
-StatsCollector.prototype.processStatsReport = function () {
|
427
|
|
- if (!this.baselineStatsReport) {
|
428
|
|
- return;
|
429
|
|
- }
|
430
|
|
-
|
431
|
|
- for (var idx in this.currentStatsReport) {
|
432
|
|
- var now = this.currentStatsReport[idx];
|
433
|
|
- try {
|
434
|
|
- if (getStatValue(now, 'receiveBandwidth') ||
|
435
|
|
- getStatValue(now, 'sendBandwidth')) {
|
436
|
|
- PeerStats.bandwidth = {
|
437
|
|
- "download": Math.round(
|
438
|
|
- (getStatValue(now, 'receiveBandwidth')) / 1000),
|
439
|
|
- "upload": Math.round(
|
440
|
|
- (getStatValue(now, 'sendBandwidth')) / 1000)
|
441
|
|
- };
|
442
|
|
- }
|
443
|
|
- }
|
444
|
|
- catch(e){/*not supported*/}
|
445
|
|
-
|
446
|
|
- if(now.type == 'googCandidatePair')
|
447
|
|
- {
|
448
|
|
- var ip, type, localIP, active;
|
449
|
|
- try {
|
450
|
|
- ip = getStatValue(now, 'remoteAddress');
|
451
|
|
- type = getStatValue(now, "transportType");
|
452
|
|
- localIP = getStatValue(now, "localAddress");
|
453
|
|
- active = getStatValue(now, "activeConnection");
|
454
|
|
- }
|
455
|
|
- catch(e){/*not supported*/}
|
456
|
|
- if(!ip || !type || !localIP || active != "true")
|
457
|
|
- continue;
|
458
|
|
- var addressSaved = false;
|
459
|
|
- for(var i = 0; i < PeerStats.transport.length; i++)
|
460
|
|
- {
|
461
|
|
- if(PeerStats.transport[i].ip == ip &&
|
462
|
|
- PeerStats.transport[i].type == type &&
|
463
|
|
- PeerStats.transport[i].localip == localIP)
|
464
|
|
- {
|
465
|
|
- addressSaved = true;
|
466
|
|
- }
|
467
|
|
- }
|
468
|
|
- if(addressSaved)
|
469
|
|
- continue;
|
470
|
|
- PeerStats.transport.push({localip: localIP, ip: ip, type: type});
|
471
|
|
- continue;
|
472
|
|
- }
|
473
|
|
-
|
474
|
|
- if(now.type == "candidatepair")
|
475
|
|
- {
|
476
|
|
- if(now.state == "succeeded")
|
477
|
|
- continue;
|
478
|
|
-
|
479
|
|
- var local = this.currentStatsReport[now.localCandidateId];
|
480
|
|
- var remote = this.currentStatsReport[now.remoteCandidateId];
|
481
|
|
- PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
|
482
|
|
- ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
|
483
|
|
-
|
484
|
|
- }
|
485
|
|
-
|
486
|
|
- if (now.type != 'ssrc' && now.type != "outboundrtp" &&
|
487
|
|
- now.type != "inboundrtp") {
|
488
|
|
- continue;
|
489
|
|
- }
|
490
|
|
-
|
491
|
|
- var before = this.baselineStatsReport[idx];
|
492
|
|
- if (!before) {
|
493
|
|
- console.warn(getStatValue(now, 'ssrc') + ' not enough data');
|
494
|
|
- continue;
|
495
|
|
- }
|
496
|
|
-
|
497
|
|
- var ssrc = getStatValue(now, 'ssrc');
|
498
|
|
- if(!ssrc)
|
499
|
|
- continue;
|
500
|
|
- var jid = APP.conference._room.room.getJidBySSRC(ssrc);
|
501
|
|
- if (!jid && (Date.now() - now.timestamp) < 3000) {
|
502
|
|
- console.warn("No jid for ssrc: " + ssrc);
|
503
|
|
- continue;
|
504
|
|
- }
|
505
|
|
-
|
506
|
|
- var jidStats = this.jid2stats[jid];
|
507
|
|
- if (!jidStats) {
|
508
|
|
- jidStats = new PeerStats();
|
509
|
|
- this.jid2stats[jid] = jidStats;
|
510
|
|
- }
|
511
|
|
-
|
512
|
|
-
|
513
|
|
- var isDownloadStream = true;
|
514
|
|
- var key = 'packetsReceived';
|
515
|
|
- var packetsNow = getStatValue(now, key);
|
516
|
|
- if (typeof packetsNow === 'undefined' || packetsNow === null) {
|
517
|
|
- isDownloadStream = false;
|
518
|
|
- key = 'packetsSent';
|
519
|
|
- packetsNow = getStatValue(now, key);
|
520
|
|
- if (typeof packetsNow === 'undefined' || packetsNow === null) {
|
521
|
|
- console.warn("No packetsReceived nor packetsSent stat found");
|
522
|
|
- continue;
|
523
|
|
- }
|
524
|
|
- }
|
525
|
|
- if (!packetsNow || packetsNow < 0)
|
526
|
|
- packetsNow = 0;
|
527
|
|
-
|
528
|
|
- var packetsBefore = getStatValue(before, key);
|
529
|
|
- if (!packetsBefore || packetsBefore < 0)
|
530
|
|
- packetsBefore = 0;
|
531
|
|
- var packetRate = packetsNow - packetsBefore;
|
532
|
|
- if (!packetRate || packetRate < 0)
|
533
|
|
- packetRate = 0;
|
534
|
|
- var currentLoss = getStatValue(now, 'packetsLost');
|
535
|
|
- if (!currentLoss || currentLoss < 0)
|
536
|
|
- currentLoss = 0;
|
537
|
|
- var previousLoss = getStatValue(before, 'packetsLost');
|
538
|
|
- if (!previousLoss || previousLoss < 0)
|
539
|
|
- previousLoss = 0;
|
540
|
|
- var lossRate = currentLoss - previousLoss;
|
541
|
|
- if (!lossRate || lossRate < 0)
|
542
|
|
- lossRate = 0;
|
543
|
|
- var packetsTotal = (packetRate + lossRate);
|
544
|
|
-
|
545
|
|
- jidStats.setSsrcLoss(ssrc,
|
546
|
|
- {"packetsTotal": packetsTotal,
|
547
|
|
- "packetsLost": lossRate,
|
548
|
|
- "isDownloadStream": isDownloadStream});
|
549
|
|
-
|
550
|
|
-
|
551
|
|
- var bytesReceived = 0, bytesSent = 0;
|
552
|
|
- if(getStatValue(now, "bytesReceived")) {
|
553
|
|
- bytesReceived = getStatValue(now, "bytesReceived") -
|
554
|
|
- getStatValue(before, "bytesReceived");
|
555
|
|
- }
|
556
|
|
-
|
557
|
|
- if(getStatValue(now, "bytesSent")) {
|
558
|
|
- bytesSent = getStatValue(now, "bytesSent") -
|
559
|
|
- getStatValue(before, "bytesSent");
|
560
|
|
- }
|
561
|
|
-
|
562
|
|
- var time = Math.round((now.timestamp - before.timestamp) / 1000);
|
563
|
|
- if(bytesReceived <= 0 || time <= 0) {
|
564
|
|
- bytesReceived = 0;
|
565
|
|
- } else {
|
566
|
|
- bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
|
567
|
|
- }
|
568
|
|
-
|
569
|
|
- if(bytesSent <= 0 || time <= 0) {
|
570
|
|
- bytesSent = 0;
|
571
|
|
- } else {
|
572
|
|
- bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
|
573
|
|
- }
|
574
|
|
-
|
575
|
|
- jidStats.setSsrcBitrate(ssrc, {
|
576
|
|
- "download": bytesReceived,
|
577
|
|
- "upload": bytesSent});
|
578
|
|
-
|
579
|
|
- var resolution = {height: null, width: null};
|
580
|
|
- try {
|
581
|
|
- if (getStatValue(now, "googFrameHeightReceived") &&
|
582
|
|
- getStatValue(now, "googFrameWidthReceived")) {
|
583
|
|
- resolution.height = getStatValue(now, "googFrameHeightReceived");
|
584
|
|
- resolution.width = getStatValue(now, "googFrameWidthReceived");
|
585
|
|
- }
|
586
|
|
- else if (getStatValue(now, "googFrameHeightSent") &&
|
587
|
|
- getStatValue(now, "googFrameWidthSent")) {
|
588
|
|
- resolution.height = getStatValue(now, "googFrameHeightSent");
|
589
|
|
- resolution.width = getStatValue(now, "googFrameWidthSent");
|
590
|
|
- }
|
591
|
|
- }
|
592
|
|
- catch(e){/*not supported*/}
|
593
|
|
-
|
594
|
|
- if(resolution.height && resolution.width) {
|
595
|
|
- jidStats.setSsrcResolution(ssrc, resolution);
|
596
|
|
- } else {
|
597
|
|
- jidStats.setSsrcResolution(ssrc, null);
|
598
|
|
- }
|
599
|
|
- }
|
600
|
|
-
|
601
|
|
- var self = this;
|
602
|
|
- // Jid stats
|
603
|
|
- var totalPackets = {download: 0, upload: 0};
|
604
|
|
- var lostPackets = {download: 0, upload: 0};
|
605
|
|
- var bitrateDownload = 0;
|
606
|
|
- var bitrateUpload = 0;
|
607
|
|
- var resolutions = {};
|
608
|
|
- Object.keys(this.jid2stats).forEach(
|
609
|
|
- function (jid) {
|
610
|
|
- Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
|
611
|
|
- function (ssrc) {
|
612
|
|
- var type = "upload";
|
613
|
|
- if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
|
614
|
|
- type = "download";
|
615
|
|
- totalPackets[type] +=
|
616
|
|
- self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
|
617
|
|
- lostPackets[type] +=
|
618
|
|
- self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
|
619
|
|
- }
|
620
|
|
- );
|
621
|
|
- Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
|
622
|
|
- function (ssrc) {
|
623
|
|
- bitrateDownload +=
|
624
|
|
- self.jid2stats[jid].ssrc2bitrate[ssrc].download;
|
625
|
|
- bitrateUpload +=
|
626
|
|
- self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
|
627
|
|
-
|
628
|
|
- delete self.jid2stats[jid].ssrc2bitrate[ssrc];
|
629
|
|
- }
|
630
|
|
- );
|
631
|
|
- resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
|
632
|
|
- }
|
633
|
|
- );
|
634
|
|
-
|
635
|
|
- PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
|
636
|
|
-
|
637
|
|
- PeerStats.packetLoss = {
|
638
|
|
- total:
|
639
|
|
- calculatePacketLoss(lostPackets.download + lostPackets.upload,
|
640
|
|
- totalPackets.download + totalPackets.upload),
|
641
|
|
- download:
|
642
|
|
- calculatePacketLoss(lostPackets.download, totalPackets.download),
|
643
|
|
- upload:
|
644
|
|
- calculatePacketLoss(lostPackets.upload, totalPackets.upload)
|
645
|
|
- };
|
646
|
|
-
|
647
|
|
- let idResolution = {};
|
648
|
|
- if (resolutions) { // use id instead of jid
|
649
|
|
- Object.keys(resolutions).forEach(function (jid) {
|
650
|
|
- let id = Strophe.getResourceFromJid(jid);
|
651
|
|
- idResolution[id] = resolutions[jid];
|
652
|
|
- });
|
653
|
|
- }
|
654
|
|
- this.eventEmitter.emit(StatisticsEvents.CONNECTION_STATS,
|
655
|
|
- {
|
656
|
|
- "bitrate": PeerStats.bitrate,
|
657
|
|
- "packetLoss": PeerStats.packetLoss,
|
658
|
|
- "bandwidth": PeerStats.bandwidth,
|
659
|
|
- "resolution": idResolution,
|
660
|
|
- "transport": PeerStats.transport
|
661
|
|
- });
|
662
|
|
- PeerStats.transport = [];
|
663
|
|
-
|
664
|
|
-};
|