/* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* More information about these options at jshint.com/docs/options */ /* jshint browser: true, camelcase: true, curly: true, devel: true, eqeqeq: true, forin: false, globalstrict: true, node: true, quotmark: single, undef: true, unused: strict */ /* global mozRTCIceCandidate, mozRTCPeerConnection, Promise, mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */ /* exported trace,requestUserMedia */ 'use strict'; var getUserMedia = null; var attachMediaStream = null; var reattachMediaStream = null; var webrtcDetectedBrowser = null; var webrtcDetectedVersion = null; var webrtcMinimumVersion = null; function trace(text) { // This function is used for logging. if (text[text.length - 1] === '\n') { text = text.substring(0, text.length - 1); } if (window.performance) { var now = (window.performance.now() / 1000).toFixed(3); beef.debug(now + ': ' + text); } else { beef.debug(text); } } if (navigator.mozGetUserMedia) { webrtcDetectedBrowser = 'firefox'; // the detected firefox version. webrtcDetectedVersion = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); // the minimum firefox version still supported by adapter. webrtcMinimumVersion = 31; // The RTCPeerConnection object. window.RTCPeerConnection = function(pcConfig, pcConstraints) { if (webrtcDetectedVersion < 38) { // .urls is not supported in FF < 38. // create RTCIceServers with a single url. if (pcConfig && pcConfig.iceServers) { var newIceServers = []; for (var i = 0; i < pcConfig.iceServers.length; i++) { var server = pcConfig.iceServers[i]; if (server.hasOwnProperty('urls')) { for (var j = 0; j < server.urls.length; j++) { var newServer = { url: server.urls[j] }; if (server.urls[j].indexOf('turn') === 0) { newServer.username = server.username; newServer.credential = server.credential; } newIceServers.push(newServer); } } else { newIceServers.push(pcConfig.iceServers[i]); } } pcConfig.iceServers = newIceServers; } } return new mozRTCPeerConnection(pcConfig, pcConstraints); }; try { // The RTCSessionDescription object. window.RTCSessionDescription = mozRTCSessionDescription; // The RTCIceCandidate object. window.RTCIceCandidate = mozRTCIceCandidate; }catch(err) { } // getUserMedia constraints shim. getUserMedia = (webrtcDetectedVersion < 38) ? function(c, onSuccess, onError) { var constraintsToFF37 = function(c) { if (typeof c !== 'object' || c.require) { return c; } var require = []; Object.keys(c).forEach(function(key) { var r = c[key] = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; if (r.exact !== undefined) { r.min = r.max = r.exact; delete r.exact; } if (r.min !== undefined || r.max !== undefined) { require.push(key); } if (r.ideal !== undefined) { c.advanced = c.advanced || []; var oc = {}; oc[key] = {min: r.ideal, max: r.ideal}; c.advanced.push(oc); delete r.ideal; if (!Object.keys(r).length) { delete c[key]; } } }); if (require.length) { c.require = require; } return c; }; beef.debug('spec: ' + JSON.stringify(c)); c.audio = constraintsToFF37(c.audio); c.video = constraintsToFF37(c.video); beef.debug('ff37: ' + JSON.stringify(c)); return navigator.mozGetUserMedia(c, onSuccess, onError); } : navigator.mozGetUserMedia.bind(navigator); navigator.getUserMedia = getUserMedia; // Shim for mediaDevices on older versions. if (!navigator.mediaDevices) { navigator.mediaDevices = {getUserMedia: requestUserMedia, addEventListener: function() { }, removeEventListener: function() { } }; } navigator.mediaDevices.enumerateDevices = navigator.mediaDevices.enumerateDevices || function() { return new Promise(function(resolve) { var infos = [ {kind: 'audioinput', deviceId: 'default', label:'', groupId:''}, {kind: 'videoinput', deviceId: 'default', label:'', groupId:''} ]; resolve(infos); }); }; if (webrtcDetectedVersion < 41) { // Work around http://bugzil.la/1169665 var orgEnumerateDevices = navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); navigator.mediaDevices.enumerateDevices = function() { return orgEnumerateDevices().then(undefined, function(e) { if (e.name === 'NotFoundError') { return []; } throw e; }); }; } // Attach a media stream to an element. attachMediaStream = function(element, stream) { beef.debug('Attaching media stream'); element.mozSrcObject = stream; }; reattachMediaStream = function(to, from) { beef.debug('Reattaching media stream'); to.mozSrcObject = from.mozSrcObject; }; } else if (navigator.webkitGetUserMedia) { webrtcDetectedBrowser = 'chrome'; // the detected chrome version. webrtcDetectedVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); // the minimum chrome version still supported by adapter. webrtcMinimumVersion = 38; // The RTCPeerConnection object. window.RTCPeerConnection = function(pcConfig, pcConstraints) { var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); var origGetStats = pc.getStats.bind(pc); pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line // If selector is a function then we are in the old style stats so just // pass back the original getStats format to avoid breaking old users. if (typeof selector === 'function') { return origGetStats(selector, successCallback); } var fixChromeStats = function(response) { var standardReport = {}; var reports = response.result(); reports.forEach(function(report) { var standardStats = { id: report.id, timestamp: report.timestamp, type: report.type }; report.names().forEach(function(name) { standardStats[name] = report.stat(name); }); standardReport[standardStats.id] = standardStats; }); return standardReport; }; var successCallbackWrapper = function(response) { successCallback(fixChromeStats(response)); }; return origGetStats(successCallbackWrapper, selector); }; return pc; }; // add promise support ['createOffer', 'createAnswer'].forEach(function(method) { var nativeMethod = webkitRTCPeerConnection.prototype[method]; webkitRTCPeerConnection.prototype[method] = function() { var self = this; if (arguments.length < 1 || (arguments.length === 1 && typeof(arguments[0]) === 'object')) { var opts = arguments.length === 1 ? arguments[0] : undefined; return new Promise(function(resolve, reject) { nativeMethod.apply(self, [resolve, reject, opts]); }); } else { return nativeMethod.apply(this, arguments); } }; }); ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function(method) { var nativeMethod = webkitRTCPeerConnection.prototype[method]; webkitRTCPeerConnection.prototype[method] = function() { var args = arguments; var self = this; return new Promise(function(resolve, reject) { nativeMethod.apply(self, [args[0], function() { resolve(); if (args.length >= 2) { args[1].apply(null, []); } }, function(err) { reject(err); if (args.length >= 3) { args[2].apply(null, [err]); } }] ); }); }; }); // getUserMedia constraints shim. getUserMedia = function(c, onSuccess, onError) { var constraintsToChrome = function(c) { if (typeof c !== 'object' || c.mandatory || c.optional) { return c; } var cc = {}; Object.keys(c).forEach(function(key) { if (key === 'require' || key === 'advanced') { return; } var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; if (r.exact !== undefined && typeof r.exact === 'number') { r.min = r.max = r.exact; } var oldname = function(prefix, name) { if (prefix) { return prefix + name.charAt(0).toUpperCase() + name.slice(1); } return (name === 'deviceId') ? 'sourceId' : name; }; if (r.ideal !== undefined) { cc.optional = cc.optional || []; var oc = {}; if (typeof r.ideal === 'number') { oc[oldname('min', key)] = r.ideal; cc.optional.push(oc); oc = {}; oc[oldname('max', key)] = r.ideal; cc.optional.push(oc); } else { oc[oldname('', key)] = r.ideal; cc.optional.push(oc); } } if (r.exact !== undefined && typeof r.exact !== 'number') { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname('', key)] = r.exact; } else { ['min', 'max'].forEach(function(mix) { if (r[mix] !== undefined) { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname(mix, key)] = r[mix]; } }); } }); if (c.advanced) { cc.optional = (cc.optional || []).concat(c.advanced); } return cc; }; beef.debug('spec: ' + JSON.stringify(c)); // whitespace for alignment c.audio = constraintsToChrome(c.audio); c.video = constraintsToChrome(c.video); beef.debug('chrome: ' + JSON.stringify(c)); return navigator.webkitGetUserMedia(c, onSuccess, onError); }; navigator.getUserMedia = getUserMedia; // Attach a media stream to an element. attachMediaStream = function(element, stream) { if (typeof element.srcObject !== 'undefined') { element.srcObject = stream; } else if (typeof element.src !== 'undefined') { element.src = URL.createObjectURL(stream); } else { beef.debug('Error attaching stream to element.'); } }; reattachMediaStream = function(to, from) { to.src = from.src; }; if (!navigator.mediaDevices) { navigator.mediaDevices = {getUserMedia: requestUserMedia, enumerateDevices: function() { return new Promise(function(resolve) { var kinds = {audio: 'audioinput', video: 'videoinput'}; return MediaStreamTrack.getSources(function(devices) { resolve(devices.map(function(device) { return {label: device.label, kind: kinds[device.kind], deviceId: device.id, groupId: ''}; })); }); }); }}; // in case someone wants to listen for the devicechange event. navigator.mediaDevices.addEventListener = function() { }; navigator.mediaDevices.removeEventListener = function() { }; } } else if (navigator.mediaDevices && navigator.userAgent.match( /Edge\/(\d+).(\d+)$/)) { webrtcDetectedBrowser = 'edge'; webrtcDetectedVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10); // the minimum version still supported by adapter. webrtcMinimumVersion = 12; attachMediaStream = function(element, stream) { element.srcObject = stream; }; reattachMediaStream = function(to, from) { to.srcObject = from.srcObject; }; } else { // console.log('Browser does not appear to be WebRTC-capable'); } // Returns the result of getUserMedia as a Promise. function requestUserMedia(constraints) { return new Promise(function(resolve, reject) { getUserMedia(constraints, resolve, reject); }); } if (typeof module !== 'undefined') { module.exports = { RTCPeerConnection: window.RTCPeerConnection, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, webrtcDetectedBrowser: webrtcDetectedBrowser, webrtcDetectedVersion: webrtcDetectedVersion, webrtcMinimumVersion: webrtcMinimumVersion //requestUserMedia: not exposed on purpose. //trace: not exposed on purpose. }; } else if ((typeof require === 'function') && (typeof define === 'function')) { // Expose objects and functions when RequireJS is doing the loading. define([], function() { return { RTCPeerConnection: window.RTCPeerConnection, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, webrtcDetectedBrowser: webrtcDetectedBrowser, webrtcDetectedVersion: webrtcDetectedVersion, webrtcMinimumVersion: webrtcMinimumVersion //requestUserMedia: not exposed on purpose. //trace: not exposed on purpose. }; }); }