From 03839587b255fb873af0d7eb80c58a439cc0946e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 30 Oct 2014 17:42:12 +0100 Subject: [PATCH 1/4] Periodically get statistics for peer connections. Extract certificate information (currently only Chromium/Chrome) and used ciphers (currently only Spreed.ME app) and trigger events. --- static/js/mediastream/peercall.js | 18 +++ static/js/mediastream/peerconnection.js | 171 ++++++++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/static/js/mediastream/peercall.js b/static/js/mediastream/peercall.js index a56f3202..af04af52 100644 --- a/static/js/mediastream/peercall.js +++ b/static/js/mediastream/peercall.js @@ -180,6 +180,24 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection }; + PeerCall.prototype.onPeerStatisticsChanged = function(statistics) { + + this.e.triggerHandler("statisticsChanged", [statistics, this]); + + }; + + PeerCall.prototype.onCertificatesReceived = function(certificates) { + + this.e.triggerHandler("certificatesReceived", [certificates, this]); + + }; + + PeerCall.prototype.onCiphersNegotiated = function(ciphers) { + + this.e.triggerHandler("ciphersNegotiated", [ciphers, this]); + + }; + PeerCall.prototype.onRemoteStreamAdded = function(stream) { this.streams[stream] = true; diff --git a/static/js/mediastream/peerconnection.js b/static/js/mediastream/peerconnection.js index 760835e3..2fa2e766 100644 --- a/static/js/mediastream/peerconnection.js +++ b/static/js/mediastream/peerconnection.js @@ -23,6 +23,9 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { var count = 0; var dataChannelDefaultLabel = "default"; + // update connection statistics once per second + var STATS_INTERVAL = 1000; + var PeerConnection = function(webrtc, currentcall) { this.webrtc = webrtc; @@ -31,6 +34,11 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { this.pc = null; this.datachannel = null; this.datachannelReady = false; + this.statsActive = false; + this.localCertificate = null; + this.remoteCertificate = null; + this.dtlsCipher = null; + this.srtpCipher = null; if (currentcall) { this.createPeerConnection(currentcall); @@ -198,10 +206,166 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { }; + PeerConnection.prototype.getStatistics = function(callback) { + if (!this.pc) { + callback([]); + return; + } + + if (!!navigator.mozGetUserMedia) { + this.getStats( + null, + function (res) { + var items = []; + res.forEach(function(result) { + items.push(result); + }); + callback(items); + }, + callback + ); + } else { + this.getStats(function(res) { + var items = []; + res.result().forEach(function(result) { + var item = {}; + result.names().forEach(function(name) { + item[name] = result.stat(name); + }); + item.id = result.id; + item.type = result.type; + item.timestamp = result.timestamp; + items.push(item); + }); + callback(items); + }); + } + }; + + PeerConnection.prototype.updateStatistics = function(callback) { + this.getStatistics(_.bind(function(stats) { + if (!this.localCertificate || !this.remoteCertificate || !this.dtlsCipher || !this.srtpCipher) { + var certificates = {}; + var dtlsCiphers = {}; + var srtpCiphers = {}; + var i; + var item; + if (!this.localCertificate || !this.remoteCertificate) { + // build lookup tables + for (i=0; i 1) { + console.warn("Using multiple DTLS ciphers", dtlsCiphers); + } + this.dtlsCipher = _.reduce(dtlsCiphers, function(a, b) { return a + ", " + b; }); + } + if (!this.srtpCipher && !_.isEmpty(srtpCiphers)) { + srtpCiphers = _.keys(srtpCiphers); + if (srtpCiphers.length > 1) { + console.warn("Using multiple SRTP ciphers", srtpCiphers); + } + this.srtpCipher = _.reduce(srtpCiphers, function(a, b) { return a + ", " + b; }); + } + + if (this.currentcall) { + if (this.localCertificate && this.remoteCertificate) { + this.currentcall.onCertificatesReceived({'local': this.localCertificate, 'remote': this.remoteCertificate}); + } + if (this.dtlsCipher && this.srtpCipher) { + this.currentcall.onCiphersNegotiated({'dtls': this.dtlsCipher, 'srtp': this.srtpCipher}); + } + } + } + + if (this.currentcall) { + this.currentcall.onPeerStatisticsChanged(stats); + } + if (callback) { + callback(stats); + } + }, this)); + }; + + PeerConnection.prototype.startStatistics = function() { + if (this.statsActive) { + return; + } + + var updateFunc = _.bind(function() { + if (!this.statsActive || !this.pc) { + return; + } + this.updateStatistics(function() { + _.delay(updateFunc, STATS_INTERVAL); + }); + }, this); + + this.statsActive = true; + updateFunc(); + }; + + PeerConnection.prototype.stopStatistics = function() { + this.statsActive = false; + }; + PeerConnection.prototype.onIceConnectionStateChange = function(event) { var iceConnectionState = event.target.iceConnectionState; console.info("ICE connection state change", iceConnectionState, event, this.currentcall); + switch (iceConnectionState) { + case "connected": + this.startStatistics(); + break; + case "closed": + this.stopStatistics(); + break; + } this.currentcall.onIceConnectionStateChange(iceConnectionState); }; @@ -237,6 +401,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { PeerConnection.prototype.close = function() { + this.stopStatistics(); if (this.datachannel) { this.datachannel.close() } @@ -311,6 +476,12 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { }; + PeerConnection.prototype.getStats = function() { + + return this.pc.getStats.apply(this.pc, arguments); + + }; + return PeerConnection; }); From ffe7d86a4005f0ab3a069f446a4745d875d914d5 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 30 Oct 2014 18:00:13 +0100 Subject: [PATCH 2/4] Cipher information are also sent in "googComponent". --- static/js/mediastream/peerconnection.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/static/js/mediastream/peerconnection.js b/static/js/mediastream/peerconnection.js index 2fa2e766..ff4a32a5 100644 --- a/static/js/mediastream/peerconnection.js +++ b/static/js/mediastream/peerconnection.js @@ -39,6 +39,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { this.remoteCertificate = null; this.dtlsCipher = null; this.srtpCipher = null; + this.notifiedCertificates = false; if (currentcall) { this.createPeerConnection(currentcall); @@ -282,9 +283,6 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { if (!this.remoteCertificate && certId && certificates.hasOwnProperty(certId)) { this.remoteCertificate = certificates[certId]; } - break; - - case "googCandidatePair": if (item.spreedDtlsCipher) { dtlsCiphers[item.spreedDtlsCipher] = true; } @@ -314,7 +312,8 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { } if (this.currentcall) { - if (this.localCertificate && this.remoteCertificate) { + if (!this.notifiedCertificates && this.localCertificate && this.remoteCertificate) { + this.notifiedCertificates = true; this.currentcall.onCertificatesReceived({'local': this.localCertificate, 'remote': this.remoteCertificate}); } if (this.dtlsCipher && this.srtpCipher) { From 83d7907dcd1aa3a3e80e5c5b3c63d826872e1237 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Fri, 31 Oct 2014 11:03:44 +0100 Subject: [PATCH 3/4] Moved browser-specific code to separate adapter. --- static/js/libs/webrtc.getstats.js | 67 +++++++++++++++++++++++++ static/js/main.js | 4 ++ static/js/mediastream/peerconnection.js | 40 +-------------- 3 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 static/js/libs/webrtc.getstats.js diff --git a/static/js/libs/webrtc.getstats.js b/static/js/libs/webrtc.getstats.js new file mode 100644 index 00000000..912941d0 --- /dev/null +++ b/static/js/libs/webrtc.getstats.js @@ -0,0 +1,67 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +var getRTCStats = null; + +define(['webrtc.adapter'], function() { + + switch (webrtcDetectedBrowser) { + case "firefox": + getRTCStats = function(peerconnection, callback) { + peerconnection.getStats( + null, + function (res) { + var items = []; + res.forEach(function(result) { + items.push(result); + }); + callback(items); + }, + callback + ); + }; + break; + + case "chrome": + getRTCStats = function(peerconnection, callback) { + peerconnection.getStats(function(res) { + var items = []; + res.result().forEach(function(result) { + var item = {}; + result.names().forEach(function(name) { + item[name] = result.stat(name); + }); + item.id = result.id; + item.type = result.type; + item.timestamp = result.timestamp; + items.push(item); + }); + callback(items); + }); + }; + break; + + default: + // browser doesn't support WebRTC + break; + } + +}); diff --git a/static/js/main.js b/static/js/main.js index 91bbe9fb..7223f824 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -27,6 +27,7 @@ require.config({ "underscore": 'libs/lodash.min', // alternative to underscore "modernizr": 'libs/modernizr', 'webrtc.adapter': 'libs/webrtc.adapter', + 'webrtc.getstats': 'libs/webrtc.getstats', 'angular': 'libs/angular/angular.min', 'ui-bootstrap': 'libs/angular/ui-bootstrap-tpls.min', 'ua-parser': 'libs/ua-parser', @@ -133,6 +134,9 @@ require.config({ deps: ['jquery'], exports: '$' }, + 'webrtc.getstats': { + deps: ['webrtc.adapter'] + } } }); diff --git a/static/js/mediastream/peerconnection.js b/static/js/mediastream/peerconnection.js index ff4a32a5..95c0f323 100644 --- a/static/js/mediastream/peerconnection.js +++ b/static/js/mediastream/peerconnection.js @@ -18,7 +18,7 @@ * along with this program. If not, see . * */ -define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { +define(['jquery', 'underscore', 'webrtc.adapter', 'webrtc.getstats'], function($, _) { var count = 0; var dataChannelDefaultLabel = "default"; @@ -207,44 +207,8 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { }; - PeerConnection.prototype.getStatistics = function(callback) { - if (!this.pc) { - callback([]); - return; - } - - if (!!navigator.mozGetUserMedia) { - this.getStats( - null, - function (res) { - var items = []; - res.forEach(function(result) { - items.push(result); - }); - callback(items); - }, - callback - ); - } else { - this.getStats(function(res) { - var items = []; - res.result().forEach(function(result) { - var item = {}; - result.names().forEach(function(name) { - item[name] = result.stat(name); - }); - item.id = result.id; - item.type = result.type; - item.timestamp = result.timestamp; - items.push(item); - }); - callback(items); - }); - } - }; - PeerConnection.prototype.updateStatistics = function(callback) { - this.getStatistics(_.bind(function(stats) { + getRTCStats(this.pc, _.bind(function(stats) { if (!this.localCertificate || !this.remoteCertificate || !this.dtlsCipher || !this.srtpCipher) { var certificates = {}; var dtlsCiphers = {}; From 2d2387b9477b8c7ba87662dccfd101fa6361166e Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 3 Nov 2014 10:52:59 +0100 Subject: [PATCH 4/4] Also start getting statistics in "completed" ICE state (if not already running). --- static/js/mediastream/peerconnection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/mediastream/peerconnection.js b/static/js/mediastream/peerconnection.js index 95c0f323..19afeb77 100644 --- a/static/js/mediastream/peerconnection.js +++ b/static/js/mediastream/peerconnection.js @@ -323,6 +323,7 @@ define(['jquery', 'underscore', 'webrtc.adapter', 'webrtc.getstats'], function($ console.info("ICE connection state change", iceConnectionState, event, this.currentcall); switch (iceConnectionState) { case "connected": + case "completed": this.startStatistics(); break; case "closed":