From fba521352aa755c91a21e4988d0a5bb068392265 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sat, 25 Apr 2015 21:25:55 +0200 Subject: [PATCH] Added client side configuration support for WebRTC renegotiation of user media streams. --- static/js/directives/audiolevel.js | 19 ++++--- static/js/directives/audiovideo.js | 4 ++ static/js/mediastream/peerconference.js | 14 +++-- static/js/mediastream/usermedia.js | 49 ++++++++--------- static/js/mediastream/webrtc.js | 70 +++++++++++++++---------- static/js/services/mediastream.js | 4 ++ 6 files changed, 96 insertions(+), 64 deletions(-) diff --git a/static/js/directives/audiolevel.js b/static/js/directives/audiolevel.js index 87220dec..7d6a19c2 100644 --- a/static/js/directives/audiolevel.js +++ b/static/js/directives/audiolevel.js @@ -22,9 +22,7 @@ "use strict"; define(['jquery', 'underscore'], function($, _) { - return ["$window", "mediaStream", "safeApply", "animationFrame", function($window, mediaStream, safeApply, animationFrame) { - - var webrtc = mediaStream.webrtc; + return ["$window", "webrtc", "safeApply", "animationFrame", function($window, webrtc, safeApply, animationFrame) { // Consider anyting lower than this % as no audio. var threshhold = 5; @@ -37,6 +35,13 @@ define(['jquery', 'underscore'], function($, _) { // Talking status history map. var talkingStatus = {}; + // Usermedia reference. + var usermedia = null; + webrtc.e.on("usermedia", function(event, um) { + console.log("Audio level user media changed", um); + usermedia = um; + }); + var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { $scope.talking = false; @@ -47,8 +52,8 @@ define(['jquery', 'underscore'], function($, _) { var width = 0; this.update = _.bind(function() { if (this.active || width > 0) { - if (webrtc.usermedia.audioLevel) { - width = Math.round(100 * webrtc.usermedia.audioLevel); + if (usermedia && usermedia.audioLevel) { + width = Math.round(100 * usermedia.audioLevel); // Hide low volumes. if (width < threshhold) { width = 0; @@ -68,8 +73,8 @@ define(['jquery', 'underscore'], function($, _) { this.meter = _.bind(function() { var talking; - if (this.active) { - var level = Math.round(100 * webrtc.usermedia.audioLevel); + if (this.active && usermedia) { + var level = Math.round(100 * usermedia.audioLevel); if (level < threshhold) { level = 0; } else { diff --git a/static/js/directives/audiovideo.js b/static/js/directives/audiovideo.js index f3727f12..c1f9d403 100644 --- a/static/js/directives/audiovideo.js +++ b/static/js/directives/audiovideo.js @@ -243,6 +243,10 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ mediaStream.webrtc.e.on("usermedia", function(event, usermedia) { + if (!usermedia || !usermedia.started) { + return; + } + //console.log("XXXX XXXXXXXXXXXXXXXXXXXXX usermedia event", usermedia); if ($scope.haveStreams) { diff --git a/static/js/mediastream/peerconference.js b/static/js/mediastream/peerconference.js index f57aa20a..e5ee4e08 100644 --- a/static/js/mediastream/peerconference.js +++ b/static/js/mediastream/peerconference.js @@ -40,6 +40,12 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall this.id = id; } + this.usermedia = null; + webrtc.e.on("usermedia", _.bind(function(event, um) { + console.log("Conference user media changed", um); + this.usermedia = um; + }, this)); + console.log("Created conference", this.id); }; @@ -97,8 +103,8 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall call.e.on("negotiationNeeded", _.bind(function(event, extracall) { this.webrtc.sendOfferWhenNegotiationNeeded(extracall); }, this)); - if (this.webrtc.usermedia) { - this.webrtc.usermedia.addToPeerConnection(peerconnection); + if (this.usermedia) { + this.usermedia.addToPeerConnection(peerconnection); } /*call.createOffer(_.bind(function(sessionDescription, extracall) { console.log("Sending offer with sessionDescription", sessionDescription, extracall.id); @@ -143,8 +149,8 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall call.createPeerConnection(_.bind(function(peerconnection) { // Success call. call.setRemoteDescription(rtcsdp, _.bind(function() { - if (this.webrtc.usermedia) { - this.webrtc.usermedia.addToPeerConnection(peerconnection); + if (this.usermedia) { + this.usermedia.addToPeerConnection(peerconnection); } call.createAnswer(_.bind(function(sessionDescription, extracall) { console.log("Sending answer", sessionDescription, extracall.id); diff --git a/static/js/mediastream/usermedia.js b/static/js/mediastream/usermedia.js index 285fecff..d2bac007 100644 --- a/static/js/mediastream/usermedia.js +++ b/static/js/mediastream/usermedia.js @@ -24,11 +24,6 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ // Create AudioContext singleton, if supported. var context = AudioContext ? new AudioContext() : null; - var peerconnections = {}; - - // Disabled for now until browser support matures. If enabled this totally breaks - // Firefox and Chrome with Firefox interop. - var enableRenegotiationSupport = false; // Converter helpers to convert media constraints to new API. var mergeConstraints = function(constraints, k, v, mandatory) { @@ -131,15 +126,21 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ this.localStream = null; this.started = false; - this.delay = 0; + //this.delay = 0; - this.audioMute = false; - this.videoMute = false; + this.peerconnections = {}; + + // If true, mute/unmute of audio/video creates a new stream which + // will trigger renegotiation on the peer connection. + this.renegotiation = options.renegotiation && true; + + this.audioMute = options.audioMute && true; + this.videoMute = options.videoMute && true; this.mediaConstraints = null; // Audio level. this.audioLevel = 0; - if (!this.options.noaudio && context && context.createScriptProcessor) { + if (!this.options.noAudio && context && context.createScriptProcessor) { this.audioSource = null; this.audioProcessor = context.createScriptProcessor(2048, 1, 1); @@ -185,7 +186,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ this.e.on("localstream", _.bind(function(event, stream, oldstream) { // Update stream support. if (oldstream) { - _.each(peerconnections, function(pc) { + _.each(this.peerconnections, function(pc) { pc.removeStream(oldstream); pc.addStream(stream); console.log("Updated usermedia stream at peer connection", pc, stream); @@ -319,7 +320,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ // Let webrtc handle the rest. setTimeout(_.bind(function() { this.e.triggerHandler("mediasuccess", [this]); - }, this), this.delay); + }, this), 0); } // Get notified of end events. @@ -357,11 +358,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ this.mediaConstraints = null; console.log("Stopped user media."); this.e.triggerHandler("stopped", [this]); - - this.delay = 1500; - setTimeout(_.bind(function() { - this.delay = 0; - }, this), 2000); + this.e.off(); }; @@ -369,7 +366,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ var m = !!mute; - if (!enableRenegotiationSupport) { + if (!this.renegotiation) { // Disable streams only - does not require renegotiation but keeps mic // active and the stream will transmit silence. @@ -418,7 +415,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ var m = !!mute; - if (!enableRenegotiationSupport) { + if (!this.renegotiation) { // Disable streams only - does not require renegotiation but keeps camera // active and the stream will transmit black. @@ -443,7 +440,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ } } else { - // Removevideo stream, by creating a new stream and doing renegotiation. This + // Remove video stream, by creating a new stream and doing renegotiation. This // is the way to go to disable the camera when video is muted. if (this.localStream) { @@ -467,11 +464,11 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ if (this.localStream) { pc.addStream(this.localStream); var id = pc.id; - if (!peerconnections.hasOwnProperty(id)) { - peerconnections[id] = pc; - pc.currentcall.e.one("closed", function() { - delete peerconnections[id]; - }); + if (!this.peerconnections.hasOwnProperty(id)) { + this.peerconnections[id] = pc; + pc.currentcall.e.one("closed", _.bind(function() { + delete this.peerconnections[id]; + }, this)); } } @@ -482,8 +479,8 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ console.log("Remove usermedia stream from peer connection", pc, this.localStream); if (this.localStream) { pc.removeStream(this.localStream); - if (peerconnections.hasOwnProperty(pc.id)) { - delete peerconnections[pc.id]; + if (this.peerconnections.hasOwnProperty(pc.id)) { + delete this.peerconnections[pc.id]; } } diff --git a/static/js/mediastream/webrtc.js b/static/js/mediastream/webrtc.js index 17405cd2..2e2e7bd2 100644 --- a/static/js/mediastream/webrtc.js +++ b/static/js/mediastream/webrtc.js @@ -54,6 +54,8 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u this.started = false; this.initiator = null; + + this.usermedia = null; this.audioMute = false; this.videoMute = false; @@ -112,7 +114,8 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u videoSendCodec: "VP8/90000" //videoRecvBitrate: , //videoRecvCodec - } + }, + renegotiation: true }; this.screensharingSettings = { @@ -121,17 +124,6 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u this.api.e.bind("received.offer received.candidate received.answer received.bye received.conference", _.bind(this.processReceived, this)); - // Create default media (audio/video). - this.usermedia = new UserMedia(); - this.usermedia.e.on("mediasuccess mediaerror", _.bind(function() { - // Start always, no matter what. - this.maybeStart(); - }, this)); - this.usermedia.e.on("mediachanged", _.bind(function() { - // Propagate media change events. - this.e.triggerHandler("usermedia", [this.usermedia]); - }, this)); - }; WebRTC.prototype.processReceived = function(event, to, data, type, to2, from) { @@ -396,6 +388,39 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u }; + WebRTC.prototype.doUserMedia = function(currentcall) { + + // Create default media (audio/video). + var usermedia = new UserMedia({ + renegotiation: this.settings.renegotiation, + audioMute: this.audioMute, + videoMute: this.videoMute + }); + usermedia.e.on("mediasuccess mediaerror", _.bind(function(event, um) { + this.e.triggerHandler("usermedia", [usermedia]); + // Start always, no matter what. + this.maybeStart(um); + }, this)); + usermedia.e.on("mediachanged", _.bind(function(event, um) { + // Propagate media change events. + this.e.triggerHandler("usermedia", [um]); + }, this)); + usermedia.e.on("stopped", _.bind(function(event, um) { + if (um === this.usermedia) { + this.e.triggerHandler("usermedia", [null]); + this.usermedia = null; + } + }, this)); + this.e.one("stop", function() { + usermedia.stop(); + }); + this.usermedia = usermedia; + this.e.triggerHandler("usermedia", [usermedia]); + + return usermedia.doGetUserMedia(currentcall); + + }; + WebRTC.prototype.doCall = function(id) { if (this.currentcall) { @@ -413,7 +438,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u } else { var currentcall = this.currentcall = this.createCall(id, null, id); this.e.triggerHandler("peercall", [currentcall]); - var ok = this.usermedia.doGetUserMedia(currentcall); + var ok = this.doUserMedia(currentcall); if (ok) { this.e.triggerHandler("waitforusermedia", [currentcall]); } else { @@ -432,7 +457,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u console.warn("Trying to accept without a call.", currentcall); return; } - var ok = this.usermedia.doGetUserMedia(currentcall); + var ok = this.doUserMedia(currentcall); if (ok) { this.e.triggerHandler("waitforusermedia", [currentcall]); } else { @@ -501,7 +526,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u WebRTC.prototype.doScreenshare = function(options) { var usermedia = new UserMedia({ - noaudio: true + noAudio: true }); var ok = usermedia.doGetUserMedia(null, PeerScreenshare.getCaptureMediaConstraints(this, options)); if (ok) { @@ -579,11 +604,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u this.currentcall.close(); this.currentcall = null; } - if (this.usermedia) { - this.usermedia.stop(); - } this.e.triggerHandler("peerconference", [null]); this.e.triggerHandler("peercall", [null]); + this.e.triggerHandler("stop"); this.msgQueue.length = 0; this.initiator = null; this.started = false; @@ -629,7 +652,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u } - WebRTC.prototype.maybeStart = function() { + WebRTC.prototype.maybeStart = function(usermedia) { //console.log("maybeStart", this.started); if (!this.started) { @@ -640,14 +663,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u console.log('Creating PeerConnection.', currentcall); currentcall.createPeerConnection(_.bind(function(peerconnection) { // Success call. - if (this.usermedia) { - this.usermedia.applyVideoMute(this.videoMute); - this.usermedia.applyAudioMute(this.audioMute); - this.e.triggerHandler("usermedia", [this.usermedia]); - this.usermedia.addToPeerConnection(peerconnection); - } else { - _.defer(peerconnection.negotiationNeeded); - } + usermedia.addToPeerConnection(peerconnection); this.started = true; if (!this.initiator) { this.calleeStart(); diff --git a/static/js/services/mediastream.js b/static/js/services/mediastream.js index b116b3fc..899e39a2 100644 --- a/static/js/services/mediastream.js +++ b/static/js/services/mediastream.js @@ -43,6 +43,10 @@ define([ // Create encryption key from server token and browser name. var secureKey = sjcl.codec.base64.fromBits(sjcl.hash.sha256.hash(context.Cfg.Token + uaparser().browser.name)); + // Apply configuration details. + webrtc.settings.renegotiation = context.Cfg.WebRTC ? context.Cfg.WebRTC.Renegotiation : false; + + // mediaStream service API. var mediaStream = { version: version, ws: url,