Advertisement
Guest User

adapter.js

a guest
Jul 27th, 2017
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  3.  *
  4.  *  Use of this source code is governed by a BSD-style license
  5.  *  that can be found in the LICENSE file in the root of the source
  6.  *  tree.
  7.  */
  8.  
  9. /* More information about these options at jshint.com/docs/options */
  10. /* jshint browser: true, camelcase: true, curly: true, devel: true,
  11.    eqeqeq: true, forin: false, globalstrict: true, node: true,
  12.    quotmark: single, undef: true, unused: strict */
  13. /* global mozRTCIceCandidate, mozRTCPeerConnection, Promise,
  14. mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack,
  15. MediaStream, RTCIceGatherer, RTCIceTransport, RTCDtlsTransport,
  16. RTCRtpSender, RTCRtpReceiver*/
  17. /* exported trace,requestUserMedia */
  18.  
  19. 'use strict';
  20.  
  21. var getUserMedia = null;
  22. var attachMediaStream = null;
  23. var reattachMediaStream = null;
  24. var webrtcDetectedBrowser = null;
  25. var webrtcDetectedVersion = null;
  26. var webrtcMinimumVersion = null;
  27. var webrtcUtils = {
  28.   log: function() {
  29.     // suppress console.log output when being included as a module.
  30.     if (typeof module !== 'undefined' ||
  31.         typeof require === 'function' && typeof define === 'function') {
  32.       return;
  33.     }
  34.     console.log.apply(console, arguments);
  35.   },
  36.   extractVersion: function(uastring, expr, pos) {
  37.     var match = uastring.match(expr);
  38.     return match && match.length >= pos && parseInt(match[pos], 10);
  39.   }
  40. };
  41.  
  42. function trace(text) {
  43.   // This function is used for logging.
  44.   if (text[text.length - 1] === '\n') {
  45.     text = text.substring(0, text.length - 1);
  46.   }
  47.   if (window.performance) {
  48.     var now = (window.performance.now() / 1000).toFixed(3);
  49.     webrtcUtils.log(now + ': ' + text);
  50.   } else {
  51.     webrtcUtils.log(text);
  52.   }
  53. }
  54.  
  55. if (typeof window === 'object') {
  56.   if (window.HTMLMediaElement &&
  57.     !('srcObject' in window.HTMLMediaElement.prototype)) {
  58.     // Shim the srcObject property, once, when HTMLMediaElement is found.
  59.     Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
  60.       get: function() {
  61.         // If prefixed srcObject property exists, return it.
  62.         // Otherwise use the shimmed property, _srcObject
  63.         return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject;
  64.       },
  65.       set: function(stream) {
  66.         if ('mozSrcObject' in this) {
  67.           this.mozSrcObject = stream;
  68.         } else {
  69.           // Use _srcObject as a private property for this shim
  70.           this._srcObject = stream;
  71.           // TODO: revokeObjectUrl(this.src) when !stream to release resources?
  72.           this.src = URL.createObjectURL(stream);
  73.         }
  74.       }
  75.     });
  76.   }
  77.   // Proxy existing globals
  78.   getUserMedia = window.navigator && window.navigator.getUserMedia;
  79. }
  80.  
  81. // Attach a media stream to an element.
  82. attachMediaStream = function(element, stream) {
  83.   element.srcObject = stream;
  84. };
  85.  
  86. reattachMediaStream = function(to, from) {
  87.   to.srcObject = from.srcObject;
  88. };
  89.  
  90. if (typeof window === 'undefined' || !window.navigator) {
  91.   webrtcUtils.log('This does not appear to be a browser');
  92.   webrtcDetectedBrowser = 'not a browser';
  93. } else if (navigator.mozGetUserMedia) {
  94.   webrtcUtils.log('This appears to be Firefox');
  95.  
  96.   webrtcDetectedBrowser = 'firefox';
  97.  
  98.   // the detected firefox version.
  99.   webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  100.       /Firefox\/([0-9]+)\./, 1);
  101.  
  102.   // the minimum firefox version still supported by adapter.
  103.   webrtcMinimumVersion = 31;
  104.  
  105.   // Shim for RTCPeerConnection on older versions.
  106.   if (!window.RTCPeerConnection) {
  107.     window.RTCPeerConnection = function(pcConfig, pcConstraints) {
  108.       if (webrtcDetectedVersion < 38) {
  109.         // .urls is not supported in FF < 38.
  110.         // create RTCIceServers with a single url.
  111.         if (pcConfig && pcConfig.iceServers) {
  112.           var newIceServers = [];
  113.           for (var i = 0; i < pcConfig.iceServers.length; i++) {
  114.             var server = pcConfig.iceServers[i];
  115.             if (server.hasOwnProperty('urls')) {
  116.               for (var j = 0; j < server.urls.length; j++) {
  117.                 var newServer = {
  118.                   url: server.urls[j]
  119.                 };
  120.                 if (server.urls[j].indexOf('turn') === 0) {
  121.                   newServer.username = server.username;
  122.                   newServer.credential = server.credential;
  123.                 }
  124.                 newIceServers.push(newServer);
  125.               }
  126.             } else {
  127.               newIceServers.push(pcConfig.iceServers[i]);
  128.             }
  129.           }
  130.           pcConfig.iceServers = newIceServers;
  131.         }
  132.       }
  133.       return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
  134.     };
  135.  
  136.     // wrap static methods. Currently just generateCertificate.
  137.     if (mozRTCPeerConnection.generateCertificate) {
  138.       Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
  139.         get: function() {
  140.           if (arguments.length) {
  141.             return mozRTCPeerConnection.generateCertificate.apply(null,
  142.                 arguments);
  143.           } else {
  144.             return mozRTCPeerConnection.generateCertificate;
  145.           }
  146.         }
  147.       });
  148.     }
  149.  
  150.     window.RTCSessionDescription = mozRTCSessionDescription;
  151.     window.RTCIceCandidate = mozRTCIceCandidate;
  152.   }
  153.  
  154.   // getUserMedia constraints shim.
  155.   getUserMedia = function(constraints, onSuccess, onError) {
  156.     var constraintsToFF37 = function(c) {
  157.       if (typeof c !== 'object' || c.require) {
  158.         return c;
  159.       }
  160.       var require = [];
  161.       Object.keys(c).forEach(function(key) {
  162.         if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  163.           return;
  164.         }
  165.         var r = c[key] = (typeof c[key] === 'object') ?
  166.             c[key] : {ideal: c[key]};
  167.         if (r.min !== undefined ||
  168.             r.max !== undefined || r.exact !== undefined) {
  169.           require.push(key);
  170.         }
  171.         if (r.exact !== undefined) {
  172.           if (typeof r.exact === 'number') {
  173.             r.min = r.max = r.exact;
  174.           } else {
  175.             c[key] = r.exact;
  176.           }
  177.           delete r.exact;
  178.         }
  179.         if (r.ideal !== undefined) {
  180.           c.advanced = c.advanced || [];
  181.           var oc = {};
  182.           if (typeof r.ideal === 'number') {
  183.             oc[key] = {min: r.ideal, max: r.ideal};
  184.           } else {
  185.             oc[key] = r.ideal;
  186.           }
  187.           c.advanced.push(oc);
  188.           delete r.ideal;
  189.           if (!Object.keys(r).length) {
  190.             delete c[key];
  191.           }
  192.         }
  193.       });
  194.       if (require.length) {
  195.         c.require = require;
  196.       }
  197.       return c;
  198.     };
  199.     if (webrtcDetectedVersion < 38) {
  200.       webrtcUtils.log('spec: ' + JSON.stringify(constraints));
  201.       if (constraints.audio) {
  202.         constraints.audio = constraintsToFF37(constraints.audio);
  203.       }
  204.       if (constraints.video) {
  205.         constraints.video = constraintsToFF37(constraints.video);
  206.       }
  207.       webrtcUtils.log('ff37: ' + JSON.stringify(constraints));
  208.     }
  209.     return navigator.mozGetUserMedia(constraints, onSuccess, onError);
  210.   };
  211.  
  212.   navigator.getUserMedia = getUserMedia;
  213.  
  214.   // Shim for mediaDevices on older versions.
  215.   if (!navigator.mediaDevices) {
  216.     navigator.mediaDevices = {getUserMedia: requestUserMedia,
  217.       addEventListener: function() { },
  218.       removeEventListener: function() { }
  219.     };
  220.   }
  221.   navigator.mediaDevices.enumerateDevices =
  222.       navigator.mediaDevices.enumerateDevices || function() {
  223.     return new Promise(function(resolve) {
  224.       var infos = [
  225.         {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
  226.         {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
  227.       ];
  228.       resolve(infos);
  229.     });
  230.   };
  231.  
  232.   if (webrtcDetectedVersion < 41) {
  233.     // Work around http://bugzil.la/1169665
  234.     var orgEnumerateDevices =
  235.         navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
  236.     navigator.mediaDevices.enumerateDevices = function() {
  237.       return orgEnumerateDevices().then(undefined, function(e) {
  238.         if (e.name === 'NotFoundError') {
  239.           return [];
  240.         }
  241.         throw e;
  242.       });
  243.     };
  244.   }
  245. } else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) {
  246.   webrtcUtils.log('This appears to be Chrome');
  247.  
  248.   webrtcDetectedBrowser = 'chrome';
  249.  
  250.   // the detected chrome version.
  251.   webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  252.       /Chrom(e|ium)\/([0-9]+)\./, 2);
  253.  
  254.   // the minimum chrome version still supported by adapter.
  255.   webrtcMinimumVersion = 38;
  256.  
  257.   // The RTCPeerConnection object.
  258.   window.RTCPeerConnection = function(pcConfig, pcConstraints) {
  259.     // Translate iceTransportPolicy to iceTransports,
  260.     // see https://code.google.com/p/webrtc/issues/detail?id=4869
  261.     if (pcConfig && pcConfig.iceTransportPolicy) {
  262.       pcConfig.iceTransports = pcConfig.iceTransportPolicy;
  263.     }
  264.  
  265.     var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
  266.     var origGetStats = pc.getStats.bind(pc);
  267.     pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line
  268.       var self = this;
  269.       var args = arguments;
  270.  
  271.       // If selector is a function then we are in the old style stats so just
  272.       // pass back the original getStats format to avoid breaking old users.
  273.       if (arguments.length > 0 && typeof selector === 'function') {
  274.         return origGetStats(selector, successCallback);
  275.       }
  276.  
  277.       var fixChromeStats = function(response) {
  278.         var standardReport = {};
  279.         var reports = response.result();
  280.         reports.forEach(function(report) {
  281.           var standardStats = {
  282.             id: report.id,
  283.             timestamp: report.timestamp,
  284.             type: report.type
  285.           };
  286.           report.names().forEach(function(name) {
  287.             standardStats[name] = report.stat(name);
  288.           });
  289.           standardReport[standardStats.id] = standardStats;
  290.         });
  291.  
  292.         return standardReport;
  293.       };
  294.  
  295.       if (arguments.length >= 2) {
  296.         var successCallbackWrapper = function(response) {
  297.           args[1](fixChromeStats(response));
  298.         };
  299.  
  300.         return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]);
  301.       }
  302.  
  303.       // promise-support
  304.       return new Promise(function(resolve, reject) {
  305.         if (args.length === 1 && selector === null) {
  306.           origGetStats.apply(self, [
  307.               function(response) {
  308.                 resolve.apply(null, [fixChromeStats(response)]);
  309.               }, reject]);
  310.         } else {
  311.           origGetStats.apply(self, [resolve, reject]);
  312.         }
  313.       });
  314.     };
  315.  
  316.     return pc;
  317.   };
  318.  
  319.   // wrap static methods. Currently just generateCertificate.
  320.   if (webkitRTCPeerConnection.generateCertificate) {
  321.     Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
  322.       get: function() {
  323.         if (arguments.length) {
  324.           return webkitRTCPeerConnection.generateCertificate.apply(null,
  325.               arguments);
  326.         } else {
  327.           return webkitRTCPeerConnection.generateCertificate;
  328.         }
  329.       }
  330.     });
  331.   }
  332.  
  333.   // add promise support
  334.   ['createOffer', 'createAnswer'].forEach(function(method) {
  335.     var nativeMethod = webkitRTCPeerConnection.prototype[method];
  336.     webkitRTCPeerConnection.prototype[method] = function() {
  337.       var self = this;
  338.       if (arguments.length < 1 || (arguments.length === 1 &&
  339.           typeof(arguments[0]) === 'object')) {
  340.         var opts = arguments.length === 1 ? arguments[0] : undefined;
  341.         return new Promise(function(resolve, reject) {
  342.           nativeMethod.apply(self, [resolve, reject, opts]);
  343.         });
  344.       } else {
  345.         return nativeMethod.apply(this, arguments);
  346.       }
  347.     };
  348.   });
  349.  
  350.   ['setLocalDescription', 'setRemoteDescription',
  351.       'addIceCandidate'].forEach(function(method) {
  352.     var nativeMethod = webkitRTCPeerConnection.prototype[method];
  353.     webkitRTCPeerConnection.prototype[method] = function() {
  354.       var args = arguments;
  355.       var self = this;
  356.       return new Promise(function(resolve, reject) {
  357.         nativeMethod.apply(self, [args[0],
  358.             function() {
  359.               resolve();
  360.               if (args.length >= 2) {
  361.                 args[1].apply(null, []);
  362.               }
  363.             },
  364.             function(err) {
  365.               reject(err);
  366.               if (args.length >= 3) {
  367.                 args[2].apply(null, [err]);
  368.               }
  369.             }]
  370.           );
  371.       });
  372.     };
  373.   });
  374.  
  375.   // getUserMedia constraints shim.
  376.   var constraintsToChrome = function(c) {
  377.     if (typeof c !== 'object' || c.mandatory || c.optional) {
  378.       return c;
  379.     }
  380.     var cc = {};
  381.     Object.keys(c).forEach(function(key) {
  382.       if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  383.         return;
  384.       }
  385.       var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
  386.       if (r.exact !== undefined && typeof r.exact === 'number') {
  387.         r.min = r.max = r.exact;
  388.       }
  389.       var oldname = function(prefix, name) {
  390.         if (prefix) {
  391.           return prefix + name.charAt(0).toUpperCase() + name.slice(1);
  392.         }
  393.         return (name === 'deviceId') ? 'sourceId' : name;
  394.       };
  395.       if (r.ideal !== undefined) {
  396.         cc.optional = cc.optional || [];
  397.         var oc = {};
  398.         if (typeof r.ideal === 'number') {
  399.           oc[oldname('min', key)] = r.ideal;
  400.           cc.optional.push(oc);
  401.           oc = {};
  402.           oc[oldname('max', key)] = r.ideal;
  403.           cc.optional.push(oc);
  404.         } else {
  405.           oc[oldname('', key)] = r.ideal;
  406.           cc.optional.push(oc);
  407.         }
  408.       }
  409.       if (r.exact !== undefined && typeof r.exact !== 'number') {
  410.         cc.mandatory = cc.mandatory || {};
  411.         cc.mandatory[oldname('', key)] = r.exact;
  412.       } else {
  413.         ['min', 'max'].forEach(function(mix) {
  414.           if (r[mix] !== undefined) {
  415.             cc.mandatory = cc.mandatory || {};
  416.             cc.mandatory[oldname(mix, key)] = r[mix];
  417.           }
  418.         });
  419.       }
  420.     });
  421.     if (c.advanced) {
  422.       cc.optional = (cc.optional || []).concat(c.advanced);
  423.     }
  424.     return cc;
  425.   };
  426.  
  427.   getUserMedia = function(constraints, onSuccess, onError) {
  428.     if (constraints.audio) {
  429.       constraints.audio = constraintsToChrome(constraints.audio);
  430.     }
  431.     if (constraints.video) {
  432.       constraints.video = constraintsToChrome(constraints.video);
  433.     }
  434.     webrtcUtils.log('chrome: ' + JSON.stringify(constraints));
  435.     return navigator.webkitGetUserMedia(constraints, onSuccess, onError);
  436.   };
  437.   navigator.getUserMedia = getUserMedia;
  438.  
  439.   if (!navigator.mediaDevices) {
  440.     navigator.mediaDevices = {getUserMedia: requestUserMedia,
  441.                               enumerateDevices: function() {
  442.       return new Promise(function(resolve) {
  443.         var kinds = {audio: 'audioinput', video: 'videoinput'};
  444.         return MediaStreamTrack.getSources(function(devices) {
  445.           resolve(devices.map(function(device) {
  446.             return {label: device.label,
  447.                     kind: kinds[device.kind],
  448.                     deviceId: device.id,
  449.                     groupId: ''};
  450.           }));
  451.         });
  452.       });
  453.     }};
  454.   }
  455.  
  456.   // A shim for getUserMedia method on the mediaDevices object.
  457.   // TODO(KaptenJansson) remove once implemented in Chrome stable.
  458.   if (!navigator.mediaDevices.getUserMedia) {
  459.     navigator.mediaDevices.getUserMedia = function(constraints) {
  460.       return requestUserMedia(constraints);
  461.     };
  462.   } else {
  463.     // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
  464.     // function which returns a Promise, it does not accept spec-style
  465.     // constraints.
  466.     var origGetUserMedia = navigator.mediaDevices.getUserMedia.
  467.         bind(navigator.mediaDevices);
  468.     navigator.mediaDevices.getUserMedia = function(c) {
  469.       webrtcUtils.log('spec:   ' + JSON.stringify(c)); // whitespace for alignment
  470.       c.audio = constraintsToChrome(c.audio);
  471.       c.video = constraintsToChrome(c.video);
  472.       webrtcUtils.log('chrome: ' + JSON.stringify(c));
  473.       return origGetUserMedia(c);
  474.     };
  475.   }
  476.  
  477.   // Dummy devicechange event methods.
  478.   // TODO(KaptenJansson) remove once implemented in Chrome stable.
  479.   if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
  480.     navigator.mediaDevices.addEventListener = function() {
  481.       webrtcUtils.log('Dummy mediaDevices.addEventListener called.');
  482.     };
  483.   }
  484.   if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
  485.     navigator.mediaDevices.removeEventListener = function() {
  486.       webrtcUtils.log('Dummy mediaDevices.removeEventListener called.');
  487.     };
  488.   }
  489.  
  490.   // Attach a media stream to an element.
  491.   attachMediaStream = function(element, stream) {
  492.     if (webrtcDetectedVersion >= 43) {
  493.       element.srcObject = stream;
  494.     } else if (typeof element.src !== 'undefined') {
  495.       element.src = URL.createObjectURL(stream);
  496.     } else {
  497.       webrtcUtils.log('Error attaching stream to element.');
  498.     }
  499.   };
  500.   reattachMediaStream = function(to, from) {
  501.     if (webrtcDetectedVersion >= 43) {
  502.       to.srcObject = from.srcObject;
  503.     } else {
  504.       to.src = from.src;
  505.     }
  506.   };
  507.  
  508. } else if (navigator.mediaDevices && navigator.userAgent.match(
  509.     /Edge\/(\d+).(\d+)$/)) {
  510.   webrtcUtils.log('This appears to be Edge');
  511.   webrtcDetectedBrowser = 'edge';
  512.  
  513.   webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  514.       /Edge\/(\d+).(\d+)$/, 2);
  515.  
  516.   // The minimum version still supported by adapter.
  517.   // This is the build number for Edge.
  518.   webrtcMinimumVersion = 10547;
  519.  
  520.   if (window.RTCIceGatherer) {
  521.     // Generate an alphanumeric identifier for cname or mids.
  522.     // TODO: use UUIDs instead? https://gist.github.com/jed/982883
  523.     var generateIdentifier = function() {
  524.       return Math.random().toString(36).substr(2, 10);
  525.     };
  526.  
  527.     // The RTCP CNAME used by all peerconnections from the same JS.
  528.     var localCName = generateIdentifier();
  529.  
  530.     // SDP helpers - to be moved into separate module.
  531.     var SDPUtils = {};
  532.  
  533.     // Splits SDP into lines, dealing with both CRLF and LF.
  534.     SDPUtils.splitLines = function(blob) {
  535.       return blob.trim().split('\n').map(function(line) {
  536.         return line.trim();
  537.       });
  538.     };
  539.  
  540.     // Splits SDP into sessionpart and mediasections. Ensures CRLF.
  541.     SDPUtils.splitSections = function(blob) {
  542.       var parts = blob.split('\r\nm=');
  543.       return parts.map(function(part, index) {
  544.         return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
  545.       });
  546.     };
  547.  
  548.     // Returns lines that start with a certain prefix.
  549.     SDPUtils.matchPrefix = function(blob, prefix) {
  550.       return SDPUtils.splitLines(blob).filter(function(line) {
  551.         return line.indexOf(prefix) === 0;
  552.       });
  553.     };
  554.  
  555.     // Parses an ICE candidate line. Sample input:
  556.     // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 rport 55996"
  557.     SDPUtils.parseCandidate = function(line) {
  558.       var parts;
  559.       // Parse both variants.
  560.       if (line.indexOf('a=candidate:') === 0) {
  561.         parts = line.substring(12).split(' ');
  562.       } else {
  563.         parts = line.substring(10).split(' ');
  564.       }
  565.  
  566.       var candidate = {
  567.         foundation: parts[0],
  568.         component: parts[1],
  569.         protocol: parts[2].toLowerCase(),
  570.         priority: parseInt(parts[3], 10),
  571.         ip: parts[4],
  572.         port: parseInt(parts[5], 10),
  573.         // skip parts[6] == 'typ'
  574.         type: parts[7]
  575.       };
  576.  
  577.       for (var i = 8; i < parts.length; i += 2) {
  578.         switch (parts[i]) {
  579.           case 'raddr':
  580.             candidate.relatedAddress = parts[i + 1];
  581.             break;
  582.           case 'rport':
  583.             candidate.relatedPort = parseInt(parts[i + 1], 10);
  584.             break;
  585.           case 'tcptype':
  586.             candidate.tcpType = parts[i + 1];
  587.             break;
  588.           default: // Unknown extensions are silently ignored.
  589.             break;
  590.         }
  591.       }
  592.       return candidate;
  593.     };
  594.  
  595.     // Translates a candidate object into SDP candidate attribute.
  596.     SDPUtils.writeCandidate = function(candidate) {
  597.       var sdp = [];
  598.       sdp.push(candidate.foundation);
  599.       sdp.push(candidate.component);
  600.       sdp.push(candidate.protocol.toUpperCase());
  601.       sdp.push(candidate.priority);
  602.       sdp.push(candidate.ip);
  603.       sdp.push(candidate.port);
  604.  
  605.       var type = candidate.type;
  606.       sdp.push('typ');
  607.       sdp.push(type);
  608.       if (type !== 'host' && candidate.relatedAddress &&
  609.           candidate.relatedPort) {
  610.         sdp.push('raddr');
  611.         sdp.push(candidate.relatedAddress); // was: relAddr
  612.         sdp.push('rport');
  613.         sdp.push(candidate.relatedPort); // was: relPort
  614.       }
  615.       if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
  616.         sdp.push('tcptype');
  617.         sdp.push(candidate.tcpType);
  618.       }
  619.       return 'candidate:' + sdp.join(' ');
  620.     };
  621.  
  622.     // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
  623.     // a=rtpmap:111 opus/48000/2
  624.     SDPUtils.parseRtpMap = function(line) {
  625.       var parts = line.substr(9).split(' ');
  626.       var parsed = {
  627.         payloadType: parseInt(parts.shift(), 10) // was: id
  628.       };
  629.  
  630.       parts = parts[0].split('/');
  631.  
  632.       parsed.name = parts[0];
  633.       parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
  634.       parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels
  635.       return parsed;
  636.     };
  637.  
  638.     // Generate an a=rtpmap line from RTCRtpCodecCapability or RTCRtpCodecParameters.
  639.     SDPUtils.writeRtpMap = function(codec) {
  640.       var pt = codec.payloadType;
  641.       if (codec.preferredPayloadType !== undefined) {
  642.         pt = codec.preferredPayloadType;
  643.       }
  644.       return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
  645.           (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
  646.     };
  647.  
  648.     // Parses an ftmp line, returns dictionary. Sample input:
  649.     // a=fmtp:96 vbr=on;cng=on
  650.     // Also deals with vbr=on; cng=on
  651.     SDPUtils.parseFmtp = function(line) {
  652.       var parsed = {};
  653.       var kv;
  654.       var parts = line.substr(line.indexOf(' ') + 1).split(';');
  655.       for (var j = 0; j < parts.length; j++) {
  656.         kv = parts[j].trim().split('=');
  657.         parsed[kv[0].trim()] = kv[1];
  658.       }
  659.       return parsed;
  660.     };
  661.  
  662.     // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
  663.     SDPUtils.writeFtmp = function(codec) {
  664.       var line = '';
  665.       var pt = codec.payloadType;
  666.       if (codec.preferredPayloadType !== undefined) {
  667.         pt = codec.preferredPayloadType;
  668.       }
  669.       if (codec.parameters && codec.parameters.length) {
  670.         var params = [];
  671.         Object.keys(codec.parameters).forEach(function(param) {
  672.           params.push(param + '=' + codec.parameters[param]);
  673.         });
  674.         line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
  675.       }
  676.       return line;
  677.     };
  678.  
  679.     // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
  680.     // a=rtcp-fb:98 nack rpsi
  681.     SDPUtils.parseRtcpFb = function(line) {
  682.       var parts = line.substr(line.indexOf(' ') + 1).split(' ');
  683.       return {
  684.         type: parts.shift(),
  685.         parameter: parts.join(' ')
  686.       };
  687.     };
  688.     // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
  689.     SDPUtils.writeRtcpFb = function(codec) {
  690.       var lines = '';
  691.       var pt = codec.payloadType;
  692.       if (codec.preferredPayloadType !== undefined) {
  693.         pt = codec.preferredPayloadType;
  694.       }
  695.       if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
  696.         // FIXME: special handling for trr-int?
  697.         codec.rtcpFeedback.forEach(function(fb) {
  698.           lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' + fb.parameter +
  699.               '\r\n';
  700.         });
  701.       }
  702.       return lines;
  703.     };
  704.  
  705.     // Parses an RFC 5576 ssrc media attribute. Sample input:
  706.     // a=ssrc:3735928559 cname:something
  707.     SDPUtils.parseSsrcMedia = function(line) {
  708.       var sp = line.indexOf(' ');
  709.       var parts = {
  710.         ssrc: line.substr(7, sp - 7),
  711.       };
  712.       var colon = line.indexOf(':', sp);
  713.       if (colon > -1) {
  714.         parts.attribute = line.substr(sp + 1, colon - sp - 1);
  715.         parts.value = line.substr(colon + 1);
  716.       } else {
  717.         parts.attribute = line.substr(sp + 1);
  718.       }
  719.       return parts;
  720.     };
  721.  
  722.     // Extracts DTLS parameters from SDP media section or sessionpart.
  723.     // FIXME: for consistency with other functions this should only
  724.     //   get the fingerprint line as input. See also getIceParameters.
  725.     SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
  726.       var lines = SDPUtils.splitLines(mediaSection);
  727.       lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too.
  728.       var fpLine = lines.filter(function(line) {
  729.         return line.indexOf('a=fingerprint:') === 0;
  730.       })[0].substr(14);
  731.       // Note: a=setup line is ignored since we use the 'auto' role.
  732.       var dtlsParameters = {
  733.         role: 'auto',
  734.         fingerprints: [{
  735.           algorithm: fpLine.split(' ')[0],
  736.           value: fpLine.split(' ')[1]
  737.         }]
  738.       };
  739.       return dtlsParameters;
  740.     };
  741.  
  742.     // Serializes DTLS parameters to SDP.
  743.     SDPUtils.writeDtlsParameters = function(params, setupType) {
  744.       var sdp = 'a=setup:' + setupType + '\r\n';
  745.       params.fingerprints.forEach(function(fp) {
  746.         sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
  747.       });
  748.       return sdp;
  749.     };
  750.     // Parses ICE information from SDP media section or sessionpart.
  751.     // FIXME: for consistency with other functions this should only
  752.     //   get the ice-ufrag and ice-pwd lines as input.
  753.     SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
  754.       var lines = SDPUtils.splitLines(mediaSection);
  755.       lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too.
  756.       var iceParameters = {
  757.         usernameFragment: lines.filter(function(line) {
  758.           return line.indexOf('a=ice-ufrag:') === 0;
  759.         })[0].substr(12),
  760.         password: lines.filter(function(line) {
  761.           return line.indexOf('a=ice-pwd:') === 0;
  762.         })[0].substr(10)
  763.       };
  764.       return iceParameters;
  765.     };
  766.  
  767.     // Serializes ICE parameters to SDP.
  768.     SDPUtils.writeIceParameters = function(params) {
  769.       return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
  770.           'a=ice-pwd:' + params.password + '\r\n';
  771.     };
  772.  
  773.     // Parses the SDP media section and returns RTCRtpParameters.
  774.     SDPUtils.parseRtpParameters = function(mediaSection) {
  775.       var description = {
  776.         codecs: [],
  777.         headerExtensions: [],
  778.         fecMechanisms: [],
  779.         rtcp: []
  780.       };
  781.       var lines = SDPUtils.splitLines(mediaSection);
  782.       var mline = lines[0].split(' ');
  783.       for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
  784.         var pt = mline[i];
  785.         var rtpmapline = SDPUtils.matchPrefix(
  786.             mediaSection, 'a=rtpmap:' + pt + ' ')[0];
  787.         if (rtpmapline) {
  788.           var codec = SDPUtils.parseRtpMap(rtpmapline);
  789.           var fmtps = SDPUtils.matchPrefix(
  790.               mediaSection, 'a=fmtp:' + pt + ' ');
  791.           // Only the first a=fmtp:<pt> is considered.
  792.           codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
  793.           codec.rtcpFeedback = SDPUtils.matchPrefix(
  794.               mediaSection, 'a=rtcp-fb:' + pt + ' ')
  795.             .map(SDPUtils.parseRtcpFb);
  796.           description.codecs.push(codec);
  797.         }
  798.       }
  799.       // FIXME: parse headerExtensions, fecMechanisms and rtcp.
  800.       return description;
  801.     };
  802.  
  803.     // Generates parts of the SDP media section describing the capabilities / parameters.
  804.     SDPUtils.writeRtpDescription = function(kind, caps) {
  805.       var sdp = '';
  806.  
  807.       // Build the mline.
  808.       sdp += 'm=' + kind + ' ';
  809.       sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
  810.       sdp += ' UDP/TLS/RTP/SAVPF ';
  811.       sdp += caps.codecs.map(function(codec) {
  812.         if (codec.preferredPayloadType !== undefined) {
  813.           return codec.preferredPayloadType;
  814.         }
  815.         return codec.payloadType;
  816.       }).join(' ') + '\r\n';
  817.  
  818.       sdp += 'c=IN IP4 0.0.0.0\r\n';
  819.       sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
  820.  
  821.       // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
  822.       caps.codecs.forEach(function(codec) {
  823.         sdp += SDPUtils.writeRtpMap(codec);
  824.         sdp += SDPUtils.writeFtmp(codec);
  825.         sdp += SDPUtils.writeRtcpFb(codec);
  826.       });
  827.       // FIXME: add headerExtensions, fecMechanismÅŸ and rtcp.
  828.       sdp += 'a=rtcp-mux\r\n';
  829.       return sdp;
  830.     };
  831.  
  832.     SDPUtils.writeSessionBoilerplate = function() {
  833.       // FIXME: sess-id should be an NTP timestamp.
  834.       return 'v=0\r\n' +
  835.           'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
  836.           's=-\r\n' +
  837.           't=0 0\r\n';
  838.     };
  839.  
  840.     SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
  841.       var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
  842.  
  843.       // Map ICE parameters (ufrag, pwd) to SDP.
  844.       sdp += SDPUtils.writeIceParameters(
  845.           transceiver.iceGatherer.getLocalParameters());
  846.  
  847.       // Map DTLS parameters to SDP.
  848.       sdp += SDPUtils.writeDtlsParameters(
  849.           transceiver.dtlsTransport.getLocalParameters(),
  850.           type === 'offer' ? 'actpass' : 'active');
  851.  
  852.       sdp += 'a=mid:' + transceiver.mid + '\r\n';
  853.  
  854.       if (transceiver.rtpSender && transceiver.rtpReceiver) {
  855.         sdp += 'a=sendrecv\r\n';
  856.       } else if (transceiver.rtpSender) {
  857.         sdp += 'a=sendonly\r\n';
  858.       } else if (transceiver.rtpReceiver) {
  859.         sdp += 'a=recvonly\r\n';
  860.       } else {
  861.         sdp += 'a=inactive\r\n';
  862.       }
  863.  
  864.       // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet.
  865.       if (transceiver.rtpSender) {
  866.         var msid = 'msid:' + stream.id + ' ' +
  867.             transceiver.rtpSender.track.id + '\r\n';
  868.         sdp += 'a=' + msid;
  869.         sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + msid;
  870.       }
  871.       // FIXME: this should be written by writeRtpDescription.
  872.       sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' +
  873.           localCName + '\r\n';
  874.       return sdp;
  875.     };
  876.  
  877.     // Gets the direction from the mediaSection or the sessionpart.
  878.     SDPUtils.getDirection = function(mediaSection, sessionpart) {
  879.       // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
  880.       var lines = SDPUtils.splitLines(mediaSection);
  881.       for (var i = 0; i < lines.length; i++) {
  882.         switch (lines[i]) {
  883.           case 'a=sendrecv':
  884.           case 'a=sendonly':
  885.           case 'a=recvonly':
  886.           case 'a=inactive':
  887.             return lines[i].substr(2);
  888.         }
  889.       }
  890.       if (sessionpart) {
  891.         return SDPUtils.getDirection(sessionpart);
  892.       }
  893.       return 'sendrecv';
  894.     };
  895.  
  896.     // ORTC defines an RTCIceCandidate object but no constructor.
  897.     // Not implemented in Edge.
  898.     if (!window.RTCIceCandidate) {
  899.       window.RTCIceCandidate = function(args) {
  900.         return args;
  901.       };
  902.     }
  903.     // ORTC does not have a session description object but
  904.     // other browsers (i.e. Chrome) that will support both PC and ORTC
  905.     // in the future might have this defined already.
  906.     if (!window.RTCSessionDescription) {
  907.       window.RTCSessionDescription = function(args) {
  908.         return args;
  909.       };
  910.     }
  911.  
  912.     window.RTCPeerConnection = function(config) {
  913.       var self = this;
  914.  
  915.       this.onicecandidate = null;
  916.       this.onaddstream = null;
  917.       this.onremovestream = null;
  918.       this.onsignalingstatechange = null;
  919.       this.oniceconnectionstatechange = null;
  920.       this.onnegotiationneeded = null;
  921.       this.ondatachannel = null;
  922.  
  923.       this.localStreams = [];
  924.       this.remoteStreams = [];
  925.       this.getLocalStreams = function() { return self.localStreams; };
  926.       this.getRemoteStreams = function() { return self.remoteStreams; };
  927.  
  928.       this.localDescription = new RTCSessionDescription({
  929.         type: '',
  930.         sdp: ''
  931.       });
  932.       this.remoteDescription = new RTCSessionDescription({
  933.         type: '',
  934.         sdp: ''
  935.       });
  936.       this.signalingState = 'stable';
  937.       this.iceConnectionState = 'new';
  938.  
  939.       this.iceOptions = {
  940.         gatherPolicy: 'all',
  941.         iceServers: []
  942.       };
  943.       if (config && config.iceTransportPolicy) {
  944.         switch (config.iceTransportPolicy) {
  945.           case 'all':
  946.           case 'relay':
  947.             this.iceOptions.gatherPolicy = config.iceTransportPolicy;
  948.             break;
  949.           case 'none':
  950.             // FIXME: remove once implementation and spec have added this.
  951.             throw new TypeError('iceTransportPolicy "none" not supported');
  952.         }
  953.       }
  954.       if (config && config.iceServers) {
  955.         // Edge does not like
  956.         // 1) stun:
  957.         // 2) turn: that does not have all of turn:host:port?transport=udp
  958.         // 3) an array of urls
  959.         config.iceServers.forEach(function(server) {
  960.           if (server.urls) {
  961.             var url;
  962.             if (typeof(server.urls) === 'string') {
  963.               url = server.urls;
  964.             } else {
  965.               url = server.urls[0];
  966.             }
  967.             if (url.indexOf('transport=udp') !== -1) {
  968.               self.iceServers.push({
  969.                 username: server.username,
  970.                 credential: server.credential,
  971.                 urls: url
  972.               });
  973.             }
  974.           }
  975.         });
  976.       }
  977.  
  978.       // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
  979.       // everything that is needed to describe a SDP m-line.
  980.       this.transceivers = [];
  981.  
  982.       // since the iceGatherer is currently created in createOffer but we
  983.       // must not emit candidates until after setLocalDescription we buffer
  984.       // them in this array.
  985.       this._localIceCandidatesBuffer = [];
  986.     };
  987.  
  988.     window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
  989.       var self = this;
  990.       // FIXME: need to apply ice candidates in a way which is async but in-order
  991.       this._localIceCandidatesBuffer.forEach(function(event) {
  992.         if (self.onicecandidate !== null) {
  993.           self.onicecandidate(event);
  994.         }
  995.       });
  996.       this._localIceCandidatesBuffer = [];
  997.     };
  998.  
  999.     window.RTCPeerConnection.prototype.addStream = function(stream) {
  1000.       // Clone is necessary for local demos mostly, attaching directly
  1001.       // to two different senders does not work (build 10547).
  1002.       this.localStreams.push(stream.clone());
  1003.       this._maybeFireNegotiationNeeded();
  1004.     };
  1005.  
  1006.     window.RTCPeerConnection.prototype.removeStream = function(stream) {
  1007.       var idx = this.localStreams.indexOf(stream);
  1008.       if (idx > -1) {
  1009.         this.localStreams.splice(idx, 1);
  1010.         this._maybeFireNegotiationNeeded();
  1011.       }
  1012.     };
  1013.  
  1014.     // Determines the intersection of local and remote capabilities.
  1015.     window.RTCPeerConnection.prototype._getCommonCapabilities =
  1016.         function(localCapabilities, remoteCapabilities) {
  1017.       var commonCapabilities = {
  1018.         codecs: [],
  1019.         headerExtensions: [],
  1020.         fecMechanisms: []
  1021.       };
  1022.       localCapabilities.codecs.forEach(function(lCodec) {
  1023.         for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
  1024.           var rCodec = remoteCapabilities.codecs[i];
  1025.           if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
  1026.               lCodec.clockRate === rCodec.clockRate &&
  1027.               lCodec.numChannels === rCodec.numChannels) {
  1028.             // push rCodec so we reply with offerer payload type
  1029.             commonCapabilities.codecs.push(rCodec);
  1030.  
  1031.             // FIXME: also need to determine intersection between
  1032.             // .rtcpFeedback and .parameters
  1033.             break;
  1034.           }
  1035.         }
  1036.       });
  1037.  
  1038.       localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
  1039.         for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) {
  1040.           var rHeaderExtension = remoteCapabilities.headerExtensions[i];
  1041.           if (lHeaderExtension.uri === rHeaderExtension.uri) {
  1042.             commonCapabilities.headerExtensions.push(rHeaderExtension);
  1043.             break;
  1044.           }
  1045.         }
  1046.       });
  1047.  
  1048.       // FIXME: fecMechanisms
  1049.       return commonCapabilities;
  1050.     };
  1051.  
  1052.     // Create ICE gatherer, ICE transport and DTLS transport.
  1053.     window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
  1054.         function(mid, sdpMLineIndex) {
  1055.       var self = this;
  1056.       var iceGatherer = new RTCIceGatherer(self.iceOptions);
  1057.       var iceTransport = new RTCIceTransport(iceGatherer);
  1058.       iceGatherer.onlocalcandidate = function(evt) {
  1059.         var event = {};
  1060.         event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
  1061.  
  1062.         var cand = evt.candidate;
  1063.         // Edge emits an empty object for RTCIceCandidateComplete‥
  1064.         if (!cand || Object.keys(cand).length === 0) {
  1065.           // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet.
  1066.           if (iceGatherer.state === undefined) {
  1067.             iceGatherer.state = 'completed';
  1068.           }
  1069.  
  1070.           // Emit a candidate with type endOfCandidates to make the samples work.
  1071.           // Edge requires addIceCandidate with this empty candidate to start checking.
  1072.           // The real solution is to signal end-of-candidates to the other side when
  1073.           // getting the null candidate but some apps (like the samples) don't do that.
  1074.           event.candidate.candidate =
  1075.               'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';
  1076.         } else {
  1077.           // RTCIceCandidate doesn't have a component, needs to be added
  1078.           cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
  1079.           event.candidate.candidate = SDPUtils.writeCandidate(cand);
  1080.         }
  1081.  
  1082.         var complete = self.transceivers.every(function(transceiver) {
  1083.           return transceiver.iceGatherer &&
  1084.               transceiver.iceGatherer.state === 'completed';
  1085.         });
  1086.         // FIXME: update .localDescription with candidate and (potentially) end-of-candidates.
  1087.         //     To make this harder, the gatherer might emit candidates before localdescription
  1088.         //     is set. To make things worse, gather.getLocalCandidates still errors in
  1089.         //     Edge 10547 when no candidates have been gathered yet.
  1090.  
  1091.         if (self.onicecandidate !== null) {
  1092.           // Emit candidate if localDescription is set.
  1093.           // Also emits null candidate when all gatherers are complete.
  1094.           if (self.localDescription && self.localDescription.type === '') {
  1095.             self._localIceCandidatesBuffer.push(event);
  1096.             if (complete) {
  1097.               self._localIceCandidatesBuffer.push({});
  1098.             }
  1099.           } else {
  1100.             self.onicecandidate(event);
  1101.             if (complete) {
  1102.               self.onicecandidate({});
  1103.             }
  1104.           }
  1105.         }
  1106.       };
  1107.       iceTransport.onicestatechange = function() {
  1108.         self._updateConnectionState();
  1109.       };
  1110.  
  1111.       var dtlsTransport = new RTCDtlsTransport(iceTransport);
  1112.       dtlsTransport.ondtlsstatechange = function() {
  1113.         self._updateConnectionState();
  1114.       };
  1115.       dtlsTransport.onerror = function() {
  1116.         // onerror does not set state to failed by itself.
  1117.         dtlsTransport.state = 'failed';
  1118.         self._updateConnectionState();
  1119.       };
  1120.  
  1121.       return {
  1122.         iceGatherer: iceGatherer,
  1123.         iceTransport: iceTransport,
  1124.         dtlsTransport: dtlsTransport
  1125.       };
  1126.     };
  1127.  
  1128.     // Start the RTP Sender and Receiver for a transceiver.
  1129.     window.RTCPeerConnection.prototype._transceive = function(transceiver,
  1130.         send, recv) {
  1131.       var params = this._getCommonCapabilities(transceiver.localCapabilities,
  1132.           transceiver.remoteCapabilities);
  1133.       if (send && transceiver.rtpSender) {
  1134.         params.encodings = [{
  1135.           ssrc: transceiver.sendSsrc
  1136.         }];
  1137.         params.rtcp = {
  1138.           cname: localCName,
  1139.           ssrc: transceiver.recvSsrc
  1140.         };
  1141.         transceiver.rtpSender.send(params);
  1142.       }
  1143.       if (recv && transceiver.rtpReceiver) {
  1144.         params.encodings = [{
  1145.           ssrc: transceiver.recvSsrc
  1146.         }];
  1147.         params.rtcp = {
  1148.           cname: transceiver.cname,
  1149.           ssrc: transceiver.sendSsrc
  1150.         };
  1151.         transceiver.rtpReceiver.receive(params);
  1152.       }
  1153.     };
  1154.  
  1155.     window.RTCPeerConnection.prototype.setLocalDescription =
  1156.         function(description) {
  1157.       var self = this;
  1158.       if (description.type === 'offer') {
  1159.         if (!this._pendingOffer) {
  1160.         } else {
  1161.           this.transceivers = this._pendingOffer;
  1162.           delete this._pendingOffer;
  1163.         }
  1164.       } else if (description.type === 'answer') {
  1165.         var sections = SDPUtils.splitSections(self.remoteDescription.sdp);
  1166.         var sessionpart = sections.shift();
  1167.         sections.forEach(function(mediaSection, sdpMLineIndex) {
  1168.           var transceiver = self.transceivers[sdpMLineIndex];
  1169.           var iceGatherer = transceiver.iceGatherer;
  1170.           var iceTransport = transceiver.iceTransport;
  1171.           var dtlsTransport = transceiver.dtlsTransport;
  1172.           var localCapabilities = transceiver.localCapabilities;
  1173.           var remoteCapabilities = transceiver.remoteCapabilities;
  1174.           var rejected = mediaSection.split('\n', 1)[0]
  1175.               .split(' ', 2)[1] === '0';
  1176.  
  1177.           if (!rejected) {
  1178.             var remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
  1179.                 sessionpart);
  1180.             iceTransport.start(iceGatherer, remoteIceParameters, 'controlled');
  1181.  
  1182.             var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
  1183.               sessionpart);
  1184.             dtlsTransport.start(remoteDtlsParameters);
  1185.  
  1186.             // Calculate intersection of capabilities.
  1187.             var params = self._getCommonCapabilities(localCapabilities,
  1188.                 remoteCapabilities);
  1189.  
  1190.             // Start the RTCRtpSender. The RTCRtpReceiver for this transceiver
  1191.             // has already been started in setRemoteDescription.
  1192.             self._transceive(transceiver,
  1193.                 params.codecs.length > 0,
  1194.                 false);
  1195.           }
  1196.         });
  1197.       }
  1198.  
  1199.       this.localDescription = description;
  1200.       switch (description.type) {
  1201.         case 'offer':
  1202.           this._updateSignalingState('have-local-offer');
  1203.           break;
  1204.         case 'answer':
  1205.           this._updateSignalingState('stable');
  1206.           break;
  1207.         default:
  1208.           throw new TypeError('unsupported type "' + description.type + '"');
  1209.       }
  1210.  
  1211.       // If a success callback was provided, emit ICE candidates after it has been
  1212.       // executed. Otherwise, emit callback after the Promise is resolved.
  1213.       var hasCallback = arguments.length > 1 &&
  1214.         typeof arguments[1] === 'function';
  1215.       if (hasCallback) {
  1216.         var cb = arguments[1];
  1217.         window.setTimeout(function() {
  1218.           cb();
  1219.           self._emitBufferedCandidates();
  1220.         }, 0);
  1221.       }
  1222.       var p = Promise.resolve();
  1223.       p.then(function() {
  1224.         if (!hasCallback) {
  1225.           window.setTimeout(self._emitBufferedCandidates.bind(self), 0);
  1226.         }
  1227.       });
  1228.       return p;
  1229.     };
  1230.  
  1231.     window.RTCPeerConnection.prototype.setRemoteDescription =
  1232.         function(description) {
  1233.       var self = this;
  1234.       var stream = new MediaStream();
  1235.       var sections = SDPUtils.splitSections(description.sdp);
  1236.       var sessionpart = sections.shift();
  1237.       sections.forEach(function(mediaSection, sdpMLineIndex) {
  1238.         var lines = SDPUtils.splitLines(mediaSection);
  1239.         var mline = lines[0].substr(2).split(' ');
  1240.         var kind = mline[0];
  1241.         var rejected = mline[1] === '0';
  1242.         var direction = SDPUtils.getDirection(mediaSection, sessionpart);
  1243.  
  1244.         var transceiver;
  1245.         var iceGatherer;
  1246.         var iceTransport;
  1247.         var dtlsTransport;
  1248.         var rtpSender;
  1249.         var rtpReceiver;
  1250.         var sendSsrc;
  1251.         var recvSsrc;
  1252.         var localCapabilities;
  1253.  
  1254.         // FIXME: ensure the mediaSection has rtcp-mux set.
  1255.         var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
  1256.         var remoteIceParameters;
  1257.         var remoteDtlsParameters;
  1258.         if (!rejected) {
  1259.           remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
  1260.               sessionpart);
  1261.           remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
  1262.               sessionpart);
  1263.         }
  1264.         var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6);
  1265.  
  1266.         var cname;
  1267.         // Gets the first SSRC. Note that with RTX there might be multiple SSRCs.
  1268.         var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
  1269.             .map(function(line) {
  1270.               return SDPUtils.parseSsrcMedia(line);
  1271.             })
  1272.             .filter(function(obj) {
  1273.               return obj.attribute === 'cname';
  1274.             })[0];
  1275.         if (remoteSsrc) {
  1276.           recvSsrc = parseInt(remoteSsrc.ssrc, 10);
  1277.           cname = remoteSsrc.value;
  1278.         }
  1279.  
  1280.         if (description.type === 'offer') {
  1281.           var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
  1282.  
  1283.           localCapabilities = RTCRtpReceiver.getCapabilities(kind);
  1284.           sendSsrc = (2 * sdpMLineIndex + 2) * 1001;
  1285.  
  1286.           rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
  1287.  
  1288.           // FIXME: not correct when there are multiple streams but that is
  1289.           // not currently supported in this shim.
  1290.           stream.addTrack(rtpReceiver.track);
  1291.  
  1292.           // FIXME: look at direction.
  1293.           if (self.localStreams.length > 0 &&
  1294.               self.localStreams[0].getTracks().length >= sdpMLineIndex) {
  1295.             // FIXME: actually more complicated, needs to match types etc
  1296.             var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex];
  1297.             rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport);
  1298.           }
  1299.  
  1300.           self.transceivers[sdpMLineIndex] = {
  1301.             iceGatherer: transports.iceGatherer,
  1302.             iceTransport: transports.iceTransport,
  1303.             dtlsTransport: transports.dtlsTransport,
  1304.             localCapabilities: localCapabilities,
  1305.             remoteCapabilities: remoteCapabilities,
  1306.             rtpSender: rtpSender,
  1307.             rtpReceiver: rtpReceiver,
  1308.             kind: kind,
  1309.             mid: mid,
  1310.             cname: cname,
  1311.             sendSsrc: sendSsrc,
  1312.             recvSsrc: recvSsrc
  1313.           };
  1314.           // Start the RTCRtpReceiver now. The RTPSender is started in setLocalDescription.
  1315.           self._transceive(self.transceivers[sdpMLineIndex],
  1316.               false,
  1317.               direction === 'sendrecv' || direction === 'sendonly');
  1318.         } else if (description.type === 'answer' && !rejected) {
  1319.           transceiver = self.transceivers[sdpMLineIndex];
  1320.           iceGatherer = transceiver.iceGatherer;
  1321.           iceTransport = transceiver.iceTransport;
  1322.           dtlsTransport = transceiver.dtlsTransport;
  1323.           rtpSender = transceiver.rtpSender;
  1324.           rtpReceiver = transceiver.rtpReceiver;
  1325.           sendSsrc = transceiver.sendSsrc;
  1326.           //recvSsrc = transceiver.recvSsrc;
  1327.           localCapabilities = transceiver.localCapabilities;
  1328.  
  1329.           self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc;
  1330.           self.transceivers[sdpMLineIndex].remoteCapabilities =
  1331.               remoteCapabilities;
  1332.           self.transceivers[sdpMLineIndex].cname = cname;
  1333.  
  1334.           iceTransport.start(iceGatherer, remoteIceParameters, 'controlling');
  1335.           dtlsTransport.start(remoteDtlsParameters);
  1336.  
  1337.           self._transceive(transceiver,
  1338.               direction === 'sendrecv' || direction === 'recvonly',
  1339.               direction === 'sendrecv' || direction === 'sendonly');
  1340.  
  1341.           if (rtpReceiver &&
  1342.               (direction === 'sendrecv' || direction === 'sendonly')) {
  1343.             stream.addTrack(rtpReceiver.track);
  1344.           } else {
  1345.             // FIXME: actually the receiver should be created later.
  1346.             delete transceiver.rtpReceiver;
  1347.           }
  1348.         }
  1349.       });
  1350.  
  1351.       this.remoteDescription = description;
  1352.       switch (description.type) {
  1353.         case 'offer':
  1354.           this._updateSignalingState('have-remote-offer');
  1355.           break;
  1356.         case 'answer':
  1357.           this._updateSignalingState('stable');
  1358.           break;
  1359.         default:
  1360.           throw new TypeError('unsupported type "' + description.type + '"');
  1361.       }
  1362.       window.setTimeout(function() {
  1363.         if (self.onaddstream !== null && stream.getTracks().length) {
  1364.           self.remoteStreams.push(stream);
  1365.           window.setTimeout(function() {
  1366.             self.onaddstream({stream: stream});
  1367.           }, 0);
  1368.         }
  1369.       }, 0);
  1370.       if (arguments.length > 1 && typeof arguments[1] === 'function') {
  1371.         window.setTimeout(arguments[1], 0);
  1372.       }
  1373.       return Promise.resolve();
  1374.     };
  1375.  
  1376.     window.RTCPeerConnection.prototype.close = function() {
  1377.       this.transceivers.forEach(function(transceiver) {
  1378.         /* not yet
  1379.         if (transceiver.iceGatherer) {
  1380.           transceiver.iceGatherer.close();
  1381.         }
  1382.         */
  1383.         if (transceiver.iceTransport) {
  1384.           transceiver.iceTransport.stop();
  1385.         }
  1386.         if (transceiver.dtlsTransport) {
  1387.           transceiver.dtlsTransport.stop();
  1388.         }
  1389.         if (transceiver.rtpSender) {
  1390.           transceiver.rtpSender.stop();
  1391.         }
  1392.         if (transceiver.rtpReceiver) {
  1393.           transceiver.rtpReceiver.stop();
  1394.         }
  1395.       });
  1396.       // FIXME: clean up tracks, local streams, remote streams, etc
  1397.       this._updateSignalingState('closed');
  1398.     };
  1399.  
  1400.     // Update the signaling state.
  1401.     window.RTCPeerConnection.prototype._updateSignalingState =
  1402.         function(newState) {
  1403.       this.signalingState = newState;
  1404.       if (this.onsignalingstatechange !== null) {
  1405.         this.onsignalingstatechange();
  1406.       }
  1407.     };
  1408.  
  1409.     // Determine whether to fire the negotiationneeded event.
  1410.     window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded =
  1411.         function() {
  1412.       // Fire away (for now).
  1413.       if (this.onnegotiationneeded !== null) {
  1414.         this.onnegotiationneeded();
  1415.       }
  1416.     };
  1417.  
  1418.     // Update the connection state.
  1419.     window.RTCPeerConnection.prototype._updateConnectionState =
  1420.         function() {
  1421.       var self = this;
  1422.       var newState;
  1423.       var states = {
  1424.         'new': 0,
  1425.         closed: 0,
  1426.         connecting: 0,
  1427.         checking: 0,
  1428.         connected: 0,
  1429.         completed: 0,
  1430.         failed: 0
  1431.       };
  1432.       this.transceivers.forEach(function(transceiver) {
  1433.         states[transceiver.iceTransport.state]++;
  1434.         states[transceiver.dtlsTransport.state]++;
  1435.       });
  1436.       // ICETransport.completed and connected are the same for this purpose.
  1437.       states.connected += states.completed;
  1438.  
  1439.       newState = 'new';
  1440.       if (states.failed > 0) {
  1441.         newState = 'failed';
  1442.       } else if (states.connecting > 0 || states.checking > 0) {
  1443.         newState = 'connecting';
  1444.       } else if (states.disconnected > 0) {
  1445.         newState = 'disconnected';
  1446.       } else if (states.new > 0) {
  1447.         newState = 'new';
  1448.       } else if (states.connecting > 0 || states.completed > 0) {
  1449.         newState = 'connected';
  1450.       }
  1451.  
  1452.       if (newState !== self.iceConnectionState) {
  1453.         self.iceConnectionState = newState;
  1454.         if (this.oniceconnectionstatechange !== null) {
  1455.           this.oniceconnectionstatechange();
  1456.         }
  1457.       }
  1458.     };
  1459.  
  1460.     window.RTCPeerConnection.prototype.createOffer = function() {
  1461.       var self = this;
  1462.       if (this._pendingOffer) {
  1463.         throw new Error('createOffer called while there is a pending offer.');
  1464.       }
  1465.       var offerOptions;
  1466.       if (arguments.length === 1 && typeof arguments[0] !== 'function') {
  1467.         offerOptions = arguments[0];
  1468.       } else if (arguments.length === 3) {
  1469.         offerOptions = arguments[2];
  1470.       }
  1471.  
  1472.       var tracks = [];
  1473.       var numAudioTracks = 0;
  1474.       var numVideoTracks = 0;
  1475.       // Default to sendrecv.
  1476.       if (this.localStreams.length) {
  1477.         numAudioTracks = this.localStreams[0].getAudioTracks().length;
  1478.         numVideoTracks = this.localStreams[0].getVideoTracks().length;
  1479.       }
  1480.       // Determine number of audio and video tracks we need to send/recv.
  1481.       if (offerOptions) {
  1482.         // Reject Chrome legacy constraints.
  1483.         if (offerOptions.mandatory || offerOptions.optional) {
  1484.           throw new TypeError(
  1485.               'Legacy mandatory/optional constraints not supported.');
  1486.         }
  1487.         if (offerOptions.offerToReceiveAudio !== undefined) {
  1488.           numAudioTracks = offerOptions.offerToReceiveAudio;
  1489.         }
  1490.         if (offerOptions.offerToReceiveVideo !== undefined) {
  1491.           numVideoTracks = offerOptions.offerToReceiveVideo;
  1492.         }
  1493.       }
  1494.       if (this.localStreams.length) {
  1495.         // Push local streams.
  1496.         this.localStreams[0].getTracks().forEach(function(track) {
  1497.           tracks.push({
  1498.             kind: track.kind,
  1499.             track: track,
  1500.             wantReceive: track.kind === 'audio' ?
  1501.                 numAudioTracks > 0 : numVideoTracks > 0
  1502.           });
  1503.           if (track.kind === 'audio') {
  1504.             numAudioTracks--;
  1505.           } else if (track.kind === 'video') {
  1506.             numVideoTracks--;
  1507.           }
  1508.         });
  1509.       }
  1510.       // Create M-lines for recvonly streams.
  1511.       while (numAudioTracks > 0 || numVideoTracks > 0) {
  1512.         if (numAudioTracks > 0) {
  1513.           tracks.push({
  1514.             kind: 'audio',
  1515.             wantReceive: true
  1516.           });
  1517.           numAudioTracks--;
  1518.         }
  1519.         if (numVideoTracks > 0) {
  1520.           tracks.push({
  1521.             kind: 'video',
  1522.             wantReceive: true
  1523.           });
  1524.           numVideoTracks--;
  1525.         }
  1526.       }
  1527.  
  1528.       var sdp = SDPUtils.writeSessionBoilerplate();
  1529.       var transceivers = [];
  1530.       tracks.forEach(function(mline, sdpMLineIndex) {
  1531.         // For each track, create an ice gatherer, ice transport, dtls transport,
  1532.         // potentially rtpsender and rtpreceiver.
  1533.         var track = mline.track;
  1534.         var kind = mline.kind;
  1535.         var mid = generateIdentifier();
  1536.  
  1537.         var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
  1538.  
  1539.         var localCapabilities = RTCRtpSender.getCapabilities(kind);
  1540.         var rtpSender;
  1541.         var rtpReceiver;
  1542.  
  1543.         // generate an ssrc now, to be used later in rtpSender.send
  1544.         var sendSsrc = (2 * sdpMLineIndex + 1) * 1001;
  1545.         if (track) {
  1546.           rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
  1547.         }
  1548.  
  1549.         if (mline.wantReceive) {
  1550.           rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
  1551.         }
  1552.  
  1553.         transceivers[sdpMLineIndex] = {
  1554.           iceGatherer: transports.iceGatherer,
  1555.           iceTransport: transports.iceTransport,
  1556.           dtlsTransport: transports.dtlsTransport,
  1557.           localCapabilities: localCapabilities,
  1558.           remoteCapabilities: null,
  1559.           rtpSender: rtpSender,
  1560.           rtpReceiver: rtpReceiver,
  1561.           kind: kind,
  1562.           mid: mid,
  1563.           sendSsrc: sendSsrc,
  1564.           recvSsrc: null
  1565.         };
  1566.         var transceiver = transceivers[sdpMLineIndex];
  1567.         sdp += SDPUtils.writeMediaSection(transceiver,
  1568.             transceiver.localCapabilities, 'offer', self.localStreams[0]);
  1569.       });
  1570.  
  1571.       this._pendingOffer = transceivers;
  1572.       var desc = new RTCSessionDescription({
  1573.         type: 'offer',
  1574.         sdp: sdp
  1575.       });
  1576.       if (arguments.length && typeof arguments[0] === 'function') {
  1577.         window.setTimeout(arguments[0], 0, desc);
  1578.       }
  1579.       return Promise.resolve(desc);
  1580.     };
  1581.  
  1582.     window.RTCPeerConnection.prototype.createAnswer = function() {
  1583.       var self = this;
  1584.       var answerOptions;
  1585.       if (arguments.length === 1 && typeof arguments[0] !== 'function') {
  1586.         answerOptions = arguments[0];
  1587.       } else if (arguments.length === 3) {
  1588.         answerOptions = arguments[2];
  1589.       }
  1590.  
  1591.       var sdp = SDPUtils.writeSessionBoilerplate();
  1592.       this.transceivers.forEach(function(transceiver) {
  1593.         // Calculate intersection of capabilities.
  1594.         var commonCapabilities = self._getCommonCapabilities(
  1595.             transceiver.localCapabilities,
  1596.             transceiver.remoteCapabilities);
  1597.  
  1598.         sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
  1599.             'answer', self.localStreams[0]);
  1600.       });
  1601.  
  1602.       var desc = new RTCSessionDescription({
  1603.         type: 'answer',
  1604.         sdp: sdp
  1605.       });
  1606.       if (arguments.length && typeof arguments[0] === 'function') {
  1607.         window.setTimeout(arguments[0], 0, desc);
  1608.       }
  1609.       return Promise.resolve(desc);
  1610.     };
  1611.  
  1612.     window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
  1613.       var mLineIndex = candidate.sdpMLineIndex;
  1614.       if (candidate.sdpMid) {
  1615.         for (var i = 0; i < this.transceivers.length; i++) {
  1616.           if (this.transceivers[i].mid === candidate.sdpMid) {
  1617.             mLineIndex = i;
  1618.             break;
  1619.           }
  1620.         }
  1621.       }
  1622.       var transceiver = this.transceivers[mLineIndex];
  1623.       if (transceiver) {
  1624.         var cand = Object.keys(candidate.candidate).length > 0 ?
  1625.             SDPUtils.parseCandidate(candidate.candidate) : {};
  1626.         // Ignore Chrome's invalid candidates since Edge does not like them.
  1627.         if (cand.protocol === 'tcp' && cand.port === 0) {
  1628.           return;
  1629.         }
  1630.         // Ignore RTCP candidates, we assume RTCP-MUX.
  1631.         if (cand.component !== '1') {
  1632.           return;
  1633.         }
  1634.         // A dirty hack to make samples work.
  1635.         if (cand.type === 'endOfCandidates') {
  1636.           cand = {};
  1637.         }
  1638.         transceiver.iceTransport.addRemoteCandidate(cand);
  1639.       }
  1640.       if (arguments.length > 1 && typeof arguments[1] === 'function') {
  1641.         window.setTimeout(arguments[1], 0);
  1642.       }
  1643.       return Promise.resolve();
  1644.     };
  1645.  
  1646.     window.RTCPeerConnection.prototype.getStats = function() {
  1647.       var promises = [];
  1648.       this.transceivers.forEach(function(transceiver) {
  1649.         ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
  1650.             'dtlsTransport'].forEach(function(method) {
  1651.           if (transceiver[method]) {
  1652.             promises.push(transceiver[method].getStats());
  1653.           }
  1654.         });
  1655.       });
  1656.       var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
  1657.           arguments[1];
  1658.       return new Promise(function(resolve) {
  1659.         var results = {};
  1660.         Promise.all(promises).then(function(res) {
  1661.           res.forEach(function(result) {
  1662.             Object.keys(result).forEach(function(id) {
  1663.               results[id] = result[id];
  1664.             });
  1665.           });
  1666.           if (cb) {
  1667.             window.setTimeout(cb, 0, results);
  1668.           }
  1669.           resolve(results);
  1670.         });
  1671.       });
  1672.     };
  1673.   }
  1674. } else {
  1675.   webrtcUtils.log('Browser does not appear to be WebRTC-capable');
  1676. }
  1677.  
  1678. // Returns the result of getUserMedia as a Promise.
  1679. function requestUserMedia(constraints) {
  1680.   return new Promise(function(resolve, reject) {
  1681.     getUserMedia(constraints, resolve, reject);
  1682.   });
  1683. }
  1684.  
  1685. var webrtcTesting = {};
  1686. try {
  1687.   Object.defineProperty(webrtcTesting, 'version', {
  1688.     set: function(version) {
  1689.       webrtcDetectedVersion = version;
  1690.     }
  1691.   });
  1692. } catch (e) {}
  1693.  
  1694. if (typeof module !== 'undefined') {
  1695.   var RTCPeerConnection;
  1696.   var RTCIceCandidate;
  1697.   var RTCSessionDescription;
  1698.   if (typeof window !== 'undefined') {
  1699.     RTCPeerConnection = window.RTCPeerConnection;
  1700.     RTCIceCandidate = window.RTCIceCandidate;
  1701.     RTCSessionDescription = window.RTCSessionDescription;
  1702.   }
  1703.   module.exports = {
  1704.     RTCPeerConnection: RTCPeerConnection,
  1705.     RTCIceCandidate: RTCIceCandidate,
  1706.     RTCSessionDescription: RTCSessionDescription,
  1707.     getUserMedia: getUserMedia,
  1708.     attachMediaStream: attachMediaStream,
  1709.     reattachMediaStream: reattachMediaStream,
  1710.     webrtcDetectedBrowser: webrtcDetectedBrowser,
  1711.     webrtcDetectedVersion: webrtcDetectedVersion,
  1712.     webrtcMinimumVersion: webrtcMinimumVersion,
  1713.     webrtcTesting: webrtcTesting,
  1714.     webrtcUtils: webrtcUtils
  1715.     //requestUserMedia: not exposed on purpose.
  1716.     //trace: not exposed on purpose.
  1717.   };
  1718. } else if ((typeof require === 'function') && (typeof define === 'function')) {
  1719.   // Expose objects and functions when RequireJS is doing the loading.
  1720.   define([], function() {
  1721.     return {
  1722.       RTCPeerConnection: window.RTCPeerConnection,
  1723.       RTCIceCandidate: window.RTCIceCandidate,
  1724.       RTCSessionDescription: window.RTCSessionDescription,
  1725.       getUserMedia: getUserMedia,
  1726.       attachMediaStream: attachMediaStream,
  1727.       reattachMediaStream: reattachMediaStream,
  1728.       webrtcDetectedBrowser: webrtcDetectedBrowser,
  1729.       webrtcDetectedVersion: webrtcDetectedVersion,
  1730.       webrtcMinimumVersion: webrtcMinimumVersion,
  1731.       webrtcTesting: webrtcTesting,
  1732.       webrtcUtils: webrtcUtils
  1733.       //requestUserMedia: not exposed on purpose.
  1734.       //trace: not exposed on purpose.
  1735.     };
  1736.   });
  1737. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement