var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js"); var Resolutions = require("../../service/RTC/Resolutions"); var currentResolution = null; function getPreviousResolution(resolution) { if(!Resolutions[resolution]) return null; var order = Resolutions[resolution].order; var res = null; var resName = null; for(var i in Resolutions) { var tmp = Resolutions[i]; if(res == null || (res.order < tmp.order && tmp.order < order)) { resName = i; res = tmp; } } return resName; } function setResolutionConstraints(constraints, resolution, isAndroid) { if (resolution && !constraints.video || isAndroid) { constraints.video = { mandatory: {}, optional: [] };// same behaviour as true } if(Resolutions[resolution]) { constraints.video.mandatory.minWidth = Resolutions[resolution].width; constraints.video.mandatory.minHeight = Resolutions[resolution].height; } else { if (isAndroid) { constraints.video.mandatory.minWidth = 320; constraints.video.mandatory.minHeight = 240; constraints.video.mandatory.maxFrameRate = 15; } } if (constraints.video.mandatory.minWidth) constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth; if (constraints.video.mandatory.minHeight) constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight; } function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid) { var constraints = {audio: false, video: false}; if (um.indexOf('video') >= 0) { constraints.video = { mandatory: {}, optional: [] };// same behaviour as true } if (um.indexOf('audio') >= 0) { constraints.audio = { mandatory: {}, optional: []};// same behaviour as true } if (um.indexOf('screen') >= 0) { constraints.video = { mandatory: { chromeMediaSource: "screen", googLeakyBucket: true, maxWidth: window.screen.width, maxHeight: window.screen.height, maxFrameRate: 3 }, optional: [] }; } if (um.indexOf('desktop') >= 0) { constraints.video = { mandatory: { chromeMediaSource: "desktop", chromeMediaSourceId: desktopStream, googLeakyBucket: true, maxWidth: window.screen.width, maxHeight: window.screen.height, maxFrameRate: 3 }, optional: [] }; } if (constraints.audio) { // if it is good enough for hangouts... constraints.audio.optional.push( {googEchoCancellation: true}, {googAutoGainControl: true}, {googNoiseSupression: true}, {googHighpassFilter: true}, {googNoisesuppression2: true}, {googEchoCancellation2: true}, {googAutoGainControl2: true} ); } if (constraints.video) { constraints.video.optional.push( {googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38 ); if (um.indexOf('video') >= 0) { constraints.video.optional.push( {googLeakyBucket: true} ); } } if (um.indexOf('video') >= 0) { setResolutionConstraints(constraints, resolution, isAndroid); } if (bandwidth) { // doesn't work currently, see webrtc issue 1846 if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true constraints.video.optional.push({bandwidth: bandwidth}); } if (fps) { // for some cameras it might be necessary to request 30fps // so they choose 30fps mjpg over 10fps yuy2 if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true; constraints.video.mandatory.minFrameRate = fps; } return constraints; } function RTCUtils(RTCService) { this.service = RTCService; if (navigator.mozGetUserMedia) { console.log('This appears to be Firefox'); var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); if (version >= 39) { this.peerconnection = mozRTCPeerConnection; this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX; this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); this.pc_constraints = {}; this.attachMediaStream = function (element, stream) { // srcObject is being standardized and FF will eventually // support that unprefixed. FF also supports the // "element.src = URL.createObjectURL(...)" combo, but that // will be deprecated in favour of srcObject. // // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg // https://github.com/webrtc/samples/issues/302 element[0].mozSrcObject = stream; element[0].play(); }; this.getStreamID = function (stream) { var tracks = stream.getVideoTracks(); if(!tracks || tracks.length == 0) { tracks = stream.getAudioTracks(); } return tracks[0].id.replace(/[\{,\}]/g,""); }; this.getVideoSrc = function (element) { return element.mozSrcObject; }; this.setVideoSrc = function (element, src) { element.mozSrcObject = src; }; RTCSessionDescription = mozRTCSessionDescription; RTCIceCandidate = mozRTCIceCandidate; } else { window.location.href = 'unsupported_browser.html'; return; } } else if (navigator.webkitGetUserMedia) { console.log('This appears to be Chrome'); this.peerconnection = webkitRTCPeerConnection; this.browser = RTCBrowserType.RTC_BROWSER_CHROME; this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); this.attachMediaStream = function (element, stream) { element.attr('src', webkitURL.createObjectURL(stream)); }; this.getStreamID = function (stream) { // streams from FF endpoints have the characters '{' and '}' // that make jQuery choke. return stream.id.replace(/[\{,\}]/g,""); }; this.getVideoSrc = function (element) { return element.getAttribute("src"); }; this.setVideoSrc = function (element, src) { element.setAttribute("src", src); }; // DTLS should now be enabled by default but.. this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}; if (navigator.userAgent.indexOf('Android') != -1) { this.pc_constraints = {}; // disable DTLS on Android } if (!webkitMediaStream.prototype.getVideoTracks) { webkitMediaStream.prototype.getVideoTracks = function () { return this.videoTracks; }; } if (!webkitMediaStream.prototype.getAudioTracks) { webkitMediaStream.prototype.getAudioTracks = function () { return this.audioTracks; }; } } else { try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { } window.location.href = 'unsupported_browser.html'; return; } } RTCUtils.prototype.getUserMediaWithConstraints = function( um, success_callback, failure_callback, resolution,bandwidth, fps, desktopStream) { currentResolution = resolution; // Check if we are running on Android device var isAndroid = navigator.userAgent.indexOf('Android') != -1; var constraints = getConstraints( um, resolution, bandwidth, fps, desktopStream, isAndroid); var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; try { if (config.enableSimulcast && constraints.video && constraints.video.chromeMediaSource !== 'screen' && constraints.video.chromeMediaSource !== 'desktop' && !isAndroid // We currently do not support FF, as it doesn't have multistream support. && !isFF) { APP.simulcast.getUserMedia(constraints, function (stream) { console.log('onUserMediaSuccess'); success_callback(stream); }, function (error) { console.warn('Failed to get access to local media. Error ', error); if (failure_callback) { failure_callback(error); } }); } else { this.getUserMedia(constraints, function (stream) { console.log('onUserMediaSuccess'); success_callback(stream); }, function (error) { console.warn('Failed to get access to local media. Error ', error, constraints); if (failure_callback) { failure_callback(error); } }); } } catch (e) { console.error('GUM failed: ', e); if(failure_callback) { failure_callback(e); } } }; /** * We ask for audio and video combined stream in order to get permissions and * not to ask twice. */ RTCUtils.prototype.obtainAudioAndVideoPermissions = function(devices, callback) { var self = this; // Get AV if(!devices) devices = ['audio', 'video']; this.getUserMediaWithConstraints( devices, function (stream) { if(callback) callback(stream); else self.successCallback(stream); }, function (error) { self.errorCallback(error); }, config.resolution || '360'); } RTCUtils.prototype.successCallback = function (stream) { if(stream) console.log('got', stream, stream.getAudioTracks().length, stream.getVideoTracks().length); this.handleLocalStream(stream); }; RTCUtils.prototype.errorCallback = function (error) { var self = this; console.error('failed to obtain audio/video stream - trying audio only', error); var resolution = getPreviousResolution(currentResolution); if(typeof error == "object" && error.constraintName && error.name && (error.name == "ConstraintNotSatisfiedError" || error.name == "OverconstrainedError") && (error.constraintName == "minWidth" || error.constraintName == "maxWidth" || error.constraintName == "minHeight" || error.constraintName == "maxHeight") && resolution != null) { self.getUserMediaWithConstraints(['audio', 'video'], function (stream) { return self.successCallback(stream); }, function (error) { return self.errorCallback(error); }, resolution); } else { self.getUserMediaWithConstraints( ['audio'], function (stream) { return self.successCallback(stream); }, function (error) { console.error('failed to obtain audio/video stream - stop', error); // APP.UI.messageHandler.showError("dialog.error", // "dialog.failedpermissions"); return self.successCallback(null); } ); } } RTCUtils.prototype.handleLocalStream = function(stream) { if(window.webkitMediaStream) { var audioStream = new webkitMediaStream(); var videoStream = new webkitMediaStream(); if(stream) { var audioTracks = stream.getAudioTracks(); for (var i = 0; i < audioTracks.length; i++) { audioStream.addTrack(audioTracks[i]); } var videoTracks = stream.getVideoTracks(); for (i = 0; i < videoTracks.length; i++) { videoStream.addTrack(videoTracks[i]); } } this.service.createLocalStream(audioStream, "audio"); this.service.createLocalStream(videoStream, "video"); } else {//firefox this.service.createLocalStream(stream, "stream"); } }; RTCUtils.prototype.createVideoStream = function(stream) { var videoStream = null; if(window.webkitMediaStream) { videoStream = new webkitMediaStream(); if(stream) { var videoTracks = stream.getVideoTracks(); for (i = 0; i < videoTracks.length; i++) { videoStream.addTrack(videoTracks[i]); } } } else videoStream = stream; return videoStream; }; module.exports = RTCUtils;