diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index 17b4e0f4..0b9df218 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -20,7 +20,7 @@ */ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'webrtc.adapter'], function($, _, angular, BigScreen, moment, sjcl, Modernizr) { - return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", "screensharing", "userSettingsData", "localStatus", "dialogs", "rooms", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage, screensharing, userSettingsData, localStatus, dialogs, rooms) { + return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", "screensharing", "userSettingsData", "localStatus", "dialogs", "rooms", "constraints", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage, screensharing, userSettingsData, localStatus, dialogs, rooms, constraints) { /*console.log("route", $route, $routeParams, $location);*/ @@ -86,29 +86,6 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder appData.set($scope); - var videoQualityMap = { - tiny: { - maxWidth: 80, - maxHeight: 45 - }, - low: { - maxWidth: 320, - maxHeight: 180 - }, - high: { - maxWidth: 640, - maxHeight: 360 - }, - hd: { - minWidth: 1280, - minHeight: 720 - }, - fullhd: { - minWidth: 1920, - minHeight: 1080 - } - } - var displayName = safeDisplayName; // Init STUN and TURN servers. @@ -219,9 +196,10 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder console.warn("This is not a WebRTC capable browser."); return; } + var settings = $scope.master.settings; - // Create iceServers from scope settings. + // Create iceServers from scope. var iceServers = []; var iceServer; if ($scope.stun.length) { @@ -236,139 +214,13 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder iceServers.push.apply(iceServers, iceServer); } } + mediaStream.webrtc.settings.pcConfig.iceServers = iceServers; - var audioConstraints = []; - var videoConstraints = []; - var videoConstraintsMandatory = {}; - var screensharingConstraints = []; - - var pushmulti = function(arrays, data) { - _.each(arrays, function(a) { - a.push(data); - }); - }; - - // Chrome only constraints. - if ($scope.isChrome) { - // Audio settings. - // For defaults in Chromium see https://code.google.com/p/webrtc/source/browse/trunk/talk/media/webrtc/webrtcvoiceengine.cc#225 - - // Experimental audio settings. - if (settings.experimental.enabled) { - - audioConstraints.push({ - googEchoCancellation: true // defaults to true - }); - audioConstraints.push({ - googEchoCancellation2: settings.experimental.audioEchoCancellation2 && true // defaults to false in Chrome - }); - audioConstraints.push({ - googAutoGainControl: true // defaults to true - }); - audioConstraints.push({ - googAutoGainControl2: settings.experimental.audioAutoGainControl2 && true // defaults to false in Chrome - }); - audioConstraints.push({ - googNoiseSuppression: true // defaults to true - }); - audioConstraints.push({ - googgNoiseSuppression2: settings.experimental.audioNoiseSuppression2 && true // defaults to false in Chrome - }); - audioConstraints.push({ - googHighpassFilter: true // defaults to true - }); - audioConstraints.push({ - googTypingNoiseDetection: settings.experimental.audioTypingNoiseDetection && true // defaults to true in Chrome - }); - - } - - if ($scope.supported.renderToAssociatedSink) { - audioConstraints.push({ - // When true uses the default communications device on Windows. - // https://codereview.chromium.org/155863003 - googDucking: true // defaults to true on Windows. - }); - audioConstraints.push({ - // Chrome will start rendering mediastream output to an output device that's associated with - // the input stream that was opened via getUserMedia. - // https://chromiumcodereview.appspot.com/23558010 - chromeRenderToAssociatedSink: settings.audioRenderToAssociatedSkin && true // defaults to false in Chrome - }); - } - - // Select microphone device by id. - if (settings.microphoneId) { - audioConstraints.push({ - sourceId: settings.microphoneId - }); - } - // Select camera by device id. - if (settings.cameraId) { - videoConstraints.push({ - sourceId: settings.cameraId - }); - } - - // Video settings. - if (settings.experimental.enabled) { - - // Experimental video settings. - pushmulti([videoConstraints, screensharingConstraints], { - // Changes the way the video encoding adapts to the available bandwidth. - // https://code.google.com/p/webrtc/issues/detail?id=3351 - googLeakyBucket: settings.experimental.videoLeakyBucket && true // defaults to false in Chrome - }); - pushmulti([videoConstraints, screensharingConstraints], { - // Removes the noise in the captured video stream at the expense of CPU. - googNoiseReduction: settings.experimental.videoNoiseReduction && true // defaults to false in Chrome - }); - pushmulti([videoConstraints, screensharingConstraints], { - googCpuOveruseDetection: settings.experimental.videoCpuOveruseDetection && true // defaults to true in Chrome - }); - - } - - // Video. - videoConstraintsMandatory = $.extend(videoConstraintsMandatory, videoQualityMap[settings.videoQuality]); - // Not supported as of Firefox 27. - if (settings.maxFrameRate && settings.maxFrameRate != "auto") { - videoConstraintsMandatory.maxFrameRate = parseInt(settings.maxFrameRate, 10); - } - } - - // Apply the shit. + // Stereo. mediaStream.webrtc.settings.stereo = settings.stereo; - mediaStream.webrtc.settings.mediaConstraints.video.mandatory = videoConstraintsMandatory; - mediaStream.webrtc.settings.mediaConstraints.video.optional = videoConstraints; - mediaStream.webrtc.settings.mediaConstraints.audio = { - optional: audioConstraints - }; - mediaStream.webrtc.settings.pcConfig.iceServers = iceServers; - mediaStream.webrtc.settings.screensharing.mediaConstraints.video.optional = screensharingConstraints; - - // Inject optional stuff. - var optionalPcConstraints = mediaStream.webrtc.settings.pcConstraints.optional = []; - if ($window.webrtcDetectedBrowser === "chrome") { - // NOTE(longsleep): We can always enable SCTP data channels, as we have a workaround - // using the "active" event for Firefox < 27. - // SCTP does not work correctly with Chrome 31. Require M32. - if ($window.webrtcDetectedVersion >= 32) { - // SCTP is supported from Chrome M31. - // No need to pass DTLS constraint as it is on by default in Chrome M31. - // For SCTP, reliable and ordered is true by default. - } else { - // Chrome < M32 does not yet do DTLS-SRTP by default whereas Firefox only - // does DTLS-SRTP. In order to get interop, you must supply Chrome - // with a PC constructor constraint to enable DTLS. - console.warn("Turning on SCTP combatibility - please update your Chrome."); - optionalPcConstraints.push({ - DtlsSrtpKeyAgreement: true - }); - } - } - //console.log("WebRTC settings", mediaStream.webrtc.settings); + // Refresh constraints. + constraints.refresh($scope.master.settings); }; $scope.refreshWebrtcSettings(); // Call once for bootstrap. diff --git a/static/js/directives/settings.js b/static/js/directives/settings.js index b8dd5173..c734a0a7 100644 --- a/static/js/directives/settings.js +++ b/static/js/directives/settings.js @@ -20,9 +20,32 @@ */ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, template) { + var videoQualityMap = { + tiny: { + maxWidth: 80, + maxHeight: 45 + }, + low: { + maxWidth: 320, + maxHeight: 180 + }, + high: { + maxWidth: 640, + maxHeight: 360 + }, + hd: { + minWidth: 1280, + minHeight: 720 + }, + fullhd: { + minWidth: 1920, + minHeight: 1080 + } + }; + return ["$compile", "mediaStream", function($compile, mediaStream) { - var controller = ['$scope', 'desktopNotify', 'mediaSources', 'safeApply', 'availableLanguages', 'translation', 'localStorage', 'userSettingsData', function($scope, desktopNotify, mediaSources, safeApply, availableLanguages, translation, localStorage, userSettingsData) { + var controller = ['$scope', 'desktopNotify', 'mediaSources', 'safeApply', 'availableLanguages', 'translation', 'localStorage', 'userSettingsData', 'constraints', function($scope, desktopNotify, mediaSources, safeApply, availableLanguages, translation, localStorage, userSettingsData, constraints) { $scope.layout.settings = false; $scope.showAdvancedSettings = true; @@ -131,17 +154,88 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t } }); - }]; + constraints.e.on("refresh", function(event, constraints) { + + var settings = $scope.master.settings; + + // Chrome only constraints. + if ($scope.isChrome) { + + // For defaults in Chromium see https://code.google.com/p/webrtc/source/browse/trunk/talk/media/webrtc/webrtcvoiceengine.cc#225 + + // Experimental audio settings. + if (settings.experimental.enabled) { + constraints.add("audio", "googEchoCancellation", true); // defaults to true + constraints.add("audio", "googEchoCancellation2", settings.experimental.audioEchoCancellation2 && true); // defaults to false in Chrome + constraints.add("audio", "googAutoGainControl", true); // defaults to true + constraints.add("audio", "googAutoGainControl2", settings.experimental.audioAutoGainControl2 && true); // defaults to false in Chrome + constraints.add("audio", "googNoiseSuppression", true); // defaults to true + constraints.add("audio", "googgNoiseSuppression2", settings.experimental.audioNoiseSuppression2 && true); // defaults to false in Chrome + constraints.add("audio", "googHighpassFilter", true); // defaults to true + constraints.add("audio", "googTypingNoiseDetection", settings.experimental.audioTypingNoiseDetection && true); // defaults to true in Chrome + } + + if ($scope.supported.renderToAssociatedSink) { + // When true uses the default communications device on Windows. + // https://codereview.chromium.org/155863003 + constraints.add("audio", "googDucking", true); // defaults to true on Windows. + // Chrome will start rendering mediastream output to an output device that's associated with + // the input stream that was opened via getUserMedia. + // https://chromiumcodereview.appspot.com/23558010 + constraints.add("audio", "chromeRenderToAssociatedSink", settings.audioRenderToAssociatedSkin && true); // defaults to false in Chrome + } + + // Select microphone device by id. + if (settings.microphoneId) { + constraints.add("audio", "sourceId", settings.microphoneId); + } + + // Select camera by device id. + if (settings.cameraId) { + constraints.add("video", "sourceId", settings.cameraId); + } + + // Experimental video settings. + if (settings.experimental.enabled) { + + // Changes the way the video encoding adapts to the available bandwidth. + // https://code.google.com/p/webrtc/issues/detail?id=3351 + constraints.add(["video", "screensharing"], "googLeakyBucket", settings.experimental.videoLeakyBucket && true); // defaults to false in Chrome + // Removes the noise in the captured video stream at the expense of CPU. + constraints.add(["video", "screensharing"], "googNoiseReduction", settings.experimental.videoNoiseReduction && true); // defaults to false in Chrome + constraints.add(["video", "screensharing"], "googCpuOveruseDetection", settings.experimental.videoCpuOveruseDetection && true) // defaults to true in Chrome - var link = function($scope, $element) {}; + } + + // Set video quality. + var videoQuality = videoQualityMap[settings.videoQuality]; + if (videoQuality) { + _.forEach(videoQuality, function(v, k) { + constraints.add("video", k, v, true); + }); + } + + // Set max frame rate if any was selected. + if (settings.maxFrameRate && settings.maxFrameRate != "auto") { + constraints.add("video", "maxFrameRate", parseInt(settings.maxFrameRate, 10), true); + } + + } else { + + // Other browsers constraints (there are none as of now.); + + } + + }); + + }]; return { scope: true, restrict: 'E', replace: true, template: template, - controller: controller, - link: link + controller: controller }; }]; diff --git a/static/js/services/constraints.js b/static/js/services/constraints.js new file mode 100644 index 00000000..86a4331d --- /dev/null +++ b/static/js/services/constraints.js @@ -0,0 +1,134 @@ +/* + * 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 . + * + */ + define(["jquery", "underscore"], function($, _) { + + // constraints + return ["webrtc", "$window", function(webrtc, $window) { + + var service = this; + + var Constraints = function(settings) { + this.settings = _.clone(settings, true); + this.pc = []; + this.audio = []; + this.video = []; + this.videoMandatory = {}; + this.screensharing =[]; + }; + + Constraints.prototype.add = function(t, k, v, mandatory) { + if (_.isArray(t)) { + _.forEach(t, function(x) { + this.add(x, k, v, mandatory); + }, this); + return; + } + var obj; + if (mandatory) { + t = t + "Mandatory"; + } + obj = this[t]; + if (!obj) { + console.warn("Pushed to unknown constraint", t, k, v, mandatory); + } else { + if (mandatory) { + // Mandatory constraints are key/values. + obj[k] = v; + } else { + // Optional constraints are arrays. + var d = {}; + d[k] = v; + obj.push(d) + } + } + }; + + Constraints.prototype.set = function(t, data, mandatory) { + if (mandatory) { + t = t + "Mandatory"; + } + if (!this[t]) { + console.warn("Set to unknown constraint", t, data, mandatory); + } else { + this[t] = data; + } + }; + + service.e = $({}); // events + + service.mediaConstraints = function(constraints) { + webrtc.settings.mediaConstraints.audio = { + optional: constraints.audio + }; + webrtc.settings.mediaConstraints.video = { + optional: constraints.video, + mandatory: constraints.videoMandatory + }; + webrtc.settings.screensharing.mediaConstraints.video.optional = constraints.screensharing; + }; + + service.pcConstraints = function(constraints) { + webrtc.settings.pcConstraints.optional = constraints.pc; + }; + + // Some default constraints. + service.e.on("refresh", function(event, constraints) { + + if ($window.webrtcDetectedBrowser === "chrome") { + // NOTE(longsleep): We can always enable SCTP data channels, as we have a workaround + // using the "active" event for Firefox < 27. + // SCTP does not work correctly with Chrome 31. Require M32. + if ($window.webrtcDetectedVersion >= 32) { + // SCTP is supported from Chrome M31. + // No need to pass DTLS constraint as it is on by default in Chrome M31. + // For SCTP, reliable and ordered is true by default. + } else { + // Chrome < M32 does not yet do DTLS-SRTP by default whereas Firefox only + // does DTLS-SRTP. In order to get interop, you must supply Chrome + // with a PC constructor constraint to enable DTLS. + console.warn("Turning on SCTP combatibility - please update your Chrome."); + constraints.add("pc", "DtlsSrtpKeyAgreement", true); + } + } + + }); + + // Public API. + return { + e: service.e, + refresh: function(settings) { + var constraints = new Constraints(settings); + service.e.triggerHandler("refresh", [constraints]); + service.mediaConstraints(constraints); + service.pcConstraints(constraints); + }, + turn: function(turnData) { + // Set TURN server details. + service.turn = turnData; + }, + stun: function(stunData) { + service.stun = stunData; + } + }; + + }]; + + }); \ No newline at end of file diff --git a/static/js/services/services.js b/static/js/services/services.js index b5a9975b..f9065458 100644 --- a/static/js/services/services.js +++ b/static/js/services/services.js @@ -62,7 +62,8 @@ define([ 'services/localstatus', 'services/rooms', 'services/resturl', - 'services/roompin'], function(_, + 'services/roompin', + 'services/constraints'], function(_, desktopNotify, playSound, safeApply, @@ -104,7 +105,8 @@ userSettingsData, localStatus, rooms, restURL, -roompin) { +roompin, +constraints) { var services = { desktopNotify: desktopNotify, @@ -148,7 +150,8 @@ roompin) { localStatus: localStatus, rooms: rooms, restURL: restURL, - roompin: roompin + roompin: roompin, + constraints: constraints }; var initialize = function(angModule) {