Browse Source

Merge pull request #188 from longsleep/ff-screensharing-candidate

Worked to get screen sharing to FF.
pull/193/head
Simon Eisenmann 10 years ago
parent
commit
dab64f7d1f
  1. 2
      static/js/controllers/mediastreamcontroller.js
  2. 8
      static/js/directives/buddypicturecapture.js
  3. 58
      static/js/directives/settings.js
  4. 98
      static/js/mediastream/usermedia.js
  5. 18
      static/js/services/constraints.js
  6. 62
      static/js/services/mediadevices.js
  7. 48
      static/js/services/screensharing.js
  8. 9
      static/js/services/services.js
  9. 22
      static/partials/screensharedialogff.html
  10. 28
      static/partials/settings.html

2
static/js/controllers/mediastreamcontroller.js

@ -111,7 +111,7 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder @@ -111,7 +111,7 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder
// Add support status.
$scope.supported = {
screensharing: screensharing.supported,
renderToAssociatedSink: $window.navigator.platform.indexOf("Win") === 0
constraints: constraints.supported
}
// Default scope data.

8
static/js/directives/buddypicturecapture.js

@ -25,7 +25,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct @@ -25,7 +25,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct
// buddyPictureCapture
return ["$compile", "$window", function($compile, $window) {
var controller = ['$scope', 'safeApply', '$timeout', '$q', function($scope, safeApply, $timeout, $q) {
var controller = ['$scope', 'safeApply', '$timeout', '$q', "mediaDevices", function($scope, safeApply, $timeout, $q, mediaDevices) {
// Buddy picutre capture size.
$scope.captureSize = {
@ -126,16 +126,16 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct @@ -126,16 +126,16 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct
}]
};
}
$window.getUserMedia({
mediaDevices.getUserMedia({
video: videoConstraints
}, function(stream) {
}).then(function(stream) {
$scope.showTakePicture = true;
localStream = stream;
$scope.waitingForPermission = false;
$window.attachMediaStream($scope.video, stream);
safeApply($scope);
videoAllowed.resolve(true);
}, function(error) {
}).catch(function(error) {
console.error('Failed to get access to local media. Error code was ' + error.code);
$scope.waitingForPermission = false;
safeApply($scope);

58
static/js/directives/settings.js

@ -171,29 +171,29 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -171,29 +171,29 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
$timeout($scope.maybeShowSettings);
});
constraints.e.on("refresh", function(event, constraints) {
constraints.e.on("refresh", function(event, c) {
var settings = $scope.master.settings;
// Assert that selected devices are there.
(function() {
var deferred = constraints.defer();
var deferred = c.defer();
mediaSources.refresh(function() {
$scope.checkDefaultMediaSources();
// Select microphone device by id.
if (settings.microphoneId) {
constraints.add("audio", "sourceId", settings.microphoneId);
c.add("audio", "sourceId", settings.microphoneId);
}
// Select camera by device id.
if (settings.cameraId) {
constraints.add("video", "sourceId", settings.cameraId);
c.add("video", "sourceId", settings.cameraId);
}
if (!mediaSources.hasAudio()) {
constraints.disable('audio');
c.disable('audio');
console.info("Disabled audio input as no audio source was found.");
}
if (!mediaSources.hasVideo()) {
constraints.disable('video');
c.disable('video');
console.info("Disabled video input as no video source was found.");
}
deferred.resolve("complete");
@ -201,7 +201,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -201,7 +201,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
})();
// Chrome only constraints.
if ($scope.isChrome) {
if (constraints.supported.chrome) {
// Chrome specific constraints overview:
// https://code.google.com/p/webrtc/source/browse/trunk/talk/app/webrtc/mediaconstraintsinterface.cc
@ -211,24 +211,24 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -211,24 +211,24 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
// 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
c.add("audio", "googEchoCancellation", true); // defaults to true
c.add("audio", "googEchoCancellation2", settings.experimental.audioEchoCancellation2 && true); // defaults to false in Chrome
c.add("audio", "googAutoGainControl", true); // defaults to true
c.add("audio", "googAutoGainControl2", settings.experimental.audioAutoGainControl2 && true); // defaults to false in Chrome
c.add("audio", "googNoiseSuppression", true); // defaults to true
c.add("audio", "googgNoiseSuppression2", settings.experimental.audioNoiseSuppression2 && true); // defaults to false in Chrome
c.add("audio", "googHighpassFilter", true); // defaults to true
c.add("audio", "googTypingNoiseDetection", settings.experimental.audioTypingNoiseDetection && true); // defaults to true in Chrome
}
if ($scope.supported.renderToAssociatedSink) {
if (constraints.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.
c.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
c.add("audio", "chromeRenderToAssociatedSink", settings.audioRenderToAssociatedSkin && true); // defaults to false in Chrome
}
// Experimental video settings.
@ -236,44 +236,44 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -236,44 +236,44 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
// 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
c.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("pc", "googCpuOveruseDetection", settings.experimental.videoCpuOveruseDetection && true); // defaults to true in Chrome
c.add(["video", "screensharing"], "googNoiseReduction", settings.experimental.videoNoiseReduction && true); // defaults to false in Chrome
c.add("pc", "googCpuOveruseDetection", settings.experimental.videoCpuOveruseDetection && true); // defaults to true in Chrome
}
}
if (constraints.supported.audioVideo) {
// Set video quality.
var videoQuality = videoQualityMap[settings.videoQuality];
if (videoQuality) {
var mandatory = videoQuality.mandatory;
_.forEach(videoQuality, function(v, k) {
if (k !== "mandatory") {
constraints.add("video", k, v, mandatory ? false : true);
c.add("video", k, v, mandatory ? false : true);
}
});
if (mandatory) {
_.forEach(mandatory, function(v, k) {
constraints.add("video", k, v, true);
c.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);
c.add("video", "maxFrameRate", parseInt(settings.maxFrameRate, 10), true);
}
// Disable AEC if stereo.
// https://github.com/webrtc/apprtc/issues/23
if (settings.sendStereo) {
constraints.add("audio", "echoCancellation", false);
c.add("audio", "echoCancellation", false);
}
} else {
// Other browsers constraints (there are none as of now.);
}
});

98
static/js/mediastream/usermedia.js

@ -30,6 +30,100 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -30,6 +30,100 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
// 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) {
var prefix = k.substring(0, 3);
switch (prefix) {
case "min":
case "max":
var suffix = k[3].toLowerCase()+k.substring(4);
if (!constraints.hasOwnProperty(suffix)) {
constraints[suffix]={};
}
if (mandatory && prefix === "min" && constraints[suffix].hasOwnProperty(prefix)) {
// Use existing min constraint as ideal.
constraints[suffix].ideal = constraints[suffix].min;
}
constraints[suffix][prefix]=v;
break;
default:
constraints[k] = v;
break;
}
};
var convertConstraints = function(constraints) {
if (!constraints) {
return false;
}
if (!constraints.hasOwnProperty("optional") && !constraints.hasOwnProperty("mandatory")) {
// No old style members.
return constraints;
}
var c = {};
// Process optional constraints.
if (constraints.optional) {
_.each(constraints.optional, function(o) {
_.each(o, function(v, k) {
mergeConstraints(c, k, v);
})
});
}
// Process mandatory constraints.
if (constraints.mandatory) {
_.each(constraints.mandatory, function(v, k) {
mergeConstraints(c, k, v, true);
});
}
// Fastpath.
if (_.isEmpty(c)) {
return true;
}
// Use ideal if there is only one value set.
_.each(c, function(v, k) {
if (_.isObject(v)) {
var values = _.values(v);
if (values.length === 1) {
// Use as ideal value if only one given.
c[k] = {ideal: values[0]};
}
}
});
if (window.webrtcDetectedBrowser === "firefox" && window.webrtcDetectedVersion < 38) {
// Firefox < 38 needs a extra require field.
var r = [];
if (c.height) {
r.push("height");
}
if (c.width) {
r.push("width");
}
if (r.length) {
c.require = r;
}
}
return c;
};
// Adapter to support navigator.mediaDevices API.
// http://w3c.github.io/mediacapture-main/getusermedia.html#mediadevices
var getUserMedia = (function() {
if (window.navigator.mediaDevices) {
console.info("Enabled mediaDevices adapter ...");
return function(constraints, success, error) {
// Full constraints syntax with plain values and ideal-algorithm supported in FF38+.
// Note on FF32-37: Plain values and ideal are not supported.
// See https://wiki.mozilla.org/Media/getUserMedia for details.
// Examples here: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
var c = {audio: convertConstraints(constraints.audio), video: convertConstraints(constraints.video)};
// mediaDevices API returns a promise.
console.log("Constraints for mediaDevices", c);
window.navigator.mediaDevices.getUserMedia(c).then(success).catch(error);
}
} else {
// Use existing adapter.
return window.getUserMedia;
}
})();
var UserMedia = function(options) {
this.options = $.extend({}, options);
@ -128,7 +222,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -128,7 +222,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
error_cb.apply(this, args);
};
try {
window.getUserMedia({
getUserMedia({
video: true,
audio: true
}, success_helper, error_helper);
@ -173,7 +267,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -173,7 +267,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
try {
console.log('Requesting access to local media with mediaConstraints:\n' +
' \'' + JSON.stringify(constraints) + '\'', constraints);
window.getUserMedia(constraints, _.bind(this.onUserMediaSuccess, this), _.bind(this.onUserMediaError, this));
getUserMedia(constraints, _.bind(this.onUserMediaSuccess, this), _.bind(this.onUserMediaError, this));
this.started = true;
return true;
} catch (e) {

18
static/js/services/constraints.js

@ -187,7 +187,23 @@ @@ -187,7 +187,23 @@
},
stun: function(stunData) {
service.stun = stunData;
}
},
supported: (function() {
var isChrome = $window.webrtcDetectedBrowser === "chrome";
var isFirefox = $window.webrtcDetectedBrowser === "firefox";
var version = $window.webrtcDetectedVersion;
// Constraints support table.
return {
// Chrome supports it. FF supports new spec starting 38. See https://wiki.mozilla.org/Media/getUserMedia for FF details.
audioVideo: isChrome || (isFirefox && version >= 38),
// HD constraints in Chrome no issue. In FF we MJPEG is fixed with 40 (see https://bugzilla.mozilla.org/show_bug.cgi?id=1151628).
hdVideo: isChrome || (isFirefox && version >= 40),
// Chrome supports this on Windows only.
renderToAssociatedSink: isChrome && $window.navigator.platform.indexOf("Win") === 0,
chrome: isChrome,
firefox: isFirefox
};
})()
};
}];

62
static/js/services/mediadevices.js

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
/* global Promise */
"use strict";
define(['webrtc.adapter'], function() {
// mediaDevices
return ["$window", function($window) {
var mediaDevices = $window.navigator.mediaDevices || {};
var getUserMedia = (function() {
// Implement a Promise based wrapper around getUserMedia.
if (mediaDevices.getUserMedia) {
// mediaDevices calls return Promise native.
return mediaDevices.getUserMedia.bind(mediaDevices);
} else {
return function getUserMedia(constraints) {
return new Promise(function(resolve, reject) {
var onSuccess = function(stream) {
resolve(stream)
};
var onError = function(error) {
reject(error);
};
try {
$window.getUserMedia(constraints, onSuccess, onError);
} catch(err) {
onError(err);
}
});
}
}
})();
// Public api.
return {
shim: mediaDevices.getUserMedia ? false : true,
getUserMedia: getUserMedia
}
}];
});

48
static/js/services/screensharing.js

@ -20,10 +20,22 @@ @@ -20,10 +20,22 @@
*/
"use strict";
define(['underscore', 'webrtc.adapter'], function(_) {
define(['underscore', 'text!partials/screensharedialogff.html', 'webrtc.adapter'], function(_, screenshareDialogFF) {
var screenshareDialogFFController = ["$scope", "$modalInstance", "data", function($scope, $modalInstance, data) {
$scope.data = data;
$scope.cancel = function() {
$modalInstance.close(null);
};
$scope.ok = function() {
$modalInstance.close($scope.data.selection);
};
}];
// screensharing
return ["$window", "$q", "$timeout", "chromeExtension", function($window, $q, $timeout, chromeExtension) {
return ["$window", "$q", "$timeout", "chromeExtension", "dialogs", "$templateCache", function($window, $q, $timeout, chromeExtension, dialogs, $templateCache) {
$templateCache.put('/dialogs/screensharedialogff.html', screenshareDialogFF);
var Screensharing = function() {
this.autoinstall = false;
@ -121,10 +133,36 @@ define(['underscore', 'webrtc.adapter'], function(_) { @@ -121,10 +133,36 @@ define(['underscore', 'webrtc.adapter'], function(_) {
}
} else {
// Currently Chrome only - sorry.
// Firefox 33 might get screen sharing support.
} else if ($window.webrtcDetectedBrowser === "firefox") {
// Firefox 36 got screen sharing support.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=923225
if ($window.webrtcDetectedVersion >= 36) {
this.supported = true;
this.prepare = function(options) {
// To work, the current domain must be whitelisted in
// media.getusermedia.screensharing.allowed_domains (about:config).
// See https://wiki.mozilla.org/Screensharing for reference.
var d = $q.defer();
var dlg = dialogs.create('/dialogs/screensharedialogff.html', screenshareDialogFFController, {selection: "screen"}, {});
dlg.result.then(function(source) {
if (source) {
var opts = _.extend({
mediaSource: source
}, options);
d.resolve(opts);
} else {
d.resolve(null);
}
}, function(err) {
d.resolve(null);
});
return d.promise;
};
}
} else {
// No support for screen sharing.
}
// Auto install support.

9
static/js/services/services.js

@ -66,7 +66,8 @@ define([ @@ -66,7 +66,8 @@ define([
'services/resturl',
'services/roompin',
'services/constraints',
'services/modules'], function(_,
'services/modules',
'services/mediadevices'], function(_,
desktopNotify,
playSound,
safeApply,
@ -110,7 +111,8 @@ rooms, @@ -110,7 +111,8 @@ rooms,
restURL,
roompin,
constraints,
modules) {
modules,
mediaDevices) {
var services = {
desktopNotify: desktopNotify,
@ -156,7 +158,8 @@ modules) { @@ -156,7 +158,8 @@ modules) {
restURL: restURL,
roompin: roompin,
constraints: constraints,
modules: modules
modules: modules,
mediaDevices: mediaDevices
};
var initialize = function(angModule) {

22
static/partials/screensharedialogff.html

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
<div>
<div class="modal-header"><button type="button" class="close" ng-click="$close()">×</button><h3 class="modal-title">{{_("Share screen")}}</h3></div>
<div class="modal-body">
<ng-form name="promptDialog" novalidate role="form">
<h4>{{_("Please select what to share.")}}</h4>
<div class="btn-group">
<label class="btn btn-default" ng-model="data.selection" btn-radio="'screen'">{{_("Screen")}}</label>
<label class="btn btn-default" ng-model="data.selection" btn-radio="'window'">{{_("Window")}}</label>
<label class="btn btn-default" ng-model="data.selection" btn-radio="'application'">{{_("Application")}}</label>
</div>
<div ng-switch="data.selection">
<span class="help-block" ng-switch-when="screen">Share the whole screen. Click share to select the screen.</span>
<span class="help-block" ng-switch-when="window">Share a single window. Click share to select the window.</span>
<span class="help-block" ng-switch-when="application">Share all windows of a application. This can leak content behind windows when windows get moved. Click share to select the application.</span>
</div>
</ng-form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="cancel()">{{_("Cancel")}}</button>
<button type="button" class="btn btn-primary" ng-click="ok()" ng-disabled="(promptDialog.$dirty && promptDialog.$invalid) || !data.selection">{{_("Share")}}</button>
</div>
</div>

28
static/partials/settings.html

@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
</div>
</div>
</settings-account>
<settings-media ng-show="mediaSources.supported || isChrome">
<settings-media ng-show="mediaSources.supported || supported.constraints.audioVideo">
<legend>{{_('Media')}}</legend>
<div ng-show="mediaSources.supported">
<div class="form-group">
@ -82,14 +82,14 @@ @@ -82,14 +82,14 @@
</div>
</div>
</div>
<div ng-show="isChrome" class="form-group">
<div ng-show="supported.constraints.audioVideo" class="form-group">
<label class="col-xs-4 control-label">{{_('Video quality')}}</label>
<div class="col-xs-8">
<div class="btn-group">
<button type="button" class="btn btn-default" ng-model="user.settings.videoQuality" btn-radio="'low'">{{_('Low')}}</button>
<button type="button" class="btn btn-default" ng-model="user.settings.videoQuality" btn-radio="'high'">{{_('High')}}</button>
<button type="button" class="btn btn-default" ng-model="user.settings.videoQuality" btn-radio="'hd'">{{_('HD')}}</button>
<button type="button" class="btn btn-default" ng-model="user.settings.videoQuality" btn-radio="'fullhd'">{{_('Full HD')}}</button>
<button ng-if="supported.constraints.hdVideo" type="button" class="btn btn-default" ng-model="user.settings.videoQuality" btn-radio="'hd'">{{_('HD')}}</button>
<button ng-if="supported.constraints.hdVideo" type="button" class="btn btn-default" ng-model="user.settings.videoQuality" btn-radio="'fullhd'">{{_('Full HD')}}</button>
</div>
</div>
</div>
@ -130,7 +130,7 @@ @@ -130,7 +130,7 @@
<div>
<div class="form-group" ng-show="isChrome && supported.renderToAssociatedSink">
<div class="form-group" ng-show="supported.constraints.renderToAssociatedSink">
<label class="col-xs-4 control-label">{{_('Play audio on same device as selected microphone')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -143,7 +143,7 @@ @@ -143,7 +143,7 @@
<div ng-show="user.settings.experimental.enabled">
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Experimental AEC')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -154,7 +154,7 @@ @@ -154,7 +154,7 @@
</div>
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Experimental AGC')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -165,7 +165,7 @@ @@ -165,7 +165,7 @@
</div>
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Experimental noise suppression')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -178,7 +178,7 @@ @@ -178,7 +178,7 @@
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.audioVideo">
<label class="col-xs-4 control-label">{{_('Max video frame rate')}}</label>
<div class="col-xs-8">
<div class="btn-group">
@ -191,7 +191,7 @@ @@ -191,7 +191,7 @@
</div>
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Send stereo audio')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -203,7 +203,7 @@ @@ -203,7 +203,7 @@
</div>
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Detect CPU over use')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -217,7 +217,7 @@ @@ -217,7 +217,7 @@
<div ng-show="user.settings.experimental.enabled">
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Optimize for high resolution video')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -228,7 +228,7 @@ @@ -228,7 +228,7 @@
</div>
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Reduce video noise')}}</label>
<div class="col-xs-8">
<div class="checkbox">
@ -241,7 +241,7 @@ @@ -241,7 +241,7 @@
</div>
<div class="form-group" ng-show="isChrome">
<div class="form-group" ng-show="supported.constraints.chrome">
<label class="col-xs-4 control-label">{{_('Enable experiments')}}</label>
<div class="col-xs-8">
<div class="checkbox">

Loading…
Cancel
Save