5552 lines
		
	
	
		
			192 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			5552 lines
		
	
	
		
			192 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 
 | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | var _adapter_factory = require('./adapter_factory.js'); | |||
|  | 
 | |||
|  | var adapter = (0, _adapter_factory.adapterFactory)({ window: window }); | |||
|  | module.exports = adapter; // this is the difference from adapter_core.
 | |||
|  | 
 | |||
|  | },{"./adapter_factory.js":2}],2:[function(require,module,exports){ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.adapterFactory = adapterFactory; | |||
|  | 
 | |||
|  | var _utils = require('./utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | var _chrome_shim = require('./chrome/chrome_shim'); | |||
|  | 
 | |||
|  | var chromeShim = _interopRequireWildcard(_chrome_shim); | |||
|  | 
 | |||
|  | var _edge_shim = require('./edge/edge_shim'); | |||
|  | 
 | |||
|  | var edgeShim = _interopRequireWildcard(_edge_shim); | |||
|  | 
 | |||
|  | var _firefox_shim = require('./firefox/firefox_shim'); | |||
|  | 
 | |||
|  | var firefoxShim = _interopRequireWildcard(_firefox_shim); | |||
|  | 
 | |||
|  | var _safari_shim = require('./safari/safari_shim'); | |||
|  | 
 | |||
|  | var safariShim = _interopRequireWildcard(_safari_shim); | |||
|  | 
 | |||
|  | var _common_shim = require('./common_shim'); | |||
|  | 
 | |||
|  | var commonShim = _interopRequireWildcard(_common_shim); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | // Shimming starts here.
 | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | function adapterFactory() { | |||
|  |   var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, | |||
|  |       window = _ref.window; | |||
|  | 
 | |||
|  |   var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { | |||
|  |     shimChrome: true, | |||
|  |     shimFirefox: true, | |||
|  |     shimEdge: true, | |||
|  |     shimSafari: true | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Utils.
 | |||
|  |   var logging = utils.log; | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  | 
 | |||
|  |   var adapter = { | |||
|  |     browserDetails: browserDetails, | |||
|  |     commonShim: commonShim, | |||
|  |     extractVersion: utils.extractVersion, | |||
|  |     disableLog: utils.disableLog, | |||
|  |     disableWarnings: utils.disableWarnings | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Shim browser if found.
 | |||
|  |   switch (browserDetails.browser) { | |||
|  |     case 'chrome': | |||
|  |       if (!chromeShim || !chromeShim.shimPeerConnection || !options.shimChrome) { | |||
|  |         logging('Chrome shim is not included in this adapter release.'); | |||
|  |         return adapter; | |||
|  |       } | |||
|  |       logging('adapter.js shimming chrome.'); | |||
|  |       // Export to the adapter global object visible in the browser.
 | |||
|  |       adapter.browserShim = chromeShim; | |||
|  | 
 | |||
|  |       chromeShim.shimGetUserMedia(window); | |||
|  |       chromeShim.shimMediaStream(window); | |||
|  |       chromeShim.shimPeerConnection(window); | |||
|  |       chromeShim.shimOnTrack(window); | |||
|  |       chromeShim.shimAddTrackRemoveTrack(window); | |||
|  |       chromeShim.shimGetSendersWithDtmf(window); | |||
|  |       chromeShim.shimGetStats(window); | |||
|  |       chromeShim.shimSenderReceiverGetStats(window); | |||
|  |       chromeShim.fixNegotiationNeeded(window); | |||
|  | 
 | |||
|  |       commonShim.shimRTCIceCandidate(window); | |||
|  |       commonShim.shimConnectionState(window); | |||
|  |       commonShim.shimMaxMessageSize(window); | |||
|  |       commonShim.shimSendThrowTypeError(window); | |||
|  |       commonShim.removeAllowExtmapMixed(window); | |||
|  |       break; | |||
|  |     case 'firefox': | |||
|  |       if (!firefoxShim || !firefoxShim.shimPeerConnection || !options.shimFirefox) { | |||
|  |         logging('Firefox shim is not included in this adapter release.'); | |||
|  |         return adapter; | |||
|  |       } | |||
|  |       logging('adapter.js shimming firefox.'); | |||
|  |       // Export to the adapter global object visible in the browser.
 | |||
|  |       adapter.browserShim = firefoxShim; | |||
|  | 
 | |||
|  |       firefoxShim.shimGetUserMedia(window); | |||
|  |       firefoxShim.shimPeerConnection(window); | |||
|  |       firefoxShim.shimOnTrack(window); | |||
|  |       firefoxShim.shimRemoveStream(window); | |||
|  |       firefoxShim.shimSenderGetStats(window); | |||
|  |       firefoxShim.shimReceiverGetStats(window); | |||
|  |       firefoxShim.shimRTCDataChannel(window); | |||
|  |       firefoxShim.shimAddTransceiver(window); | |||
|  |       firefoxShim.shimCreateOffer(window); | |||
|  |       firefoxShim.shimCreateAnswer(window); | |||
|  | 
 | |||
|  |       commonShim.shimRTCIceCandidate(window); | |||
|  |       commonShim.shimConnectionState(window); | |||
|  |       commonShim.shimMaxMessageSize(window); | |||
|  |       commonShim.shimSendThrowTypeError(window); | |||
|  |       break; | |||
|  |     case 'edge': | |||
|  |       if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { | |||
|  |         logging('MS edge shim is not included in this adapter release.'); | |||
|  |         return adapter; | |||
|  |       } | |||
|  |       logging('adapter.js shimming edge.'); | |||
|  |       // Export to the adapter global object visible in the browser.
 | |||
|  |       adapter.browserShim = edgeShim; | |||
|  | 
 | |||
|  |       edgeShim.shimGetUserMedia(window); | |||
|  |       edgeShim.shimGetDisplayMedia(window); | |||
|  |       edgeShim.shimPeerConnection(window); | |||
|  |       edgeShim.shimReplaceTrack(window); | |||
|  | 
 | |||
|  |       // the edge shim implements the full RTCIceCandidate object.
 | |||
|  | 
 | |||
|  |       commonShim.shimMaxMessageSize(window); | |||
|  |       commonShim.shimSendThrowTypeError(window); | |||
|  |       break; | |||
|  |     case 'safari': | |||
|  |       if (!safariShim || !options.shimSafari) { | |||
|  |         logging('Safari shim is not included in this adapter release.'); | |||
|  |         return adapter; | |||
|  |       } | |||
|  |       logging('adapter.js shimming safari.'); | |||
|  |       // Export to the adapter global object visible in the browser.
 | |||
|  |       adapter.browserShim = safariShim; | |||
|  | 
 | |||
|  |       safariShim.shimRTCIceServerUrls(window); | |||
|  |       safariShim.shimCreateOfferLegacy(window); | |||
|  |       safariShim.shimCallbacksAPI(window); | |||
|  |       safariShim.shimLocalStreamsAPI(window); | |||
|  |       safariShim.shimRemoteStreamsAPI(window); | |||
|  |       safariShim.shimTrackEventTransceiver(window); | |||
|  |       safariShim.shimGetUserMedia(window); | |||
|  | 
 | |||
|  |       commonShim.shimRTCIceCandidate(window); | |||
|  |       commonShim.shimMaxMessageSize(window); | |||
|  |       commonShim.shimSendThrowTypeError(window); | |||
|  |       commonShim.removeAllowExtmapMixed(window); | |||
|  |       break; | |||
|  |     default: | |||
|  |       logging('Unsupported browser!'); | |||
|  |       break; | |||
|  |   } | |||
|  | 
 | |||
|  |   return adapter; | |||
|  | } | |||
|  | 
 | |||
|  | // Browser shims.
 | |||
|  | 
 | |||
|  | },{"./chrome/chrome_shim":3,"./common_shim":6,"./edge/edge_shim":7,"./firefox/firefox_shim":11,"./safari/safari_shim":14,"./utils":15}],3:[function(require,module,exports){ | |||
|  | 
 | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | var _getusermedia = require('./getusermedia'); | |||
|  | 
 | |||
|  | Object.defineProperty(exports, 'shimGetUserMedia', { | |||
|  |   enumerable: true, | |||
|  |   get: function get() { | |||
|  |     return _getusermedia.shimGetUserMedia; | |||
|  |   } | |||
|  | }); | |||
|  | 
 | |||
|  | var _getdisplaymedia = require('./getdisplaymedia'); | |||
|  | 
 | |||
|  | Object.defineProperty(exports, 'shimGetDisplayMedia', { | |||
|  |   enumerable: true, | |||
|  |   get: function get() { | |||
|  |     return _getdisplaymedia.shimGetDisplayMedia; | |||
|  |   } | |||
|  | }); | |||
|  | exports.shimMediaStream = shimMediaStream; | |||
|  | exports.shimOnTrack = shimOnTrack; | |||
|  | exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; | |||
|  | exports.shimGetStats = shimGetStats; | |||
|  | exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; | |||
|  | exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; | |||
|  | exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; | |||
|  | exports.shimPeerConnection = shimPeerConnection; | |||
|  | exports.fixNegotiationNeeded = fixNegotiationNeeded; | |||
|  | 
 | |||
|  | var _utils = require('../utils.js'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | |||
|  | 
 | |||
|  | function shimMediaStream(window) { | |||
|  |   window.MediaStream = window.MediaStream || window.webkitMediaStream; | |||
|  | } | |||
|  | 
 | |||
|  | function shimOnTrack(window) { | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { | |||
|  |     Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { | |||
|  |       get: function get() { | |||
|  |         return this._ontrack; | |||
|  |       }, | |||
|  |       set: function set(f) { | |||
|  |         if (this._ontrack) { | |||
|  |           this.removeEventListener('track', this._ontrack); | |||
|  |         } | |||
|  |         this.addEventListener('track', this._ontrack = f); | |||
|  |       }, | |||
|  | 
 | |||
|  |       enumerable: true, | |||
|  |       configurable: true | |||
|  |     }); | |||
|  |     var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; | |||
|  |     window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { | |||
|  |       var _this = this; | |||
|  | 
 | |||
|  |       if (!this._ontrackpoly) { | |||
|  |         this._ontrackpoly = function (e) { | |||
|  |           // onaddstream does not fire when a track is added to an existing
 | |||
|  |           // stream. But stream.onaddtrack is implemented so we use that.
 | |||
|  |           e.stream.addEventListener('addtrack', function (te) { | |||
|  |             var receiver = void 0; | |||
|  |             if (window.RTCPeerConnection.prototype.getReceivers) { | |||
|  |               receiver = _this.getReceivers().find(function (r) { | |||
|  |                 return r.track && r.track.id === te.track.id; | |||
|  |               }); | |||
|  |             } else { | |||
|  |               receiver = { track: te.track }; | |||
|  |             } | |||
|  | 
 | |||
|  |             var event = new Event('track'); | |||
|  |             event.track = te.track; | |||
|  |             event.receiver = receiver; | |||
|  |             event.transceiver = { receiver: receiver }; | |||
|  |             event.streams = [e.stream]; | |||
|  |             _this.dispatchEvent(event); | |||
|  |           }); | |||
|  |           e.stream.getTracks().forEach(function (track) { | |||
|  |             var receiver = void 0; | |||
|  |             if (window.RTCPeerConnection.prototype.getReceivers) { | |||
|  |               receiver = _this.getReceivers().find(function (r) { | |||
|  |                 return r.track && r.track.id === track.id; | |||
|  |               }); | |||
|  |             } else { | |||
|  |               receiver = { track: track }; | |||
|  |             } | |||
|  |             var event = new Event('track'); | |||
|  |             event.track = track; | |||
|  |             event.receiver = receiver; | |||
|  |             event.transceiver = { receiver: receiver }; | |||
|  |             event.streams = [e.stream]; | |||
|  |             _this.dispatchEvent(event); | |||
|  |           }); | |||
|  |         }; | |||
|  |         this.addEventListener('addstream', this._ontrackpoly); | |||
|  |       } | |||
|  |       return origSetRemoteDescription.apply(this, arguments); | |||
|  |     }; | |||
|  |   } else { | |||
|  |     // even if RTCRtpTransceiver is in window, it is only used and
 | |||
|  |     // emitted in unified-plan. Unfortunately this means we need
 | |||
|  |     // to unconditionally wrap the event.
 | |||
|  |     utils.wrapPeerConnectionEvent(window, 'track', function (e) { | |||
|  |       if (!e.transceiver) { | |||
|  |         Object.defineProperty(e, 'transceiver', { value: { receiver: e.receiver } }); | |||
|  |       } | |||
|  |       return e; | |||
|  |     }); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimGetSendersWithDtmf(window) { | |||
|  |   // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
 | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { | |||
|  |     var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) { | |||
|  |       return { | |||
|  |         track: track, | |||
|  |         get dtmf() { | |||
|  |           if (this._dtmf === undefined) { | |||
|  |             if (track.kind === 'audio') { | |||
|  |               this._dtmf = pc.createDTMFSender(track); | |||
|  |             } else { | |||
|  |               this._dtmf = null; | |||
|  |             } | |||
|  |           } | |||
|  |           return this._dtmf; | |||
|  |         }, | |||
|  |         _pc: pc | |||
|  |       }; | |||
|  |     }; | |||
|  | 
 | |||
|  |     // augment addTrack when getSenders is not available.
 | |||
|  |     if (!window.RTCPeerConnection.prototype.getSenders) { | |||
|  |       window.RTCPeerConnection.prototype.getSenders = function getSenders() { | |||
|  |         this._senders = this._senders || []; | |||
|  |         return this._senders.slice(); // return a copy of the internal state.
 | |||
|  |       }; | |||
|  |       var origAddTrack = window.RTCPeerConnection.prototype.addTrack; | |||
|  |       window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { | |||
|  |         var sender = origAddTrack.apply(this, arguments); | |||
|  |         if (!sender) { | |||
|  |           sender = shimSenderWithDtmf(this, track); | |||
|  |           this._senders.push(sender); | |||
|  |         } | |||
|  |         return sender; | |||
|  |       }; | |||
|  | 
 | |||
|  |       var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; | |||
|  |       window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { | |||
|  |         origRemoveTrack.apply(this, arguments); | |||
|  |         var idx = this._senders.indexOf(sender); | |||
|  |         if (idx !== -1) { | |||
|  |           this._senders.splice(idx, 1); | |||
|  |         } | |||
|  |       }; | |||
|  |     } | |||
|  |     var origAddStream = window.RTCPeerConnection.prototype.addStream; | |||
|  |     window.RTCPeerConnection.prototype.addStream = function addStream(stream) { | |||
|  |       var _this2 = this; | |||
|  | 
 | |||
|  |       this._senders = this._senders || []; | |||
|  |       origAddStream.apply(this, [stream]); | |||
|  |       stream.getTracks().forEach(function (track) { | |||
|  |         _this2._senders.push(shimSenderWithDtmf(_this2, track)); | |||
|  |       }); | |||
|  |     }; | |||
|  | 
 | |||
|  |     var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; | |||
|  |     window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { | |||
|  |       var _this3 = this; | |||
|  | 
 | |||
|  |       this._senders = this._senders || []; | |||
|  |       origRemoveStream.apply(this, [stream]); | |||
|  | 
 | |||
|  |       stream.getTracks().forEach(function (track) { | |||
|  |         var sender = _this3._senders.find(function (s) { | |||
|  |           return s.track === track; | |||
|  |         }); | |||
|  |         if (sender) { | |||
|  |           // remove sender
 | |||
|  |           _this3._senders.splice(_this3._senders.indexOf(sender), 1); | |||
|  |         } | |||
|  |       }); | |||
|  |     }; | |||
|  |   } else if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { | |||
|  |     var origGetSenders = window.RTCPeerConnection.prototype.getSenders; | |||
|  |     window.RTCPeerConnection.prototype.getSenders = function getSenders() { | |||
|  |       var _this4 = this; | |||
|  | 
 | |||
|  |       var senders = origGetSenders.apply(this, []); | |||
|  |       senders.forEach(function (sender) { | |||
|  |         return sender._pc = _this4; | |||
|  |       }); | |||
|  |       return senders; | |||
|  |     }; | |||
|  | 
 | |||
|  |     Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { | |||
|  |       get: function get() { | |||
|  |         if (this._dtmf === undefined) { | |||
|  |           if (this.track.kind === 'audio') { | |||
|  |             this._dtmf = this._pc.createDTMFSender(this.track); | |||
|  |           } else { | |||
|  |             this._dtmf = null; | |||
|  |           } | |||
|  |         } | |||
|  |         return this._dtmf; | |||
|  |       } | |||
|  |     }); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimGetStats(window) { | |||
|  |   if (!window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   var origGetStats = window.RTCPeerConnection.prototype.getStats; | |||
|  |   window.RTCPeerConnection.prototype.getStats = function getStats() { | |||
|  |     var _this5 = this; | |||
|  | 
 | |||
|  |     var _arguments = Array.prototype.slice.call(arguments), | |||
|  |         selector = _arguments[0], | |||
|  |         onSucc = _arguments[1], | |||
|  |         onErr = _arguments[2]; | |||
|  | 
 | |||
|  |     // 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 (arguments.length > 0 && typeof selector === 'function') { | |||
|  |       return origGetStats.apply(this, arguments); | |||
|  |     } | |||
|  | 
 | |||
|  |     // When spec-style getStats is supported, return those when called with
 | |||
|  |     // either no arguments or the selector argument is null.
 | |||
|  |     if (origGetStats.length === 0 && (arguments.length === 0 || typeof selector !== 'function')) { | |||
|  |       return origGetStats.apply(this, []); | |||
|  |     } | |||
|  | 
 | |||
|  |     var fixChromeStats_ = function fixChromeStats_(response) { | |||
|  |       var standardReport = {}; | |||
|  |       var reports = response.result(); | |||
|  |       reports.forEach(function (report) { | |||
|  |         var standardStats = { | |||
|  |           id: report.id, | |||
|  |           timestamp: report.timestamp, | |||
|  |           type: { | |||
|  |             localcandidate: 'local-candidate', | |||
|  |             remotecandidate: 'remote-candidate' | |||
|  |           }[report.type] || report.type | |||
|  |         }; | |||
|  |         report.names().forEach(function (name) { | |||
|  |           standardStats[name] = report.stat(name); | |||
|  |         }); | |||
|  |         standardReport[standardStats.id] = standardStats; | |||
|  |       }); | |||
|  | 
 | |||
|  |       return standardReport; | |||
|  |     }; | |||
|  | 
 | |||
|  |     // shim getStats with maplike support
 | |||
|  |     var makeMapStats = function makeMapStats(stats) { | |||
|  |       return new Map(Object.keys(stats).map(function (key) { | |||
|  |         return [key, stats[key]]; | |||
|  |       })); | |||
|  |     }; | |||
|  | 
 | |||
|  |     if (arguments.length >= 2) { | |||
|  |       var successCallbackWrapper_ = function successCallbackWrapper_(response) { | |||
|  |         onSucc(makeMapStats(fixChromeStats_(response))); | |||
|  |       }; | |||
|  | 
 | |||
|  |       return origGetStats.apply(this, [successCallbackWrapper_, selector]); | |||
|  |     } | |||
|  | 
 | |||
|  |     // promise-support
 | |||
|  |     return new Promise(function (resolve, reject) { | |||
|  |       origGetStats.apply(_this5, [function (response) { | |||
|  |         resolve(makeMapStats(fixChromeStats_(response))); | |||
|  |       }, reject]); | |||
|  |     }).then(onSucc, onErr); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimSenderReceiverGetStats(window) { | |||
|  |   if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   // shim sender stats.
 | |||
|  |   if (!('getStats' in window.RTCRtpSender.prototype)) { | |||
|  |     var origGetSenders = window.RTCPeerConnection.prototype.getSenders; | |||
|  |     if (origGetSenders) { | |||
|  |       window.RTCPeerConnection.prototype.getSenders = function getSenders() { | |||
|  |         var _this6 = this; | |||
|  | 
 | |||
|  |         var senders = origGetSenders.apply(this, []); | |||
|  |         senders.forEach(function (sender) { | |||
|  |           return sender._pc = _this6; | |||
|  |         }); | |||
|  |         return senders; | |||
|  |       }; | |||
|  |     } | |||
|  | 
 | |||
|  |     var origAddTrack = window.RTCPeerConnection.prototype.addTrack; | |||
|  |     if (origAddTrack) { | |||
|  |       window.RTCPeerConnection.prototype.addTrack = function addTrack() { | |||
|  |         var sender = origAddTrack.apply(this, arguments); | |||
|  |         sender._pc = this; | |||
|  |         return sender; | |||
|  |       }; | |||
|  |     } | |||
|  |     window.RTCRtpSender.prototype.getStats = function getStats() { | |||
|  |       var sender = this; | |||
|  |       return this._pc.getStats().then(function (result) { | |||
|  |         return ( | |||
|  |           /* Note: this will include stats of all senders that | |||
|  |            *   send a track with the same id as sender.track as | |||
|  |            *   it is not possible to identify the RTCRtpSender. | |||
|  |            */ | |||
|  |           utils.filterStats(result, sender.track, true) | |||
|  |         ); | |||
|  |       }); | |||
|  |     }; | |||
|  |   } | |||
|  | 
 | |||
|  |   // shim receiver stats.
 | |||
|  |   if (!('getStats' in window.RTCRtpReceiver.prototype)) { | |||
|  |     var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; | |||
|  |     if (origGetReceivers) { | |||
|  |       window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { | |||
|  |         var _this7 = this; | |||
|  | 
 | |||
|  |         var receivers = origGetReceivers.apply(this, []); | |||
|  |         receivers.forEach(function (receiver) { | |||
|  |           return receiver._pc = _this7; | |||
|  |         }); | |||
|  |         return receivers; | |||
|  |       }; | |||
|  |     } | |||
|  |     utils.wrapPeerConnectionEvent(window, 'track', function (e) { | |||
|  |       e.receiver._pc = e.srcElement; | |||
|  |       return e; | |||
|  |     }); | |||
|  |     window.RTCRtpReceiver.prototype.getStats = function getStats() { | |||
|  |       var receiver = this; | |||
|  |       return this._pc.getStats().then(function (result) { | |||
|  |         return utils.filterStats(result, receiver.track, false); | |||
|  |       }); | |||
|  |     }; | |||
|  |   } | |||
|  | 
 | |||
|  |   if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   // shim RTCPeerConnection.getStats(track).
 | |||
|  |   var origGetStats = window.RTCPeerConnection.prototype.getStats; | |||
|  |   window.RTCPeerConnection.prototype.getStats = function getStats() { | |||
|  |     if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { | |||
|  |       var track = arguments[0]; | |||
|  |       var sender = void 0; | |||
|  |       var receiver = void 0; | |||
|  |       var err = void 0; | |||
|  |       this.getSenders().forEach(function (s) { | |||
|  |         if (s.track === track) { | |||
|  |           if (sender) { | |||
|  |             err = true; | |||
|  |           } else { | |||
|  |             sender = s; | |||
|  |           } | |||
|  |         } | |||
|  |       }); | |||
|  |       this.getReceivers().forEach(function (r) { | |||
|  |         if (r.track === track) { | |||
|  |           if (receiver) { | |||
|  |             err = true; | |||
|  |           } else { | |||
|  |             receiver = r; | |||
|  |           } | |||
|  |         } | |||
|  |         return r.track === track; | |||
|  |       }); | |||
|  |       if (err || sender && receiver) { | |||
|  |         return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError')); | |||
|  |       } else if (sender) { | |||
|  |         return sender.getStats(); | |||
|  |       } else if (receiver) { | |||
|  |         return receiver.getStats(); | |||
|  |       } | |||
|  |       return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError')); | |||
|  |     } | |||
|  |     return origGetStats.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimAddTrackRemoveTrackWithNative(window) { | |||
|  |   // shim addTrack/removeTrack with native variants in order to make
 | |||
|  |   // the interactions with legacy getLocalStreams behave as in other browsers.
 | |||
|  |   // Keeps a mapping stream.id => [stream, rtpsenders...]
 | |||
|  |   window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { | |||
|  |     var _this8 = this; | |||
|  | 
 | |||
|  |     this._shimmedLocalStreams = this._shimmedLocalStreams || {}; | |||
|  |     return Object.keys(this._shimmedLocalStreams).map(function (streamId) { | |||
|  |       return _this8._shimmedLocalStreams[streamId][0]; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origAddTrack = window.RTCPeerConnection.prototype.addTrack; | |||
|  |   window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { | |||
|  |     if (!stream) { | |||
|  |       return origAddTrack.apply(this, arguments); | |||
|  |     } | |||
|  |     this._shimmedLocalStreams = this._shimmedLocalStreams || {}; | |||
|  | 
 | |||
|  |     var sender = origAddTrack.apply(this, arguments); | |||
|  |     if (!this._shimmedLocalStreams[stream.id]) { | |||
|  |       this._shimmedLocalStreams[stream.id] = [stream, sender]; | |||
|  |     } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { | |||
|  |       this._shimmedLocalStreams[stream.id].push(sender); | |||
|  |     } | |||
|  |     return sender; | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origAddStream = window.RTCPeerConnection.prototype.addStream; | |||
|  |   window.RTCPeerConnection.prototype.addStream = function addStream(stream) { | |||
|  |     var _this9 = this; | |||
|  | 
 | |||
|  |     this._shimmedLocalStreams = this._shimmedLocalStreams || {}; | |||
|  | 
 | |||
|  |     stream.getTracks().forEach(function (track) { | |||
|  |       var alreadyExists = _this9.getSenders().find(function (s) { | |||
|  |         return s.track === track; | |||
|  |       }); | |||
|  |       if (alreadyExists) { | |||
|  |         throw new DOMException('Track already exists.', 'InvalidAccessError'); | |||
|  |       } | |||
|  |     }); | |||
|  |     var existingSenders = this.getSenders(); | |||
|  |     origAddStream.apply(this, arguments); | |||
|  |     var newSenders = this.getSenders().filter(function (newSender) { | |||
|  |       return existingSenders.indexOf(newSender) === -1; | |||
|  |     }); | |||
|  |     this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; | |||
|  |   window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { | |||
|  |     this._shimmedLocalStreams = this._shimmedLocalStreams || {}; | |||
|  |     delete this._shimmedLocalStreams[stream.id]; | |||
|  |     return origRemoveStream.apply(this, arguments); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; | |||
|  |   window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { | |||
|  |     var _this10 = this; | |||
|  | 
 | |||
|  |     this._shimmedLocalStreams = this._shimmedLocalStreams || {}; | |||
|  |     if (sender) { | |||
|  |       Object.keys(this._shimmedLocalStreams).forEach(function (streamId) { | |||
|  |         var idx = _this10._shimmedLocalStreams[streamId].indexOf(sender); | |||
|  |         if (idx !== -1) { | |||
|  |           _this10._shimmedLocalStreams[streamId].splice(idx, 1); | |||
|  |         } | |||
|  |         if (_this10._shimmedLocalStreams[streamId].length === 1) { | |||
|  |           delete _this10._shimmedLocalStreams[streamId]; | |||
|  |         } | |||
|  |       }); | |||
|  |     } | |||
|  |     return origRemoveTrack.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimAddTrackRemoveTrack(window) { | |||
|  |   if (!window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  |   // shim addTrack and removeTrack.
 | |||
|  |   if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { | |||
|  |     return shimAddTrackRemoveTrackWithNative(window); | |||
|  |   } | |||
|  | 
 | |||
|  |   // also shim pc.getLocalStreams when addTrack is shimmed
 | |||
|  |   // to return the original streams.
 | |||
|  |   var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; | |||
|  |   window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { | |||
|  |     var _this11 = this; | |||
|  | 
 | |||
|  |     var nativeStreams = origGetLocalStreams.apply(this); | |||
|  |     this._reverseStreams = this._reverseStreams || {}; | |||
|  |     return nativeStreams.map(function (stream) { | |||
|  |       return _this11._reverseStreams[stream.id]; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origAddStream = window.RTCPeerConnection.prototype.addStream; | |||
|  |   window.RTCPeerConnection.prototype.addStream = function addStream(stream) { | |||
|  |     var _this12 = this; | |||
|  | 
 | |||
|  |     this._streams = this._streams || {}; | |||
|  |     this._reverseStreams = this._reverseStreams || {}; | |||
|  | 
 | |||
|  |     stream.getTracks().forEach(function (track) { | |||
|  |       var alreadyExists = _this12.getSenders().find(function (s) { | |||
|  |         return s.track === track; | |||
|  |       }); | |||
|  |       if (alreadyExists) { | |||
|  |         throw new DOMException('Track already exists.', 'InvalidAccessError'); | |||
|  |       } | |||
|  |     }); | |||
|  |     // Add identity mapping for consistency with addTrack.
 | |||
|  |     // Unless this is being used with a stream from addTrack.
 | |||
|  |     if (!this._reverseStreams[stream.id]) { | |||
|  |       var newStream = new window.MediaStream(stream.getTracks()); | |||
|  |       this._streams[stream.id] = newStream; | |||
|  |       this._reverseStreams[newStream.id] = stream; | |||
|  |       stream = newStream; | |||
|  |     } | |||
|  |     origAddStream.apply(this, [stream]); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; | |||
|  |   window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { | |||
|  |     this._streams = this._streams || {}; | |||
|  |     this._reverseStreams = this._reverseStreams || {}; | |||
|  | 
 | |||
|  |     origRemoveStream.apply(this, [this._streams[stream.id] || stream]); | |||
|  |     delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id]; | |||
|  |     delete this._streams[stream.id]; | |||
|  |   }; | |||
|  | 
 | |||
|  |   window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { | |||
|  |     var _this13 = this; | |||
|  | 
 | |||
|  |     if (this.signalingState === 'closed') { | |||
|  |       throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); | |||
|  |     } | |||
|  |     var streams = [].slice.call(arguments, 1); | |||
|  |     if (streams.length !== 1 || !streams[0].getTracks().find(function (t) { | |||
|  |       return t === track; | |||
|  |     })) { | |||
|  |       // this is not fully correct but all we can manage without
 | |||
|  |       // [[associated MediaStreams]] internal slot.
 | |||
|  |       throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); | |||
|  |     } | |||
|  | 
 | |||
|  |     var alreadyExists = this.getSenders().find(function (s) { | |||
|  |       return s.track === track; | |||
|  |     }); | |||
|  |     if (alreadyExists) { | |||
|  |       throw new DOMException('Track already exists.', 'InvalidAccessError'); | |||
|  |     } | |||
|  | 
 | |||
|  |     this._streams = this._streams || {}; | |||
|  |     this._reverseStreams = this._reverseStreams || {}; | |||
|  |     var oldStream = this._streams[stream.id]; | |||
|  |     if (oldStream) { | |||
|  |       // this is using odd Chrome behaviour, use with caution:
 | |||
|  |       // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
 | |||
|  |       // Note: we rely on the high-level addTrack/dtmf shim to
 | |||
|  |       // create the sender with a dtmf sender.
 | |||
|  |       oldStream.addTrack(track); | |||
|  | 
 | |||
|  |       // Trigger ONN async.
 | |||
|  |       Promise.resolve().then(function () { | |||
|  |         _this13.dispatchEvent(new Event('negotiationneeded')); | |||
|  |       }); | |||
|  |     } else { | |||
|  |       var newStream = new window.MediaStream([track]); | |||
|  |       this._streams[stream.id] = newStream; | |||
|  |       this._reverseStreams[newStream.id] = stream; | |||
|  |       this.addStream(newStream); | |||
|  |     } | |||
|  |     return this.getSenders().find(function (s) { | |||
|  |       return s.track === track; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // replace the internal stream id with the external one and
 | |||
|  |   // vice versa.
 | |||
|  |   function replaceInternalStreamId(pc, description) { | |||
|  |     var sdp = description.sdp; | |||
|  |     Object.keys(pc._reverseStreams || []).forEach(function (internalId) { | |||
|  |       var externalStream = pc._reverseStreams[internalId]; | |||
|  |       var internalStream = pc._streams[externalStream.id]; | |||
|  |       sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); | |||
|  |     }); | |||
|  |     return new RTCSessionDescription({ | |||
|  |       type: description.type, | |||
|  |       sdp: sdp | |||
|  |     }); | |||
|  |   } | |||
|  |   function replaceExternalStreamId(pc, description) { | |||
|  |     var sdp = description.sdp; | |||
|  |     Object.keys(pc._reverseStreams || []).forEach(function (internalId) { | |||
|  |       var externalStream = pc._reverseStreams[internalId]; | |||
|  |       var internalStream = pc._streams[externalStream.id]; | |||
|  |       sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); | |||
|  |     }); | |||
|  |     return new RTCSessionDescription({ | |||
|  |       type: description.type, | |||
|  |       sdp: sdp | |||
|  |     }); | |||
|  |   } | |||
|  |   ['createOffer', 'createAnswer'].forEach(function (method) { | |||
|  |     var nativeMethod = window.RTCPeerConnection.prototype[method]; | |||
|  |     var methodObj = _defineProperty({}, method, function () { | |||
|  |       var _this14 = this; | |||
|  | 
 | |||
|  |       var args = arguments; | |||
|  |       var isLegacyCall = arguments.length && typeof arguments[0] === 'function'; | |||
|  |       if (isLegacyCall) { | |||
|  |         return nativeMethod.apply(this, [function (description) { | |||
|  |           var desc = replaceInternalStreamId(_this14, description); | |||
|  |           args[0].apply(null, [desc]); | |||
|  |         }, function (err) { | |||
|  |           if (args[1]) { | |||
|  |             args[1].apply(null, err); | |||
|  |           } | |||
|  |         }, arguments[2]]); | |||
|  |       } | |||
|  |       return nativeMethod.apply(this, arguments).then(function (description) { | |||
|  |         return replaceInternalStreamId(_this14, description); | |||
|  |       }); | |||
|  |     }); | |||
|  |     window.RTCPeerConnection.prototype[method] = methodObj[method]; | |||
|  |   }); | |||
|  | 
 | |||
|  |   var origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; | |||
|  |   window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { | |||
|  |     if (!arguments.length || !arguments[0].type) { | |||
|  |       return origSetLocalDescription.apply(this, arguments); | |||
|  |     } | |||
|  |     arguments[0] = replaceExternalStreamId(this, arguments[0]); | |||
|  |     return origSetLocalDescription.apply(this, arguments); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
 | |||
|  | 
 | |||
|  |   var origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription'); | |||
|  |   Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { | |||
|  |     get: function get() { | |||
|  |       var description = origLocalDescription.get.apply(this); | |||
|  |       if (description.type === '') { | |||
|  |         return description; | |||
|  |       } | |||
|  |       return replaceInternalStreamId(this, description); | |||
|  |     } | |||
|  |   }); | |||
|  | 
 | |||
|  |   window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { | |||
|  |     var _this15 = this; | |||
|  | 
 | |||
|  |     if (this.signalingState === 'closed') { | |||
|  |       throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); | |||
|  |     } | |||
|  |     // We can not yet check for sender instanceof RTCRtpSender
 | |||
|  |     // since we shim RTPSender. So we check if sender._pc is set.
 | |||
|  |     if (!sender._pc) { | |||
|  |       throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); | |||
|  |     } | |||
|  |     var isLocal = sender._pc === this; | |||
|  |     if (!isLocal) { | |||
|  |       throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); | |||
|  |     } | |||
|  | 
 | |||
|  |     // Search for the native stream the senders track belongs to.
 | |||
|  |     this._streams = this._streams || {}; | |||
|  |     var stream = void 0; | |||
|  |     Object.keys(this._streams).forEach(function (streamid) { | |||
|  |       var hasTrack = _this15._streams[streamid].getTracks().find(function (track) { | |||
|  |         return sender.track === track; | |||
|  |       }); | |||
|  |       if (hasTrack) { | |||
|  |         stream = _this15._streams[streamid]; | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     if (stream) { | |||
|  |       if (stream.getTracks().length === 1) { | |||
|  |         // if this is the last track of the stream, remove the stream. This
 | |||
|  |         // takes care of any shimmed _senders.
 | |||
|  |         this.removeStream(this._reverseStreams[stream.id]); | |||
|  |       } else { | |||
|  |         // relying on the same odd chrome behaviour as above.
 | |||
|  |         stream.removeTrack(sender.track); | |||
|  |       } | |||
|  |       this.dispatchEvent(new Event('negotiationneeded')); | |||
|  |     } | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimPeerConnection(window) { | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  | 
 | |||
|  |   if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { | |||
|  |     // very basic support for old versions.
 | |||
|  |     window.RTCPeerConnection = window.webkitRTCPeerConnection; | |||
|  |   } | |||
|  |   if (!window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   // shim implicit creation of RTCSessionDescription/RTCIceCandidate
 | |||
|  |   if (browserDetails.version < 53) { | |||
|  |     ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { | |||
|  |       var nativeMethod = window.RTCPeerConnection.prototype[method]; | |||
|  |       var methodObj = _defineProperty({}, method, function () { | |||
|  |         arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); | |||
|  |         return nativeMethod.apply(this, arguments); | |||
|  |       }); | |||
|  |       window.RTCPeerConnection.prototype[method] = methodObj[method]; | |||
|  |     }); | |||
|  |   } | |||
|  | 
 | |||
|  |   // support for addIceCandidate(null or undefined)
 | |||
|  |   var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; | |||
|  |   window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { | |||
|  |     if (!arguments[0]) { | |||
|  |       if (arguments[1]) { | |||
|  |         arguments[1].apply(null); | |||
|  |       } | |||
|  |       return Promise.resolve(); | |||
|  |     } | |||
|  |     // Firefox 68+ emits and processes {candidate: "", ...}, ignore
 | |||
|  |     // in older versions. Native support planned for Chrome M77.
 | |||
|  |     if (browserDetails.version < 78 && arguments[0] && arguments[0].candidate === '') { | |||
|  |       return Promise.resolve(); | |||
|  |     } | |||
|  |     return nativeAddIceCandidate.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function fixNegotiationNeeded(window) { | |||
|  |   utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) { | |||
|  |     var pc = e.target; | |||
|  |     if (pc.signalingState !== 'stable') { | |||
|  |       return; | |||
|  |     } | |||
|  |     return e; | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils.js":15,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2018 The adapter.js 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetDisplayMedia = shimGetDisplayMedia; | |||
|  | function shimGetDisplayMedia(window, getSourceId) { | |||
|  |   if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (!window.navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  |   // getSourceId is a function that returns a promise resolving with
 | |||
|  |   // the sourceId of the screen/window/tab to be shared.
 | |||
|  |   if (typeof getSourceId !== 'function') { | |||
|  |     console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function'); | |||
|  |     return; | |||
|  |   } | |||
|  |   window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { | |||
|  |     return getSourceId(constraints).then(function (sourceId) { | |||
|  |       var widthSpecified = constraints.video && constraints.video.width; | |||
|  |       var heightSpecified = constraints.video && constraints.video.height; | |||
|  |       var frameRateSpecified = constraints.video && constraints.video.frameRate; | |||
|  |       constraints.video = { | |||
|  |         mandatory: { | |||
|  |           chromeMediaSource: 'desktop', | |||
|  |           chromeMediaSourceId: sourceId, | |||
|  |           maxFrameRate: frameRateSpecified || 3 | |||
|  |         } | |||
|  |       }; | |||
|  |       if (widthSpecified) { | |||
|  |         constraints.video.mandatory.maxWidth = widthSpecified; | |||
|  |       } | |||
|  |       if (heightSpecified) { | |||
|  |         constraints.video.mandatory.maxHeight = heightSpecified; | |||
|  |       } | |||
|  |       return window.navigator.mediaDevices.getUserMedia(constraints); | |||
|  |     }); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | },{}],5:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | exports.shimGetUserMedia = shimGetUserMedia; | |||
|  | 
 | |||
|  | var _utils = require('../utils.js'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | var logging = utils.log; | |||
|  | 
 | |||
|  | function shimGetUserMedia(window) { | |||
|  |   var navigator = window && window.navigator; | |||
|  | 
 | |||
|  |   if (!navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  | 
 | |||
|  |   var constraintsToChrome_ = function constraintsToChrome_(c) { | |||
|  |     if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) !== 'object' || c.mandatory || c.optional) { | |||
|  |       return c; | |||
|  |     } | |||
|  |     var cc = {}; | |||
|  |     Object.keys(c).forEach(function (key) { | |||
|  |       if (key === 'require' || key === 'advanced' || key === 'mediaSource') { | |||
|  |         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 oldname_(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; | |||
|  |   }; | |||
|  | 
 | |||
|  |   var shimConstraints_ = function shimConstraints_(constraints, func) { | |||
|  |     if (browserDetails.version >= 61) { | |||
|  |       return func(constraints); | |||
|  |     } | |||
|  |     constraints = JSON.parse(JSON.stringify(constraints)); | |||
|  |     if (constraints && _typeof(constraints.audio) === 'object') { | |||
|  |       var remap = function remap(obj, a, b) { | |||
|  |         if (a in obj && !(b in obj)) { | |||
|  |           obj[b] = obj[a]; | |||
|  |           delete obj[a]; | |||
|  |         } | |||
|  |       }; | |||
|  |       constraints = JSON.parse(JSON.stringify(constraints)); | |||
|  |       remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); | |||
|  |       remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); | |||
|  |       constraints.audio = constraintsToChrome_(constraints.audio); | |||
|  |     } | |||
|  |     if (constraints && _typeof(constraints.video) === 'object') { | |||
|  |       // Shim facingMode for mobile & surface pro.
 | |||
|  |       var face = constraints.video.facingMode; | |||
|  |       face = face && ((typeof face === 'undefined' ? 'undefined' : _typeof(face)) === 'object' ? face : { ideal: face }); | |||
|  |       var getSupportedFacingModeLies = browserDetails.version < 66; | |||
|  | 
 | |||
|  |       if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { | |||
|  |         delete constraints.video.facingMode; | |||
|  |         var matches = void 0; | |||
|  |         if (face.exact === 'environment' || face.ideal === 'environment') { | |||
|  |           matches = ['back', 'rear']; | |||
|  |         } else if (face.exact === 'user' || face.ideal === 'user') { | |||
|  |           matches = ['front']; | |||
|  |         } | |||
|  |         if (matches) { | |||
|  |           // Look for matches in label, or use last cam for back (typical).
 | |||
|  |           return navigator.mediaDevices.enumerateDevices().then(function (devices) { | |||
|  |             devices = devices.filter(function (d) { | |||
|  |               return d.kind === 'videoinput'; | |||
|  |             }); | |||
|  |             var dev = devices.find(function (d) { | |||
|  |               return matches.some(function (match) { | |||
|  |                 return d.label.toLowerCase().includes(match); | |||
|  |               }); | |||
|  |             }); | |||
|  |             if (!dev && devices.length && matches.includes('back')) { | |||
|  |               dev = devices[devices.length - 1]; // more likely the back cam
 | |||
|  |             } | |||
|  |             if (dev) { | |||
|  |               constraints.video.deviceId = face.exact ? { exact: dev.deviceId } : { ideal: dev.deviceId }; | |||
|  |             } | |||
|  |             constraints.video = constraintsToChrome_(constraints.video); | |||
|  |             logging('chrome: ' + JSON.stringify(constraints)); | |||
|  |             return func(constraints); | |||
|  |           }); | |||
|  |         } | |||
|  |       } | |||
|  |       constraints.video = constraintsToChrome_(constraints.video); | |||
|  |     } | |||
|  |     logging('chrome: ' + JSON.stringify(constraints)); | |||
|  |     return func(constraints); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var shimError_ = function shimError_(e) { | |||
|  |     if (browserDetails.version >= 64) { | |||
|  |       return e; | |||
|  |     } | |||
|  |     return { | |||
|  |       name: { | |||
|  |         PermissionDeniedError: 'NotAllowedError', | |||
|  |         PermissionDismissedError: 'NotAllowedError', | |||
|  |         InvalidStateError: 'NotAllowedError', | |||
|  |         DevicesNotFoundError: 'NotFoundError', | |||
|  |         ConstraintNotSatisfiedError: 'OverconstrainedError', | |||
|  |         TrackStartError: 'NotReadableError', | |||
|  |         MediaDeviceFailedDueToShutdown: 'NotAllowedError', | |||
|  |         MediaDeviceKillSwitchOn: 'NotAllowedError', | |||
|  |         TabCaptureError: 'AbortError', | |||
|  |         ScreenCaptureError: 'AbortError', | |||
|  |         DeviceCaptureError: 'AbortError' | |||
|  |       }[e.name] || e.name, | |||
|  |       message: e.message, | |||
|  |       constraint: e.constraint || e.constraintName, | |||
|  |       toString: function toString() { | |||
|  |         return this.name + (this.message && ': ') + this.message; | |||
|  |       } | |||
|  |     }; | |||
|  |   }; | |||
|  | 
 | |||
|  |   var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) { | |||
|  |     shimConstraints_(constraints, function (c) { | |||
|  |       navigator.webkitGetUserMedia(c, onSuccess, function (e) { | |||
|  |         if (onError) { | |||
|  |           onError(shimError_(e)); | |||
|  |         } | |||
|  |       }); | |||
|  |     }); | |||
|  |   }; | |||
|  |   navigator.getUserMedia = getUserMedia_.bind(navigator); | |||
|  | 
 | |||
|  |   // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
 | |||
|  |   // function which returns a Promise, it does not accept spec-style
 | |||
|  |   // constraints.
 | |||
|  |   if (navigator.mediaDevices.getUserMedia) { | |||
|  |     var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); | |||
|  |     navigator.mediaDevices.getUserMedia = function (cs) { | |||
|  |       return shimConstraints_(cs, function (c) { | |||
|  |         return origGetUserMedia(c).then(function (stream) { | |||
|  |           if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { | |||
|  |             stream.getTracks().forEach(function (track) { | |||
|  |               track.stop(); | |||
|  |             }); | |||
|  |             throw new DOMException('', 'NotFoundError'); | |||
|  |           } | |||
|  |           return stream; | |||
|  |         }, function (e) { | |||
|  |           return Promise.reject(shimError_(e)); | |||
|  |         }); | |||
|  |       }); | |||
|  |     }; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils.js":15}],6:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2017 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | exports.shimRTCIceCandidate = shimRTCIceCandidate; | |||
|  | exports.shimMaxMessageSize = shimMaxMessageSize; | |||
|  | exports.shimSendThrowTypeError = shimSendThrowTypeError; | |||
|  | exports.shimConnectionState = shimConnectionState; | |||
|  | exports.removeAllowExtmapMixed = removeAllowExtmapMixed; | |||
|  | 
 | |||
|  | var _sdp = require('sdp'); | |||
|  | 
 | |||
|  | var _sdp2 = _interopRequireDefault(_sdp); | |||
|  | 
 | |||
|  | var _utils = require('./utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |||
|  | 
 | |||
|  | function shimRTCIceCandidate(window) { | |||
|  |   // foundation is arbitrarily chosen as an indicator for full support for
 | |||
|  |   // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
 | |||
|  |   if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   var NativeRTCIceCandidate = window.RTCIceCandidate; | |||
|  |   window.RTCIceCandidate = function RTCIceCandidate(args) { | |||
|  |     // Remove the a= which shouldn't be part of the candidate string.
 | |||
|  |     if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { | |||
|  |       args = JSON.parse(JSON.stringify(args)); | |||
|  |       args.candidate = args.candidate.substr(2); | |||
|  |     } | |||
|  | 
 | |||
|  |     if (args.candidate && args.candidate.length) { | |||
|  |       // Augment the native candidate with the parsed fields.
 | |||
|  |       var nativeCandidate = new NativeRTCIceCandidate(args); | |||
|  |       var parsedCandidate = _sdp2.default.parseCandidate(args.candidate); | |||
|  |       var augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate); | |||
|  | 
 | |||
|  |       // Add a serializer that does not serialize the extra attributes.
 | |||
|  |       augmentedCandidate.toJSON = function toJSON() { | |||
|  |         return { | |||
|  |           candidate: augmentedCandidate.candidate, | |||
|  |           sdpMid: augmentedCandidate.sdpMid, | |||
|  |           sdpMLineIndex: augmentedCandidate.sdpMLineIndex, | |||
|  |           usernameFragment: augmentedCandidate.usernameFragment | |||
|  |         }; | |||
|  |       }; | |||
|  |       return augmentedCandidate; | |||
|  |     } | |||
|  |     return new NativeRTCIceCandidate(args); | |||
|  |   }; | |||
|  |   window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; | |||
|  | 
 | |||
|  |   // Hook up the augmented candidate in onicecandidate and
 | |||
|  |   // addEventListener('icecandidate', ...)
 | |||
|  |   utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) { | |||
|  |     if (e.candidate) { | |||
|  |       Object.defineProperty(e, 'candidate', { | |||
|  |         value: new window.RTCIceCandidate(e.candidate), | |||
|  |         writable: 'false' | |||
|  |       }); | |||
|  |     } | |||
|  |     return e; | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | function shimMaxMessageSize(window) { | |||
|  |   if (!window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  | 
 | |||
|  |   if (!('sctp' in window.RTCPeerConnection.prototype)) { | |||
|  |     Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { | |||
|  |       get: function get() { | |||
|  |         return typeof this._sctp === 'undefined' ? null : this._sctp; | |||
|  |       } | |||
|  |     }); | |||
|  |   } | |||
|  | 
 | |||
|  |   var sctpInDescription = function sctpInDescription(description) { | |||
|  |     if (!description || !description.sdp) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     var sections = _sdp2.default.splitSections(description.sdp); | |||
|  |     sections.shift(); | |||
|  |     return sections.some(function (mediaSection) { | |||
|  |       var mLine = _sdp2.default.parseMLine(mediaSection); | |||
|  |       return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) { | |||
|  |     // TODO: Is there a better solution for detecting Firefox?
 | |||
|  |     var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); | |||
|  |     if (match === null || match.length < 2) { | |||
|  |       return -1; | |||
|  |     } | |||
|  |     var version = parseInt(match[1], 10); | |||
|  |     // Test for NaN (yes, this is ugly)
 | |||
|  |     return version !== version ? -1 : version; | |||
|  |   }; | |||
|  | 
 | |||
|  |   var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) { | |||
|  |     // Every implementation we know can send at least 64 KiB.
 | |||
|  |     // Note: Although Chrome is technically able to send up to 256 KiB, the
 | |||
|  |     //       data does not reach the other peer reliably.
 | |||
|  |     //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
 | |||
|  |     var canSendMaxMessageSize = 65536; | |||
|  |     if (browserDetails.browser === 'firefox') { | |||
|  |       if (browserDetails.version < 57) { | |||
|  |         if (remoteIsFirefox === -1) { | |||
|  |           // FF < 57 will send in 16 KiB chunks using the deprecated PPID
 | |||
|  |           // fragmentation.
 | |||
|  |           canSendMaxMessageSize = 16384; | |||
|  |         } else { | |||
|  |           // However, other FF (and RAWRTC) can reassemble PPID-fragmented
 | |||
|  |           // messages. Thus, supporting ~2 GiB when sending.
 | |||
|  |           canSendMaxMessageSize = 2147483637; | |||
|  |         } | |||
|  |       } else if (browserDetails.version < 60) { | |||
|  |         // Currently, all FF >= 57 will reset the remote maximum message size
 | |||
|  |         // to the default value when a data channel is created at a later
 | |||
|  |         // stage. :(
 | |||
|  |         // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
 | |||
|  |         canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; | |||
|  |       } else { | |||
|  |         // FF >= 60 supports sending ~2 GiB
 | |||
|  |         canSendMaxMessageSize = 2147483637; | |||
|  |       } | |||
|  |     } | |||
|  |     return canSendMaxMessageSize; | |||
|  |   }; | |||
|  | 
 | |||
|  |   var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) { | |||
|  |     // Note: 65536 bytes is the default value from the SDP spec. Also,
 | |||
|  |     //       every implementation we know supports receiving 65536 bytes.
 | |||
|  |     var maxMessageSize = 65536; | |||
|  | 
 | |||
|  |     // FF 57 has a slightly incorrect default remote max message size, so
 | |||
|  |     // we need to adjust it here to avoid a failure when sending.
 | |||
|  |     // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
 | |||
|  |     if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { | |||
|  |       maxMessageSize = 65535; | |||
|  |     } | |||
|  | 
 | |||
|  |     var match = _sdp2.default.matchPrefix(description.sdp, 'a=max-message-size:'); | |||
|  |     if (match.length > 0) { | |||
|  |       maxMessageSize = parseInt(match[0].substr(19), 10); | |||
|  |     } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { | |||
|  |       // If the maximum message size is not present in the remote SDP and
 | |||
|  |       // both local and remote are Firefox, the remote peer can receive
 | |||
|  |       // ~2 GiB.
 | |||
|  |       maxMessageSize = 2147483637; | |||
|  |     } | |||
|  |     return maxMessageSize; | |||
|  |   }; | |||
|  | 
 | |||
|  |   var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; | |||
|  |   window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { | |||
|  |     this._sctp = null; | |||
|  |     // Chrome decided to not expose .sctp in plan-b mode.
 | |||
|  |     // As usual, adapter.js has to do an 'ugly worakaround'
 | |||
|  |     // to cover up the mess.
 | |||
|  |     if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { | |||
|  |       var _getConfiguration = this.getConfiguration(), | |||
|  |           sdpSemantics = _getConfiguration.sdpSemantics; | |||
|  | 
 | |||
|  |       if (sdpSemantics === 'plan-b') { | |||
|  |         Object.defineProperty(this, 'sctp', { | |||
|  |           get: function get() { | |||
|  |             return typeof this._sctp === 'undefined' ? null : this._sctp; | |||
|  |           }, | |||
|  | 
 | |||
|  |           enumerable: true, | |||
|  |           configurable: true | |||
|  |         }); | |||
|  |       } | |||
|  |     } | |||
|  | 
 | |||
|  |     if (sctpInDescription(arguments[0])) { | |||
|  |       // Check if the remote is FF.
 | |||
|  |       var isFirefox = getRemoteFirefoxVersion(arguments[0]); | |||
|  | 
 | |||
|  |       // Get the maximum message size the local peer is capable of sending
 | |||
|  |       var canSendMMS = getCanSendMaxMessageSize(isFirefox); | |||
|  | 
 | |||
|  |       // Get the maximum message size of the remote peer.
 | |||
|  |       var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); | |||
|  | 
 | |||
|  |       // Determine final maximum message size
 | |||
|  |       var maxMessageSize = void 0; | |||
|  |       if (canSendMMS === 0 && remoteMMS === 0) { | |||
|  |         maxMessageSize = Number.POSITIVE_INFINITY; | |||
|  |       } else if (canSendMMS === 0 || remoteMMS === 0) { | |||
|  |         maxMessageSize = Math.max(canSendMMS, remoteMMS); | |||
|  |       } else { | |||
|  |         maxMessageSize = Math.min(canSendMMS, remoteMMS); | |||
|  |       } | |||
|  | 
 | |||
|  |       // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
 | |||
|  |       // attribute.
 | |||
|  |       var sctp = {}; | |||
|  |       Object.defineProperty(sctp, 'maxMessageSize', { | |||
|  |         get: function get() { | |||
|  |           return maxMessageSize; | |||
|  |         } | |||
|  |       }); | |||
|  |       this._sctp = sctp; | |||
|  |     } | |||
|  | 
 | |||
|  |     return origSetRemoteDescription.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimSendThrowTypeError(window) { | |||
|  |   if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { | |||
|  |     return; | |||
|  |   } | |||
|  | 
 | |||
|  |   // Note: Although Firefox >= 57 has a native implementation, the maximum
 | |||
|  |   //       message size can be reset for all data channels at a later stage.
 | |||
|  |   //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
 | |||
|  | 
 | |||
|  |   function wrapDcSend(dc, pc) { | |||
|  |     var origDataChannelSend = dc.send; | |||
|  |     dc.send = function send() { | |||
|  |       var data = arguments[0]; | |||
|  |       var length = data.length || data.size || data.byteLength; | |||
|  |       if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { | |||
|  |         throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); | |||
|  |       } | |||
|  |       return origDataChannelSend.apply(dc, arguments); | |||
|  |     }; | |||
|  |   } | |||
|  |   var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; | |||
|  |   window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() { | |||
|  |     var dataChannel = origCreateDataChannel.apply(this, arguments); | |||
|  |     wrapDcSend(dataChannel, this); | |||
|  |     return dataChannel; | |||
|  |   }; | |||
|  |   utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) { | |||
|  |     wrapDcSend(e.channel, e.target); | |||
|  |     return e; | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | /* shims RTCConnectionState by pretending it is the same as iceConnectionState. | |||
|  |  * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
 | |||
|  |  * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect | |||
|  |  * since DTLS failures would be hidden. See | |||
|  |  * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
 | |||
|  |  * for the Firefox tracking bug. | |||
|  |  */ | |||
|  | function shimConnectionState(window) { | |||
|  |   if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var proto = window.RTCPeerConnection.prototype; | |||
|  |   Object.defineProperty(proto, 'connectionState', { | |||
|  |     get: function get() { | |||
|  |       return { | |||
|  |         completed: 'connected', | |||
|  |         checking: 'connecting' | |||
|  |       }[this.iceConnectionState] || this.iceConnectionState; | |||
|  |     }, | |||
|  | 
 | |||
|  |     enumerable: true, | |||
|  |     configurable: true | |||
|  |   }); | |||
|  |   Object.defineProperty(proto, 'onconnectionstatechange', { | |||
|  |     get: function get() { | |||
|  |       return this._onconnectionstatechange || null; | |||
|  |     }, | |||
|  |     set: function set(cb) { | |||
|  |       if (this._onconnectionstatechange) { | |||
|  |         this.removeEventListener('connectionstatechange', this._onconnectionstatechange); | |||
|  |         delete this._onconnectionstatechange; | |||
|  |       } | |||
|  |       if (cb) { | |||
|  |         this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); | |||
|  |       } | |||
|  |     }, | |||
|  | 
 | |||
|  |     enumerable: true, | |||
|  |     configurable: true | |||
|  |   }); | |||
|  | 
 | |||
|  |   ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) { | |||
|  |     var origMethod = proto[method]; | |||
|  |     proto[method] = function () { | |||
|  |       if (!this._connectionstatechangepoly) { | |||
|  |         this._connectionstatechangepoly = function (e) { | |||
|  |           var pc = e.target; | |||
|  |           if (pc._lastConnectionState !== pc.connectionState) { | |||
|  |             pc._lastConnectionState = pc.connectionState; | |||
|  |             var newEvent = new Event('connectionstatechange', e); | |||
|  |             pc.dispatchEvent(newEvent); | |||
|  |           } | |||
|  |           return e; | |||
|  |         }; | |||
|  |         this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); | |||
|  |       } | |||
|  |       return origMethod.apply(this, arguments); | |||
|  |     }; | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | function removeAllowExtmapMixed(window) { | |||
|  |   /* remove a=extmap-allow-mixed for Chrome < M71 */ | |||
|  |   if (!window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  |   if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; | |||
|  |   window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) { | |||
|  |     if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { | |||
|  |       desc.sdp = desc.sdp.split('\n').filter(function (line) { | |||
|  |         return line.trim() !== 'a=extmap-allow-mixed'; | |||
|  |       }).join('\n'); | |||
|  |     } | |||
|  |     return nativeSRD.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | },{"./utils":15,"sdp":17}],7:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; | |||
|  | 
 | |||
|  | var _getusermedia = require('./getusermedia'); | |||
|  | 
 | |||
|  | Object.defineProperty(exports, 'shimGetUserMedia', { | |||
|  |   enumerable: true, | |||
|  |   get: function get() { | |||
|  |     return _getusermedia.shimGetUserMedia; | |||
|  |   } | |||
|  | }); | |||
|  | 
 | |||
|  | var _getdisplaymedia = require('./getdisplaymedia'); | |||
|  | 
 | |||
|  | Object.defineProperty(exports, 'shimGetDisplayMedia', { | |||
|  |   enumerable: true, | |||
|  |   get: function get() { | |||
|  |     return _getdisplaymedia.shimGetDisplayMedia; | |||
|  |   } | |||
|  | }); | |||
|  | exports.shimPeerConnection = shimPeerConnection; | |||
|  | exports.shimReplaceTrack = shimReplaceTrack; | |||
|  | 
 | |||
|  | var _utils = require('../utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | var _filtericeservers = require('./filtericeservers'); | |||
|  | 
 | |||
|  | var _rtcpeerconnectionShim = require('rtcpeerconnection-shim'); | |||
|  | 
 | |||
|  | var _rtcpeerconnectionShim2 = _interopRequireDefault(_rtcpeerconnectionShim); | |||
|  | 
 | |||
|  | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | function shimPeerConnection(window) { | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  | 
 | |||
|  |   if (window.RTCIceGatherer) { | |||
|  |     if (!window.RTCIceCandidate) { | |||
|  |       window.RTCIceCandidate = function RTCIceCandidate(args) { | |||
|  |         return args; | |||
|  |       }; | |||
|  |     } | |||
|  |     if (!window.RTCSessionDescription) { | |||
|  |       window.RTCSessionDescription = function RTCSessionDescription(args) { | |||
|  |         return args; | |||
|  |       }; | |||
|  |     } | |||
|  |     // this adds an additional event listener to MediaStrackTrack that signals
 | |||
|  |     // when a tracks enabled property was changed. Workaround for a bug in
 | |||
|  |     // addStream, see below. No longer required in 15025+
 | |||
|  |     if (browserDetails.version < 15025) { | |||
|  |       var origMSTEnabled = Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype, 'enabled'); | |||
|  |       Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { | |||
|  |         set: function set(value) { | |||
|  |           origMSTEnabled.set.call(this, value); | |||
|  |           var ev = new Event('enabled'); | |||
|  |           ev.enabled = value; | |||
|  |           this.dispatchEvent(ev); | |||
|  |         } | |||
|  |       }); | |||
|  |     } | |||
|  |   } | |||
|  | 
 | |||
|  |   // ORTC defines the DTMF sender a bit different.
 | |||
|  |   // https://github.com/w3c/ortc/issues/714
 | |||
|  |   if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { | |||
|  |     Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { | |||
|  |       get: function get() { | |||
|  |         if (this._dtmf === undefined) { | |||
|  |           if (this.track.kind === 'audio') { | |||
|  |             this._dtmf = new window.RTCDtmfSender(this); | |||
|  |           } else if (this.track.kind === 'video') { | |||
|  |             this._dtmf = null; | |||
|  |           } | |||
|  |         } | |||
|  |         return this._dtmf; | |||
|  |       } | |||
|  |     }); | |||
|  |   } | |||
|  |   // Edge currently only implements the RTCDtmfSender, not the
 | |||
|  |   // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2*
 | |||
|  |   if (window.RTCDtmfSender && !window.RTCDTMFSender) { | |||
|  |     window.RTCDTMFSender = window.RTCDtmfSender; | |||
|  |   } | |||
|  | 
 | |||
|  |   var RTCPeerConnectionShim = (0, _rtcpeerconnectionShim2.default)(window, browserDetails.version); | |||
|  |   window.RTCPeerConnection = function RTCPeerConnection(config) { | |||
|  |     if (config && config.iceServers) { | |||
|  |       config.iceServers = (0, _filtericeservers.filterIceServers)(config.iceServers, browserDetails.version); | |||
|  |       utils.log('ICE servers after filtering:', config.iceServers); | |||
|  |     } | |||
|  |     return new RTCPeerConnectionShim(config); | |||
|  |   }; | |||
|  |   window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; | |||
|  | } | |||
|  | 
 | |||
|  | function shimReplaceTrack(window) { | |||
|  |   // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
 | |||
|  |   if (window.RTCRtpSender && !('replaceTrack' in window.RTCRtpSender.prototype)) { | |||
|  |     window.RTCRtpSender.prototype.replaceTrack = window.RTCRtpSender.prototype.setTrack; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils":15,"./filtericeservers":8,"./getdisplaymedia":9,"./getusermedia":10,"rtcpeerconnection-shim":16}],8:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2018 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.filterIceServers = filterIceServers; | |||
|  | 
 | |||
|  | var _utils = require('../utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | // Edge does not like
 | |||
|  | // 1) stun: filtered after 14393 unless ?transport=udp is present
 | |||
|  | // 2) turn: that does not have all of turn:host:port?transport=udp
 | |||
|  | // 3) turn: with ipv6 addresses
 | |||
|  | // 4) turn: occurring muliple times
 | |||
|  | function filterIceServers(iceServers, edgeVersion) { | |||
|  |   var hasTurn = false; | |||
|  |   iceServers = JSON.parse(JSON.stringify(iceServers)); | |||
|  |   return iceServers.filter(function (server) { | |||
|  |     if (server && (server.urls || server.url)) { | |||
|  |       var urls = server.urls || server.url; | |||
|  |       if (server.url && !server.urls) { | |||
|  |         utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); | |||
|  |       } | |||
|  |       var isString = typeof urls === 'string'; | |||
|  |       if (isString) { | |||
|  |         urls = [urls]; | |||
|  |       } | |||
|  |       urls = urls.filter(function (url) { | |||
|  |         // filter STUN unconditionally.
 | |||
|  |         if (url.indexOf('stun:') === 0) { | |||
|  |           return false; | |||
|  |         } | |||
|  | 
 | |||
|  |         var validTurn = url.startsWith('turn') && !url.startsWith('turn:[') && url.includes('transport=udp'); | |||
|  |         if (validTurn && !hasTurn) { | |||
|  |           hasTurn = true; | |||
|  |           return true; | |||
|  |         } | |||
|  |         return validTurn && !hasTurn; | |||
|  |       }); | |||
|  | 
 | |||
|  |       delete server.url; | |||
|  |       server.urls = isString ? urls[0] : urls; | |||
|  |       return !!urls.length; | |||
|  |     } | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils":15}],9:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2018 The adapter.js 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetDisplayMedia = shimGetDisplayMedia; | |||
|  | function shimGetDisplayMedia(window) { | |||
|  |   if (!('getDisplayMedia' in window.navigator)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (!window.navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  |   window.navigator.mediaDevices.getDisplayMedia = window.navigator.getDisplayMedia.bind(window.navigator); | |||
|  | } | |||
|  | 
 | |||
|  | },{}],10:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetUserMedia = shimGetUserMedia; | |||
|  | function shimGetUserMedia(window) { | |||
|  |   var navigator = window && window.navigator; | |||
|  | 
 | |||
|  |   var shimError_ = function shimError_(e) { | |||
|  |     return { | |||
|  |       name: { PermissionDeniedError: 'NotAllowedError' }[e.name] || e.name, | |||
|  |       message: e.message, | |||
|  |       constraint: e.constraint, | |||
|  |       toString: function toString() { | |||
|  |         return this.name; | |||
|  |       } | |||
|  |     }; | |||
|  |   }; | |||
|  | 
 | |||
|  |   // getUserMedia error shim.
 | |||
|  |   var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); | |||
|  |   navigator.mediaDevices.getUserMedia = function (c) { | |||
|  |     return origGetUserMedia(c).catch(function (e) { | |||
|  |       return Promise.reject(shimError_(e)); | |||
|  |     }); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | },{}],11:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | var _getusermedia = require('./getusermedia'); | |||
|  | 
 | |||
|  | Object.defineProperty(exports, 'shimGetUserMedia', { | |||
|  |   enumerable: true, | |||
|  |   get: function get() { | |||
|  |     return _getusermedia.shimGetUserMedia; | |||
|  |   } | |||
|  | }); | |||
|  | 
 | |||
|  | var _getdisplaymedia = require('./getdisplaymedia'); | |||
|  | 
 | |||
|  | Object.defineProperty(exports, 'shimGetDisplayMedia', { | |||
|  |   enumerable: true, | |||
|  |   get: function get() { | |||
|  |     return _getdisplaymedia.shimGetDisplayMedia; | |||
|  |   } | |||
|  | }); | |||
|  | exports.shimOnTrack = shimOnTrack; | |||
|  | exports.shimPeerConnection = shimPeerConnection; | |||
|  | exports.shimSenderGetStats = shimSenderGetStats; | |||
|  | exports.shimReceiverGetStats = shimReceiverGetStats; | |||
|  | exports.shimRemoveStream = shimRemoveStream; | |||
|  | exports.shimRTCDataChannel = shimRTCDataChannel; | |||
|  | exports.shimAddTransceiver = shimAddTransceiver; | |||
|  | exports.shimCreateOffer = shimCreateOffer; | |||
|  | exports.shimCreateAnswer = shimCreateAnswer; | |||
|  | 
 | |||
|  | var _utils = require('../utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | |||
|  | 
 | |||
|  | function shimOnTrack(window) { | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { | |||
|  |     Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { | |||
|  |       get: function get() { | |||
|  |         return { receiver: this.receiver }; | |||
|  |       } | |||
|  |     }); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimPeerConnection(window) { | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  | 
 | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { | |||
|  |     return; // probably media.peerconnection.enabled=false in about:config
 | |||
|  |   } | |||
|  |   if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { | |||
|  |     // very basic support for old versions.
 | |||
|  |     window.RTCPeerConnection = window.mozRTCPeerConnection; | |||
|  |   } | |||
|  | 
 | |||
|  |   if (browserDetails.version < 53) { | |||
|  |     // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
 | |||
|  |     ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { | |||
|  |       var nativeMethod = window.RTCPeerConnection.prototype[method]; | |||
|  |       var methodObj = _defineProperty({}, method, function () { | |||
|  |         arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); | |||
|  |         return nativeMethod.apply(this, arguments); | |||
|  |       }); | |||
|  |       window.RTCPeerConnection.prototype[method] = methodObj[method]; | |||
|  |     }); | |||
|  |   } | |||
|  | 
 | |||
|  |   // support for addIceCandidate(null or undefined)
 | |||
|  |   // as well as ignoring {sdpMid, candidate: ""}
 | |||
|  |   if (browserDetails.version < 68) { | |||
|  |     var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; | |||
|  |     window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { | |||
|  |       if (!arguments[0]) { | |||
|  |         if (arguments[1]) { | |||
|  |           arguments[1].apply(null); | |||
|  |         } | |||
|  |         return Promise.resolve(); | |||
|  |       } | |||
|  |       // Firefox 68+ emits and processes {candidate: "", ...}, ignore
 | |||
|  |       // in older versions.
 | |||
|  |       if (arguments[0] && arguments[0].candidate === '') { | |||
|  |         return Promise.resolve(); | |||
|  |       } | |||
|  |       return nativeAddIceCandidate.apply(this, arguments); | |||
|  |     }; | |||
|  |   } | |||
|  | 
 | |||
|  |   var modernStatsTypes = { | |||
|  |     inboundrtp: 'inbound-rtp', | |||
|  |     outboundrtp: 'outbound-rtp', | |||
|  |     candidatepair: 'candidate-pair', | |||
|  |     localcandidate: 'local-candidate', | |||
|  |     remotecandidate: 'remote-candidate' | |||
|  |   }; | |||
|  | 
 | |||
|  |   var nativeGetStats = window.RTCPeerConnection.prototype.getStats; | |||
|  |   window.RTCPeerConnection.prototype.getStats = function getStats() { | |||
|  |     var _arguments = Array.prototype.slice.call(arguments), | |||
|  |         selector = _arguments[0], | |||
|  |         onSucc = _arguments[1], | |||
|  |         onErr = _arguments[2]; | |||
|  | 
 | |||
|  |     return nativeGetStats.apply(this, [selector || null]).then(function (stats) { | |||
|  |       if (browserDetails.version < 53 && !onSucc) { | |||
|  |         // Shim only promise getStats with spec-hyphens in type names
 | |||
|  |         // Leave callback version alone; misc old uses of forEach before Map
 | |||
|  |         try { | |||
|  |           stats.forEach(function (stat) { | |||
|  |             stat.type = modernStatsTypes[stat.type] || stat.type; | |||
|  |           }); | |||
|  |         } catch (e) { | |||
|  |           if (e.name !== 'TypeError') { | |||
|  |             throw e; | |||
|  |           } | |||
|  |           // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
 | |||
|  |           stats.forEach(function (stat, i) { | |||
|  |             stats.set(i, Object.assign({}, stat, { | |||
|  |               type: modernStatsTypes[stat.type] || stat.type | |||
|  |             })); | |||
|  |           }); | |||
|  |         } | |||
|  |       } | |||
|  |       return stats; | |||
|  |     }).then(onSucc, onErr); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimSenderGetStats(window) { | |||
|  |   if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var origGetSenders = window.RTCPeerConnection.prototype.getSenders; | |||
|  |   if (origGetSenders) { | |||
|  |     window.RTCPeerConnection.prototype.getSenders = function getSenders() { | |||
|  |       var _this = this; | |||
|  | 
 | |||
|  |       var senders = origGetSenders.apply(this, []); | |||
|  |       senders.forEach(function (sender) { | |||
|  |         return sender._pc = _this; | |||
|  |       }); | |||
|  |       return senders; | |||
|  |     }; | |||
|  |   } | |||
|  | 
 | |||
|  |   var origAddTrack = window.RTCPeerConnection.prototype.addTrack; | |||
|  |   if (origAddTrack) { | |||
|  |     window.RTCPeerConnection.prototype.addTrack = function addTrack() { | |||
|  |       var sender = origAddTrack.apply(this, arguments); | |||
|  |       sender._pc = this; | |||
|  |       return sender; | |||
|  |     }; | |||
|  |   } | |||
|  |   window.RTCRtpSender.prototype.getStats = function getStats() { | |||
|  |     return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimReceiverGetStats(window) { | |||
|  |   if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; | |||
|  |   if (origGetReceivers) { | |||
|  |     window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { | |||
|  |       var _this2 = this; | |||
|  | 
 | |||
|  |       var receivers = origGetReceivers.apply(this, []); | |||
|  |       receivers.forEach(function (receiver) { | |||
|  |         return receiver._pc = _this2; | |||
|  |       }); | |||
|  |       return receivers; | |||
|  |     }; | |||
|  |   } | |||
|  |   utils.wrapPeerConnectionEvent(window, 'track', function (e) { | |||
|  |     e.receiver._pc = e.srcElement; | |||
|  |     return e; | |||
|  |   }); | |||
|  |   window.RTCRtpReceiver.prototype.getStats = function getStats() { | |||
|  |     return this._pc.getStats(this.track); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimRemoveStream(window) { | |||
|  |   if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { | |||
|  |     return; | |||
|  |   } | |||
|  |   window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { | |||
|  |     var _this3 = this; | |||
|  | 
 | |||
|  |     utils.deprecated('removeStream', 'removeTrack'); | |||
|  |     this.getSenders().forEach(function (sender) { | |||
|  |       if (sender.track && stream.getTracks().includes(sender.track)) { | |||
|  |         _this3.removeTrack(sender); | |||
|  |       } | |||
|  |     }); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimRTCDataChannel(window) { | |||
|  |   // rename DataChannel to RTCDataChannel (native fix in FF60):
 | |||
|  |   // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
 | |||
|  |   if (window.DataChannel && !window.RTCDataChannel) { | |||
|  |     window.RTCDataChannel = window.DataChannel; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimAddTransceiver(window) { | |||
|  |   // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
 | |||
|  |   // Firefox ignores the init sendEncodings options passed to addTransceiver
 | |||
|  |   // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
 | |||
|  |   if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; | |||
|  |   if (origAddTransceiver) { | |||
|  |     window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() { | |||
|  |       this.setParametersPromises = []; | |||
|  |       var initParameters = arguments[1]; | |||
|  |       var shouldPerformCheck = initParameters && 'sendEncodings' in initParameters; | |||
|  |       if (shouldPerformCheck) { | |||
|  |         // If sendEncodings params are provided, validate grammar
 | |||
|  |         initParameters.sendEncodings.forEach(function (encodingParam) { | |||
|  |           if ('rid' in encodingParam) { | |||
|  |             var ridRegex = /^[a-z0-9]{0,16}$/i; | |||
|  |             if (!ridRegex.test(encodingParam.rid)) { | |||
|  |               throw new TypeError('Invalid RID value provided.'); | |||
|  |             } | |||
|  |           } | |||
|  |           if ('scaleResolutionDownBy' in encodingParam) { | |||
|  |             if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { | |||
|  |               throw new RangeError('scale_resolution_down_by must be >= 1.0'); | |||
|  |             } | |||
|  |           } | |||
|  |           if ('maxFramerate' in encodingParam) { | |||
|  |             if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { | |||
|  |               throw new RangeError('max_framerate must be >= 0.0'); | |||
|  |             } | |||
|  |           } | |||
|  |         }); | |||
|  |       } | |||
|  |       var transceiver = origAddTransceiver.apply(this, arguments); | |||
|  |       if (shouldPerformCheck) { | |||
|  |         // Check if the init options were applied. If not we do this in an
 | |||
|  |         // asynchronous way and save the promise reference in a global object.
 | |||
|  |         // This is an ugly hack, but at the same time is way more robust than
 | |||
|  |         // checking the sender parameters before and after the createOffer
 | |||
|  |         // Also note that after the createoffer we are not 100% sure that
 | |||
|  |         // the params were asynchronously applied so we might miss the
 | |||
|  |         // opportunity to recreate offer.
 | |||
|  |         var sender = transceiver.sender; | |||
|  | 
 | |||
|  |         var params = sender.getParameters(); | |||
|  |         if (!('encodings' in params)) { | |||
|  |           params.encodings = initParameters.sendEncodings; | |||
|  |           this.setParametersPromises.push(sender.setParameters(params).catch(function () {})); | |||
|  |         } | |||
|  |       } | |||
|  |       return transceiver; | |||
|  |     }; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimCreateOffer(window) { | |||
|  |   // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
 | |||
|  |   // Firefox ignores the init sendEncodings options passed to addTransceiver
 | |||
|  |   // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
 | |||
|  |   if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; | |||
|  |   window.RTCPeerConnection.prototype.createOffer = function createOffer() { | |||
|  |     var _this4 = this, | |||
|  |         _arguments2 = arguments; | |||
|  | 
 | |||
|  |     if (this.setParametersPromises && this.setParametersPromises.length) { | |||
|  |       return Promise.all(this.setParametersPromises).then(function () { | |||
|  |         return origCreateOffer.apply(_this4, _arguments2); | |||
|  |       }).finally(function () { | |||
|  |         _this4.setParametersPromises = []; | |||
|  |       }); | |||
|  |     } | |||
|  |     return origCreateOffer.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | function shimCreateAnswer(window) { | |||
|  |   // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
 | |||
|  |   // Firefox ignores the init sendEncodings options passed to addTransceiver
 | |||
|  |   // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
 | |||
|  |   if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; | |||
|  |   window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { | |||
|  |     var _this5 = this, | |||
|  |         _arguments3 = arguments; | |||
|  | 
 | |||
|  |     if (this.setParametersPromises && this.setParametersPromises.length) { | |||
|  |       return Promise.all(this.setParametersPromises).then(function () { | |||
|  |         return origCreateAnswer.apply(_this5, _arguments3); | |||
|  |       }).finally(function () { | |||
|  |         _this5.setParametersPromises = []; | |||
|  |       }); | |||
|  |     } | |||
|  |     return origCreateAnswer.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils":15,"./getdisplaymedia":12,"./getusermedia":13}],12:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2018 The adapter.js 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | exports.shimGetDisplayMedia = shimGetDisplayMedia; | |||
|  | function shimGetDisplayMedia(window, preferredMediaSource) { | |||
|  |   if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (!window.navigator.mediaDevices) { | |||
|  |     return; | |||
|  |   } | |||
|  |   window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { | |||
|  |     if (!(constraints && constraints.video)) { | |||
|  |       var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); | |||
|  |       err.name = 'NotFoundError'; | |||
|  |       // from https://heycam.github.io/webidl/#idl-DOMException-error-names
 | |||
|  |       err.code = 8; | |||
|  |       return Promise.reject(err); | |||
|  |     } | |||
|  |     if (constraints.video === true) { | |||
|  |       constraints.video = { mediaSource: preferredMediaSource }; | |||
|  |     } else { | |||
|  |       constraints.video.mediaSource = preferredMediaSource; | |||
|  |     } | |||
|  |     return window.navigator.mediaDevices.getUserMedia(constraints); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | },{}],13:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | exports.shimGetUserMedia = shimGetUserMedia; | |||
|  | 
 | |||
|  | var _utils = require('../utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | function shimGetUserMedia(window) { | |||
|  |   var browserDetails = utils.detectBrowser(window); | |||
|  |   var navigator = window && window.navigator; | |||
|  |   var MediaStreamTrack = window && window.MediaStreamTrack; | |||
|  | 
 | |||
|  |   navigator.getUserMedia = function (constraints, onSuccess, onError) { | |||
|  |     // Replace Firefox 44+'s deprecation warning with unprefixed version.
 | |||
|  |     utils.deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); | |||
|  |     navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); | |||
|  |   }; | |||
|  | 
 | |||
|  |   if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { | |||
|  |     var remap = function remap(obj, a, b) { | |||
|  |       if (a in obj && !(b in obj)) { | |||
|  |         obj[b] = obj[a]; | |||
|  |         delete obj[a]; | |||
|  |       } | |||
|  |     }; | |||
|  | 
 | |||
|  |     var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); | |||
|  |     navigator.mediaDevices.getUserMedia = function (c) { | |||
|  |       if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object' && _typeof(c.audio) === 'object') { | |||
|  |         c = JSON.parse(JSON.stringify(c)); | |||
|  |         remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); | |||
|  |         remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); | |||
|  |       } | |||
|  |       return nativeGetUserMedia(c); | |||
|  |     }; | |||
|  | 
 | |||
|  |     if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { | |||
|  |       var nativeGetSettings = MediaStreamTrack.prototype.getSettings; | |||
|  |       MediaStreamTrack.prototype.getSettings = function () { | |||
|  |         var obj = nativeGetSettings.apply(this, arguments); | |||
|  |         remap(obj, 'mozAutoGainControl', 'autoGainControl'); | |||
|  |         remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); | |||
|  |         return obj; | |||
|  |       }; | |||
|  |     } | |||
|  | 
 | |||
|  |     if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { | |||
|  |       var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; | |||
|  |       MediaStreamTrack.prototype.applyConstraints = function (c) { | |||
|  |         if (this.kind === 'audio' && (typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object') { | |||
|  |           c = JSON.parse(JSON.stringify(c)); | |||
|  |           remap(c, 'autoGainControl', 'mozAutoGainControl'); | |||
|  |           remap(c, 'noiseSuppression', 'mozNoiseSuppression'); | |||
|  |         } | |||
|  |         return nativeApplyConstraints.apply(this, [c]); | |||
|  |       }; | |||
|  |     } | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils":15}],14:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | exports.shimLocalStreamsAPI = shimLocalStreamsAPI; | |||
|  | exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; | |||
|  | exports.shimCallbacksAPI = shimCallbacksAPI; | |||
|  | exports.shimGetUserMedia = shimGetUserMedia; | |||
|  | exports.shimConstraints = shimConstraints; | |||
|  | exports.shimRTCIceServerUrls = shimRTCIceServerUrls; | |||
|  | exports.shimTrackEventTransceiver = shimTrackEventTransceiver; | |||
|  | exports.shimCreateOfferLegacy = shimCreateOfferLegacy; | |||
|  | 
 | |||
|  | var _utils = require('../utils'); | |||
|  | 
 | |||
|  | var utils = _interopRequireWildcard(_utils); | |||
|  | 
 | |||
|  | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | |||
|  | 
 | |||
|  | function shimLocalStreamsAPI(window) { | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { | |||
|  |     window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { | |||
|  |       if (!this._localStreams) { | |||
|  |         this._localStreams = []; | |||
|  |       } | |||
|  |       return this._localStreams; | |||
|  |     }; | |||
|  |   } | |||
|  |   if (!('addStream' in window.RTCPeerConnection.prototype)) { | |||
|  |     var _addTrack = window.RTCPeerConnection.prototype.addTrack; | |||
|  |     window.RTCPeerConnection.prototype.addStream = function addStream(stream) { | |||
|  |       var _this = this; | |||
|  | 
 | |||
|  |       if (!this._localStreams) { | |||
|  |         this._localStreams = []; | |||
|  |       } | |||
|  |       if (!this._localStreams.includes(stream)) { | |||
|  |         this._localStreams.push(stream); | |||
|  |       } | |||
|  |       // Try to emulate Chrome's behaviour of adding in audio-video order.
 | |||
|  |       // Safari orders by track id.
 | |||
|  |       stream.getAudioTracks().forEach(function (track) { | |||
|  |         return _addTrack.call(_this, track, stream); | |||
|  |       }); | |||
|  |       stream.getVideoTracks().forEach(function (track) { | |||
|  |         return _addTrack.call(_this, track, stream); | |||
|  |       }); | |||
|  |     }; | |||
|  | 
 | |||
|  |     window.RTCPeerConnection.prototype.addTrack = function addTrack(track) { | |||
|  |       var stream = arguments[1]; | |||
|  |       if (stream) { | |||
|  |         if (!this._localStreams) { | |||
|  |           this._localStreams = [stream]; | |||
|  |         } else if (!this._localStreams.includes(stream)) { | |||
|  |           this._localStreams.push(stream); | |||
|  |         } | |||
|  |       } | |||
|  |       return _addTrack.apply(this, arguments); | |||
|  |     }; | |||
|  |   } | |||
|  |   if (!('removeStream' in window.RTCPeerConnection.prototype)) { | |||
|  |     window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { | |||
|  |       var _this2 = this; | |||
|  | 
 | |||
|  |       if (!this._localStreams) { | |||
|  |         this._localStreams = []; | |||
|  |       } | |||
|  |       var index = this._localStreams.indexOf(stream); | |||
|  |       if (index === -1) { | |||
|  |         return; | |||
|  |       } | |||
|  |       this._localStreams.splice(index, 1); | |||
|  |       var tracks = stream.getTracks(); | |||
|  |       this.getSenders().forEach(function (sender) { | |||
|  |         if (tracks.includes(sender.track)) { | |||
|  |           _this2.removeTrack(sender); | |||
|  |         } | |||
|  |       }); | |||
|  |     }; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimRemoteStreamsAPI(window) { | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { | |||
|  |     window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() { | |||
|  |       return this._remoteStreams ? this._remoteStreams : []; | |||
|  |     }; | |||
|  |   } | |||
|  |   if (!('onaddstream' in window.RTCPeerConnection.prototype)) { | |||
|  |     Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { | |||
|  |       get: function get() { | |||
|  |         return this._onaddstream; | |||
|  |       }, | |||
|  |       set: function set(f) { | |||
|  |         var _this3 = this; | |||
|  | 
 | |||
|  |         if (this._onaddstream) { | |||
|  |           this.removeEventListener('addstream', this._onaddstream); | |||
|  |           this.removeEventListener('track', this._onaddstreampoly); | |||
|  |         } | |||
|  |         this.addEventListener('addstream', this._onaddstream = f); | |||
|  |         this.addEventListener('track', this._onaddstreampoly = function (e) { | |||
|  |           e.streams.forEach(function (stream) { | |||
|  |             if (!_this3._remoteStreams) { | |||
|  |               _this3._remoteStreams = []; | |||
|  |             } | |||
|  |             if (_this3._remoteStreams.includes(stream)) { | |||
|  |               return; | |||
|  |             } | |||
|  |             _this3._remoteStreams.push(stream); | |||
|  |             var event = new Event('addstream'); | |||
|  |             event.stream = stream; | |||
|  |             _this3.dispatchEvent(event); | |||
|  |           }); | |||
|  |         }); | |||
|  |       } | |||
|  |     }); | |||
|  |     var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; | |||
|  |     window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { | |||
|  |       var pc = this; | |||
|  |       if (!this._onaddstreampoly) { | |||
|  |         this.addEventListener('track', this._onaddstreampoly = function (e) { | |||
|  |           e.streams.forEach(function (stream) { | |||
|  |             if (!pc._remoteStreams) { | |||
|  |               pc._remoteStreams = []; | |||
|  |             } | |||
|  |             if (pc._remoteStreams.indexOf(stream) >= 0) { | |||
|  |               return; | |||
|  |             } | |||
|  |             pc._remoteStreams.push(stream); | |||
|  |             var event = new Event('addstream'); | |||
|  |             event.stream = stream; | |||
|  |             pc.dispatchEvent(event); | |||
|  |           }); | |||
|  |         }); | |||
|  |       } | |||
|  |       return origSetRemoteDescription.apply(pc, arguments); | |||
|  |     }; | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimCallbacksAPI(window) { | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var prototype = window.RTCPeerConnection.prototype; | |||
|  |   var origCreateOffer = prototype.createOffer; | |||
|  |   var origCreateAnswer = prototype.createAnswer; | |||
|  |   var setLocalDescription = prototype.setLocalDescription; | |||
|  |   var setRemoteDescription = prototype.setRemoteDescription; | |||
|  |   var addIceCandidate = prototype.addIceCandidate; | |||
|  | 
 | |||
|  |   prototype.createOffer = function createOffer(successCallback, failureCallback) { | |||
|  |     var options = arguments.length >= 2 ? arguments[2] : arguments[0]; | |||
|  |     var promise = origCreateOffer.apply(this, [options]); | |||
|  |     if (!failureCallback) { | |||
|  |       return promise; | |||
|  |     } | |||
|  |     promise.then(successCallback, failureCallback); | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  | 
 | |||
|  |   prototype.createAnswer = function createAnswer(successCallback, failureCallback) { | |||
|  |     var options = arguments.length >= 2 ? arguments[2] : arguments[0]; | |||
|  |     var promise = origCreateAnswer.apply(this, [options]); | |||
|  |     if (!failureCallback) { | |||
|  |       return promise; | |||
|  |     } | |||
|  |     promise.then(successCallback, failureCallback); | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var withCallback = function withCallback(description, successCallback, failureCallback) { | |||
|  |     var promise = setLocalDescription.apply(this, [description]); | |||
|  |     if (!failureCallback) { | |||
|  |       return promise; | |||
|  |     } | |||
|  |     promise.then(successCallback, failureCallback); | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  |   prototype.setLocalDescription = withCallback; | |||
|  | 
 | |||
|  |   withCallback = function withCallback(description, successCallback, failureCallback) { | |||
|  |     var promise = setRemoteDescription.apply(this, [description]); | |||
|  |     if (!failureCallback) { | |||
|  |       return promise; | |||
|  |     } | |||
|  |     promise.then(successCallback, failureCallback); | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  |   prototype.setRemoteDescription = withCallback; | |||
|  | 
 | |||
|  |   withCallback = function withCallback(candidate, successCallback, failureCallback) { | |||
|  |     var promise = addIceCandidate.apply(this, [candidate]); | |||
|  |     if (!failureCallback) { | |||
|  |       return promise; | |||
|  |     } | |||
|  |     promise.then(successCallback, failureCallback); | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  |   prototype.addIceCandidate = withCallback; | |||
|  | } | |||
|  | 
 | |||
|  | function shimGetUserMedia(window) { | |||
|  |   var navigator = window && window.navigator; | |||
|  | 
 | |||
|  |   if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |||
|  |     // shim not needed in Safari 12.1
 | |||
|  |     var mediaDevices = navigator.mediaDevices; | |||
|  |     var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); | |||
|  |     navigator.mediaDevices.getUserMedia = function (constraints) { | |||
|  |       return _getUserMedia(shimConstraints(constraints)); | |||
|  |     }; | |||
|  |   } | |||
|  | 
 | |||
|  |   if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |||
|  |     navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { | |||
|  |       navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb); | |||
|  |     }.bind(navigator); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimConstraints(constraints) { | |||
|  |   if (constraints && constraints.video !== undefined) { | |||
|  |     return Object.assign({}, constraints, { video: utils.compactObject(constraints.video) }); | |||
|  |   } | |||
|  | 
 | |||
|  |   return constraints; | |||
|  | } | |||
|  | 
 | |||
|  | function shimRTCIceServerUrls(window) { | |||
|  |   // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
 | |||
|  |   var OrigPeerConnection = window.RTCPeerConnection; | |||
|  |   window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) { | |||
|  |     if (pcConfig && pcConfig.iceServers) { | |||
|  |       var newIceServers = []; | |||
|  |       for (var i = 0; i < pcConfig.iceServers.length; i++) { | |||
|  |         var server = pcConfig.iceServers[i]; | |||
|  |         if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) { | |||
|  |           utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); | |||
|  |           server = JSON.parse(JSON.stringify(server)); | |||
|  |           server.urls = server.url; | |||
|  |           delete server.url; | |||
|  |           newIceServers.push(server); | |||
|  |         } else { | |||
|  |           newIceServers.push(pcConfig.iceServers[i]); | |||
|  |         } | |||
|  |       } | |||
|  |       pcConfig.iceServers = newIceServers; | |||
|  |     } | |||
|  |     return new OrigPeerConnection(pcConfig, pcConstraints); | |||
|  |   }; | |||
|  |   window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; | |||
|  |   // wrap static methods. Currently just generateCertificate.
 | |||
|  |   if ('generateCertificate' in window.RTCPeerConnection) { | |||
|  |     Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { | |||
|  |       get: function get() { | |||
|  |         return OrigPeerConnection.generateCertificate; | |||
|  |       } | |||
|  |     }); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimTrackEventTransceiver(window) { | |||
|  |   // Add event.transceiver member over deprecated event.receiver
 | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { | |||
|  |     Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { | |||
|  |       get: function get() { | |||
|  |         return { receiver: this.receiver }; | |||
|  |       } | |||
|  |     }); | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | function shimCreateOfferLegacy(window) { | |||
|  |   var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; | |||
|  |   window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { | |||
|  |     if (offerOptions) { | |||
|  |       if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { | |||
|  |         // support bit values
 | |||
|  |         offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; | |||
|  |       } | |||
|  |       var audioTransceiver = this.getTransceivers().find(function (transceiver) { | |||
|  |         return transceiver.receiver.track.kind === 'audio'; | |||
|  |       }); | |||
|  |       if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { | |||
|  |         if (audioTransceiver.direction === 'sendrecv') { | |||
|  |           if (audioTransceiver.setDirection) { | |||
|  |             audioTransceiver.setDirection('sendonly'); | |||
|  |           } else { | |||
|  |             audioTransceiver.direction = 'sendonly'; | |||
|  |           } | |||
|  |         } else if (audioTransceiver.direction === 'recvonly') { | |||
|  |           if (audioTransceiver.setDirection) { | |||
|  |             audioTransceiver.setDirection('inactive'); | |||
|  |           } else { | |||
|  |             audioTransceiver.direction = 'inactive'; | |||
|  |           } | |||
|  |         } | |||
|  |       } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { | |||
|  |         this.addTransceiver('audio'); | |||
|  |       } | |||
|  | 
 | |||
|  |       if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { | |||
|  |         // support bit values
 | |||
|  |         offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; | |||
|  |       } | |||
|  |       var videoTransceiver = this.getTransceivers().find(function (transceiver) { | |||
|  |         return transceiver.receiver.track.kind === 'video'; | |||
|  |       }); | |||
|  |       if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { | |||
|  |         if (videoTransceiver.direction === 'sendrecv') { | |||
|  |           if (videoTransceiver.setDirection) { | |||
|  |             videoTransceiver.setDirection('sendonly'); | |||
|  |           } else { | |||
|  |             videoTransceiver.direction = 'sendonly'; | |||
|  |           } | |||
|  |         } else if (videoTransceiver.direction === 'recvonly') { | |||
|  |           if (videoTransceiver.setDirection) { | |||
|  |             videoTransceiver.setDirection('inactive'); | |||
|  |           } else { | |||
|  |             videoTransceiver.direction = 'inactive'; | |||
|  |           } | |||
|  |         } | |||
|  |       } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { | |||
|  |         this.addTransceiver('video'); | |||
|  |       } | |||
|  |     } | |||
|  |     return origCreateOffer.apply(this, arguments); | |||
|  |   }; | |||
|  | } | |||
|  | 
 | |||
|  | },{"../utils":15}],15:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2016 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. | |||
|  |  */ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | Object.defineProperty(exports, "__esModule", { | |||
|  |   value: true | |||
|  | }); | |||
|  | 
 | |||
|  | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |||
|  | 
 | |||
|  | exports.extractVersion = extractVersion; | |||
|  | exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; | |||
|  | exports.disableLog = disableLog; | |||
|  | exports.disableWarnings = disableWarnings; | |||
|  | exports.log = log; | |||
|  | exports.deprecated = deprecated; | |||
|  | exports.detectBrowser = detectBrowser; | |||
|  | exports.compactObject = compactObject; | |||
|  | exports.walkStats = walkStats; | |||
|  | exports.filterStats = filterStats; | |||
|  | 
 | |||
|  | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | |||
|  | 
 | |||
|  | var logDisabled_ = true; | |||
|  | var deprecationWarnings_ = true; | |||
|  | 
 | |||
|  | /** | |||
|  |  * Extract browser version out of the provided user agent string. | |||
|  |  * | |||
|  |  * @param {!string} uastring userAgent string. | |||
|  |  * @param {!string} expr Regular expression used as match criteria. | |||
|  |  * @param {!number} pos position in the version string to be returned. | |||
|  |  * @return {!number} browser version. | |||
|  |  */ | |||
|  | function extractVersion(uastring, expr, pos) { | |||
|  |   var match = uastring.match(expr); | |||
|  |   return match && match.length >= pos && parseInt(match[pos], 10); | |||
|  | } | |||
|  | 
 | |||
|  | // Wraps the peerconnection event eventNameToWrap in a function
 | |||
|  | // which returns the modified event object (or false to prevent
 | |||
|  | // the event).
 | |||
|  | function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { | |||
|  |   if (!window.RTCPeerConnection) { | |||
|  |     return; | |||
|  |   } | |||
|  |   var proto = window.RTCPeerConnection.prototype; | |||
|  |   var nativeAddEventListener = proto.addEventListener; | |||
|  |   proto.addEventListener = function (nativeEventName, cb) { | |||
|  |     if (nativeEventName !== eventNameToWrap) { | |||
|  |       return nativeAddEventListener.apply(this, arguments); | |||
|  |     } | |||
|  |     var wrappedCallback = function wrappedCallback(e) { | |||
|  |       var modifiedEvent = wrapper(e); | |||
|  |       if (modifiedEvent) { | |||
|  |         cb(modifiedEvent); | |||
|  |       } | |||
|  |     }; | |||
|  |     this._eventMap = this._eventMap || {}; | |||
|  |     this._eventMap[cb] = wrappedCallback; | |||
|  |     return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); | |||
|  |   }; | |||
|  | 
 | |||
|  |   var nativeRemoveEventListener = proto.removeEventListener; | |||
|  |   proto.removeEventListener = function (nativeEventName, cb) { | |||
|  |     if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[cb]) { | |||
|  |       return nativeRemoveEventListener.apply(this, arguments); | |||
|  |     } | |||
|  |     var unwrappedCb = this._eventMap[cb]; | |||
|  |     delete this._eventMap[cb]; | |||
|  |     return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); | |||
|  |   }; | |||
|  | 
 | |||
|  |   Object.defineProperty(proto, 'on' + eventNameToWrap, { | |||
|  |     get: function get() { | |||
|  |       return this['_on' + eventNameToWrap]; | |||
|  |     }, | |||
|  |     set: function set(cb) { | |||
|  |       if (this['_on' + eventNameToWrap]) { | |||
|  |         this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); | |||
|  |         delete this['_on' + eventNameToWrap]; | |||
|  |       } | |||
|  |       if (cb) { | |||
|  |         this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); | |||
|  |       } | |||
|  |     }, | |||
|  | 
 | |||
|  |     enumerable: true, | |||
|  |     configurable: true | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | function disableLog(bool) { | |||
|  |   if (typeof bool !== 'boolean') { | |||
|  |     return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); | |||
|  |   } | |||
|  |   logDisabled_ = bool; | |||
|  |   return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; | |||
|  | } | |||
|  | 
 | |||
|  | /** | |||
|  |  * Disable or enable deprecation warnings | |||
|  |  * @param {!boolean} bool set to true to disable warnings. | |||
|  |  */ | |||
|  | function disableWarnings(bool) { | |||
|  |   if (typeof bool !== 'boolean') { | |||
|  |     return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); | |||
|  |   } | |||
|  |   deprecationWarnings_ = !bool; | |||
|  |   return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); | |||
|  | } | |||
|  | 
 | |||
|  | function log() { | |||
|  |   if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object') { | |||
|  |     if (logDisabled_) { | |||
|  |       return; | |||
|  |     } | |||
|  |     if (typeof console !== 'undefined' && typeof console.log === 'function') { | |||
|  |       console.log.apply(console, arguments); | |||
|  |     } | |||
|  |   } | |||
|  | } | |||
|  | 
 | |||
|  | /** | |||
|  |  * Shows a deprecation warning suggesting the modern and spec-compatible API. | |||
|  |  */ | |||
|  | function deprecated(oldMethod, newMethod) { | |||
|  |   if (!deprecationWarnings_) { | |||
|  |     return; | |||
|  |   } | |||
|  |   console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); | |||
|  | } | |||
|  | 
 | |||
|  | /** | |||
|  |  * Browser detector. | |||
|  |  * | |||
|  |  * @return {object} result containing browser and version | |||
|  |  *     properties. | |||
|  |  */ | |||
|  | function detectBrowser(window) { | |||
|  |   var navigator = window.navigator; | |||
|  | 
 | |||
|  |   // Returned result object.
 | |||
|  | 
 | |||
|  |   var result = { browser: null, version: null }; | |||
|  | 
 | |||
|  |   // Fail early if it's not a browser
 | |||
|  |   if (typeof window === 'undefined' || !window.navigator) { | |||
|  |     result.browser = 'Not a browser.'; | |||
|  |     return result; | |||
|  |   } | |||
|  | 
 | |||
|  |   if (navigator.mozGetUserMedia) { | |||
|  |     // Firefox.
 | |||
|  |     result.browser = 'firefox'; | |||
|  |     result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); | |||
|  |   } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection && !window.RTCIceGatherer) { | |||
|  |     // Chrome, Chromium, Webview, Opera.
 | |||
|  |     // Version matches Chrome/WebRTC version.
 | |||
|  |     // Chrome 74 removed webkitGetUserMedia on http as well so we need the
 | |||
|  |     // more complicated fallback to webkitRTCPeerConnection.
 | |||
|  |     result.browser = 'chrome'; | |||
|  |     result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); | |||
|  |   } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { | |||
|  |     // Edge.
 | |||
|  |     result.browser = 'edge'; | |||
|  |     result.version = extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); | |||
|  |   } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { | |||
|  |     // Safari.
 | |||
|  |     result.browser = 'safari'; | |||
|  |     result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); | |||
|  |     result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; | |||
|  |   } else { | |||
|  |     // Default fallthrough: not supported.
 | |||
|  |     result.browser = 'Not a supported browser.'; | |||
|  |     return result; | |||
|  |   } | |||
|  | 
 | |||
|  |   return result; | |||
|  | } | |||
|  | 
 | |||
|  | /** | |||
|  |  * Checks if something is an object. | |||
|  |  * | |||
|  |  * @param {*} val The something you want to check. | |||
|  |  * @return true if val is an object, false otherwise. | |||
|  |  */ | |||
|  | function isObject(val) { | |||
|  |   return Object.prototype.toString.call(val) === '[object Object]'; | |||
|  | } | |||
|  | 
 | |||
|  | /** | |||
|  |  * Remove all empty objects and undefined values | |||
|  |  * from a nested object -- an enhanced and vanilla version | |||
|  |  * of Lodash's `compact`. | |||
|  |  */ | |||
|  | function compactObject(data) { | |||
|  |   if (!isObject(data)) { | |||
|  |     return data; | |||
|  |   } | |||
|  | 
 | |||
|  |   return Object.keys(data).reduce(function (accumulator, key) { | |||
|  |     var isObj = isObject(data[key]); | |||
|  |     var value = isObj ? compactObject(data[key]) : data[key]; | |||
|  |     var isEmptyObject = isObj && !Object.keys(value).length; | |||
|  |     if (value === undefined || isEmptyObject) { | |||
|  |       return accumulator; | |||
|  |     } | |||
|  |     return Object.assign(accumulator, _defineProperty({}, key, value)); | |||
|  |   }, {}); | |||
|  | } | |||
|  | 
 | |||
|  | /* iterates the stats graph recursively. */ | |||
|  | function walkStats(stats, base, resultSet) { | |||
|  |   if (!base || resultSet.has(base.id)) { | |||
|  |     return; | |||
|  |   } | |||
|  |   resultSet.set(base.id, base); | |||
|  |   Object.keys(base).forEach(function (name) { | |||
|  |     if (name.endsWith('Id')) { | |||
|  |       walkStats(stats, stats.get(base[name]), resultSet); | |||
|  |     } else if (name.endsWith('Ids')) { | |||
|  |       base[name].forEach(function (id) { | |||
|  |         walkStats(stats, stats.get(id), resultSet); | |||
|  |       }); | |||
|  |     } | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | /* filter getStats for a sender/receiver track. */ | |||
|  | function filterStats(result, track, outbound) { | |||
|  |   var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; | |||
|  |   var filteredResult = new Map(); | |||
|  |   if (track === null) { | |||
|  |     return filteredResult; | |||
|  |   } | |||
|  |   var trackStats = []; | |||
|  |   result.forEach(function (value) { | |||
|  |     if (value.type === 'track' && value.trackIdentifier === track.id) { | |||
|  |       trackStats.push(value); | |||
|  |     } | |||
|  |   }); | |||
|  |   trackStats.forEach(function (trackStat) { | |||
|  |     result.forEach(function (stats) { | |||
|  |       if (stats.type === streamStatsType && stats.trackId === trackStat.id) { | |||
|  |         walkStats(result, stats, filteredResult); | |||
|  |       } | |||
|  |     }); | |||
|  |   }); | |||
|  |   return filteredResult; | |||
|  | } | |||
|  | 
 | |||
|  | },{}],16:[function(require,module,exports){ | |||
|  | /* | |||
|  |  *  Copyright (c) 2017 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. | |||
|  |  */ | |||
|  |  /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | var SDPUtils = require('sdp'); | |||
|  | 
 | |||
|  | function fixStatsType(stat) { | |||
|  |   return { | |||
|  |     inboundrtp: 'inbound-rtp', | |||
|  |     outboundrtp: 'outbound-rtp', | |||
|  |     candidatepair: 'candidate-pair', | |||
|  |     localcandidate: 'local-candidate', | |||
|  |     remotecandidate: 'remote-candidate' | |||
|  |   }[stat.type] || stat.type; | |||
|  | } | |||
|  | 
 | |||
|  | function writeMediaSection(transceiver, caps, type, stream, dtlsRole) { | |||
|  |   var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); | |||
|  | 
 | |||
|  |   // Map ICE parameters (ufrag, pwd) to SDP.
 | |||
|  |   sdp += SDPUtils.writeIceParameters( | |||
|  |       transceiver.iceGatherer.getLocalParameters()); | |||
|  | 
 | |||
|  |   // Map DTLS parameters to SDP.
 | |||
|  |   sdp += SDPUtils.writeDtlsParameters( | |||
|  |       transceiver.dtlsTransport.getLocalParameters(), | |||
|  |       type === 'offer' ? 'actpass' : dtlsRole || 'active'); | |||
|  | 
 | |||
|  |   sdp += 'a=mid:' + transceiver.mid + '\r\n'; | |||
|  | 
 | |||
|  |   if (transceiver.rtpSender && transceiver.rtpReceiver) { | |||
|  |     sdp += 'a=sendrecv\r\n'; | |||
|  |   } else if (transceiver.rtpSender) { | |||
|  |     sdp += 'a=sendonly\r\n'; | |||
|  |   } else if (transceiver.rtpReceiver) { | |||
|  |     sdp += 'a=recvonly\r\n'; | |||
|  |   } else { | |||
|  |     sdp += 'a=inactive\r\n'; | |||
|  |   } | |||
|  | 
 | |||
|  |   if (transceiver.rtpSender) { | |||
|  |     var trackId = transceiver.rtpSender._initialTrackId || | |||
|  |         transceiver.rtpSender.track.id; | |||
|  |     transceiver.rtpSender._initialTrackId = trackId; | |||
|  |     // spec.
 | |||
|  |     var msid = 'msid:' + (stream ? stream.id : '-') + ' ' + | |||
|  |         trackId + '\r\n'; | |||
|  |     sdp += 'a=' + msid; | |||
|  |     // for Chrome. Legacy should no longer be required.
 | |||
|  |     sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + | |||
|  |         ' ' + msid; | |||
|  | 
 | |||
|  |     // RTX
 | |||
|  |     if (transceiver.sendEncodingParameters[0].rtx) { | |||
|  |       sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + | |||
|  |           ' ' + msid; | |||
|  |       sdp += 'a=ssrc-group:FID ' + | |||
|  |           transceiver.sendEncodingParameters[0].ssrc + ' ' + | |||
|  |           transceiver.sendEncodingParameters[0].rtx.ssrc + | |||
|  |           '\r\n'; | |||
|  |     } | |||
|  |   } | |||
|  |   // FIXME: this should be written by writeRtpDescription.
 | |||
|  |   sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + | |||
|  |       ' cname:' + SDPUtils.localCName + '\r\n'; | |||
|  |   if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { | |||
|  |     sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + | |||
|  |         ' cname:' + SDPUtils.localCName + '\r\n'; | |||
|  |   } | |||
|  |   return sdp; | |||
|  | } | |||
|  | 
 | |||
|  | // Edge does not like
 | |||
|  | // 1) stun: filtered after 14393 unless ?transport=udp is present
 | |||
|  | // 2) turn: that does not have all of turn:host:port?transport=udp
 | |||
|  | // 3) turn: with ipv6 addresses
 | |||
|  | // 4) turn: occurring muliple times
 | |||
|  | function filterIceServers(iceServers, edgeVersion) { | |||
|  |   var hasTurn = false; | |||
|  |   iceServers = JSON.parse(JSON.stringify(iceServers)); | |||
|  |   return iceServers.filter(function(server) { | |||
|  |     if (server && (server.urls || server.url)) { | |||
|  |       var urls = server.urls || server.url; | |||
|  |       if (server.url && !server.urls) { | |||
|  |         console.warn('RTCIceServer.url is deprecated! Use urls instead.'); | |||
|  |       } | |||
|  |       var isString = typeof urls === 'string'; | |||
|  |       if (isString) { | |||
|  |         urls = [urls]; | |||
|  |       } | |||
|  |       urls = urls.filter(function(url) { | |||
|  |         var validTurn = url.indexOf('turn:') === 0 && | |||
|  |             url.indexOf('transport=udp') !== -1 && | |||
|  |             url.indexOf('turn:[') === -1 && | |||
|  |             !hasTurn; | |||
|  | 
 | |||
|  |         if (validTurn) { | |||
|  |           hasTurn = true; | |||
|  |           return true; | |||
|  |         } | |||
|  |         return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && | |||
|  |             url.indexOf('?transport=udp') === -1; | |||
|  |       }); | |||
|  | 
 | |||
|  |       delete server.url; | |||
|  |       server.urls = isString ? urls[0] : urls; | |||
|  |       return !!urls.length; | |||
|  |     } | |||
|  |   }); | |||
|  | } | |||
|  | 
 | |||
|  | // Determines the intersection of local and remote capabilities.
 | |||
|  | function getCommonCapabilities(localCapabilities, remoteCapabilities) { | |||
|  |   var commonCapabilities = { | |||
|  |     codecs: [], | |||
|  |     headerExtensions: [], | |||
|  |     fecMechanisms: [] | |||
|  |   }; | |||
|  | 
 | |||
|  |   var findCodecByPayloadType = function(pt, codecs) { | |||
|  |     pt = parseInt(pt, 10); | |||
|  |     for (var i = 0; i < codecs.length; i++) { | |||
|  |       if (codecs[i].payloadType === pt || | |||
|  |           codecs[i].preferredPayloadType === pt) { | |||
|  |         return codecs[i]; | |||
|  |       } | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { | |||
|  |     var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); | |||
|  |     var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); | |||
|  |     return lCodec && rCodec && | |||
|  |         lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); | |||
|  |   }; | |||
|  | 
 | |||
|  |   localCapabilities.codecs.forEach(function(lCodec) { | |||
|  |     for (var i = 0; i < remoteCapabilities.codecs.length; i++) { | |||
|  |       var rCodec = remoteCapabilities.codecs[i]; | |||
|  |       if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && | |||
|  |           lCodec.clockRate === rCodec.clockRate) { | |||
|  |         if (lCodec.name.toLowerCase() === 'rtx' && | |||
|  |             lCodec.parameters && rCodec.parameters.apt) { | |||
|  |           // for RTX we need to find the local rtx that has a apt
 | |||
|  |           // which points to the same local codec as the remote one.
 | |||
|  |           if (!rtxCapabilityMatches(lCodec, rCodec, | |||
|  |               localCapabilities.codecs, remoteCapabilities.codecs)) { | |||
|  |             continue; | |||
|  |           } | |||
|  |         } | |||
|  |         rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
 | |||
|  |         // number of channels is the highest common number of channels
 | |||
|  |         rCodec.numChannels = Math.min(lCodec.numChannels, | |||
|  |             rCodec.numChannels); | |||
|  |         // push rCodec so we reply with offerer payload type
 | |||
|  |         commonCapabilities.codecs.push(rCodec); | |||
|  | 
 | |||
|  |         // determine common feedback mechanisms
 | |||
|  |         rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { | |||
|  |           for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { | |||
|  |             if (lCodec.rtcpFeedback[j].type === fb.type && | |||
|  |                 lCodec.rtcpFeedback[j].parameter === fb.parameter) { | |||
|  |               return true; | |||
|  |             } | |||
|  |           } | |||
|  |           return false; | |||
|  |         }); | |||
|  |         // FIXME: also need to determine .parameters
 | |||
|  |         //  see https://github.com/openpeer/ortc/issues/569
 | |||
|  |         break; | |||
|  |       } | |||
|  |     } | |||
|  |   }); | |||
|  | 
 | |||
|  |   localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { | |||
|  |     for (var i = 0; i < remoteCapabilities.headerExtensions.length; | |||
|  |          i++) { | |||
|  |       var rHeaderExtension = remoteCapabilities.headerExtensions[i]; | |||
|  |       if (lHeaderExtension.uri === rHeaderExtension.uri) { | |||
|  |         commonCapabilities.headerExtensions.push(rHeaderExtension); | |||
|  |         break; | |||
|  |       } | |||
|  |     } | |||
|  |   }); | |||
|  | 
 | |||
|  |   // FIXME: fecMechanisms
 | |||
|  |   return commonCapabilities; | |||
|  | } | |||
|  | 
 | |||
|  | // is action=setLocalDescription with type allowed in signalingState
 | |||
|  | function isActionAllowedInSignalingState(action, type, signalingState) { | |||
|  |   return { | |||
|  |     offer: { | |||
|  |       setLocalDescription: ['stable', 'have-local-offer'], | |||
|  |       setRemoteDescription: ['stable', 'have-remote-offer'] | |||
|  |     }, | |||
|  |     answer: { | |||
|  |       setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], | |||
|  |       setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] | |||
|  |     } | |||
|  |   }[type][action].indexOf(signalingState) !== -1; | |||
|  | } | |||
|  | 
 | |||
|  | function maybeAddCandidate(iceTransport, candidate) { | |||
|  |   // Edge's internal representation adds some fields therefore
 | |||
|  |   // not all fieldѕ are taken into account.
 | |||
|  |   var alreadyAdded = iceTransport.getRemoteCandidates() | |||
|  |       .find(function(remoteCandidate) { | |||
|  |         return candidate.foundation === remoteCandidate.foundation && | |||
|  |             candidate.ip === remoteCandidate.ip && | |||
|  |             candidate.port === remoteCandidate.port && | |||
|  |             candidate.priority === remoteCandidate.priority && | |||
|  |             candidate.protocol === remoteCandidate.protocol && | |||
|  |             candidate.type === remoteCandidate.type; | |||
|  |       }); | |||
|  |   if (!alreadyAdded) { | |||
|  |     iceTransport.addRemoteCandidate(candidate); | |||
|  |   } | |||
|  |   return !alreadyAdded; | |||
|  | } | |||
|  | 
 | |||
|  | 
 | |||
|  | function makeError(name, description) { | |||
|  |   var e = new Error(description); | |||
|  |   e.name = name; | |||
|  |   // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names
 | |||
|  |   e.code = { | |||
|  |     NotSupportedError: 9, | |||
|  |     InvalidStateError: 11, | |||
|  |     InvalidAccessError: 15, | |||
|  |     TypeError: undefined, | |||
|  |     OperationError: undefined | |||
|  |   }[name]; | |||
|  |   return e; | |||
|  | } | |||
|  | 
 | |||
|  | module.exports = function(window, edgeVersion) { | |||
|  |   // https://w3c.github.io/mediacapture-main/#mediastream
 | |||
|  |   // Helper function to add the track to the stream and
 | |||
|  |   // dispatch the event ourselves.
 | |||
|  |   function addTrackToStreamAndFireEvent(track, stream) { | |||
|  |     stream.addTrack(track); | |||
|  |     stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack', | |||
|  |         {track: track})); | |||
|  |   } | |||
|  | 
 | |||
|  |   function removeTrackFromStreamAndFireEvent(track, stream) { | |||
|  |     stream.removeTrack(track); | |||
|  |     stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack', | |||
|  |         {track: track})); | |||
|  |   } | |||
|  | 
 | |||
|  |   function fireAddTrack(pc, track, receiver, streams) { | |||
|  |     var trackEvent = new Event('track'); | |||
|  |     trackEvent.track = track; | |||
|  |     trackEvent.receiver = receiver; | |||
|  |     trackEvent.transceiver = {receiver: receiver}; | |||
|  |     trackEvent.streams = streams; | |||
|  |     window.setTimeout(function() { | |||
|  |       pc._dispatchEvent('track', trackEvent); | |||
|  |     }); | |||
|  |   } | |||
|  | 
 | |||
|  |   var RTCPeerConnection = function(config) { | |||
|  |     var pc = this; | |||
|  | 
 | |||
|  |     var _eventTarget = document.createDocumentFragment(); | |||
|  |     ['addEventListener', 'removeEventListener', 'dispatchEvent'] | |||
|  |         .forEach(function(method) { | |||
|  |           pc[method] = _eventTarget[method].bind(_eventTarget); | |||
|  |         }); | |||
|  | 
 | |||
|  |     this.canTrickleIceCandidates = null; | |||
|  | 
 | |||
|  |     this.needNegotiation = false; | |||
|  | 
 | |||
|  |     this.localStreams = []; | |||
|  |     this.remoteStreams = []; | |||
|  | 
 | |||
|  |     this._localDescription = null; | |||
|  |     this._remoteDescription = null; | |||
|  | 
 | |||
|  |     this.signalingState = 'stable'; | |||
|  |     this.iceConnectionState = 'new'; | |||
|  |     this.connectionState = 'new'; | |||
|  |     this.iceGatheringState = 'new'; | |||
|  | 
 | |||
|  |     config = JSON.parse(JSON.stringify(config || {})); | |||
|  | 
 | |||
|  |     this.usingBundle = config.bundlePolicy === 'max-bundle'; | |||
|  |     if (config.rtcpMuxPolicy === 'negotiate') { | |||
|  |       throw(makeError('NotSupportedError', | |||
|  |           'rtcpMuxPolicy \'negotiate\' is not supported')); | |||
|  |     } else if (!config.rtcpMuxPolicy) { | |||
|  |       config.rtcpMuxPolicy = 'require'; | |||
|  |     } | |||
|  | 
 | |||
|  |     switch (config.iceTransportPolicy) { | |||
|  |       case 'all': | |||
|  |       case 'relay': | |||
|  |         break; | |||
|  |       default: | |||
|  |         config.iceTransportPolicy = 'all'; | |||
|  |         break; | |||
|  |     } | |||
|  | 
 | |||
|  |     switch (config.bundlePolicy) { | |||
|  |       case 'balanced': | |||
|  |       case 'max-compat': | |||
|  |       case 'max-bundle': | |||
|  |         break; | |||
|  |       default: | |||
|  |         config.bundlePolicy = 'balanced'; | |||
|  |         break; | |||
|  |     } | |||
|  | 
 | |||
|  |     config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); | |||
|  | 
 | |||
|  |     this._iceGatherers = []; | |||
|  |     if (config.iceCandidatePoolSize) { | |||
|  |       for (var i = config.iceCandidatePoolSize; i > 0; i--) { | |||
|  |         this._iceGatherers.push(new window.RTCIceGatherer({ | |||
|  |           iceServers: config.iceServers, | |||
|  |           gatherPolicy: config.iceTransportPolicy | |||
|  |         })); | |||
|  |       } | |||
|  |     } else { | |||
|  |       config.iceCandidatePoolSize = 0; | |||
|  |     } | |||
|  | 
 | |||
|  |     this._config = config; | |||
|  | 
 | |||
|  |     // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
 | |||
|  |     // everything that is needed to describe a SDP m-line.
 | |||
|  |     this.transceivers = []; | |||
|  | 
 | |||
|  |     this._sdpSessionId = SDPUtils.generateSessionId(); | |||
|  |     this._sdpSessionVersion = 0; | |||
|  | 
 | |||
|  |     this._dtlsRole = undefined; // role for a=setup to use in answers.
 | |||
|  | 
 | |||
|  |     this._isClosed = false; | |||
|  |   }; | |||
|  | 
 | |||
|  |   Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { | |||
|  |     configurable: true, | |||
|  |     get: function() { | |||
|  |       return this._localDescription; | |||
|  |     } | |||
|  |   }); | |||
|  |   Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { | |||
|  |     configurable: true, | |||
|  |     get: function() { | |||
|  |       return this._remoteDescription; | |||
|  |     } | |||
|  |   }); | |||
|  | 
 | |||
|  |   // set up event handlers on prototype
 | |||
|  |   RTCPeerConnection.prototype.onicecandidate = null; | |||
|  |   RTCPeerConnection.prototype.onaddstream = null; | |||
|  |   RTCPeerConnection.prototype.ontrack = null; | |||
|  |   RTCPeerConnection.prototype.onremovestream = null; | |||
|  |   RTCPeerConnection.prototype.onsignalingstatechange = null; | |||
|  |   RTCPeerConnection.prototype.oniceconnectionstatechange = null; | |||
|  |   RTCPeerConnection.prototype.onconnectionstatechange = null; | |||
|  |   RTCPeerConnection.prototype.onicegatheringstatechange = null; | |||
|  |   RTCPeerConnection.prototype.onnegotiationneeded = null; | |||
|  |   RTCPeerConnection.prototype.ondatachannel = null; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype._dispatchEvent = function(name, event) { | |||
|  |     if (this._isClosed) { | |||
|  |       return; | |||
|  |     } | |||
|  |     this.dispatchEvent(event); | |||
|  |     if (typeof this['on' + name] === 'function') { | |||
|  |       this['on' + name](event); | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype._emitGatheringStateChange = function() { | |||
|  |     var event = new Event('icegatheringstatechange'); | |||
|  |     this._dispatchEvent('icegatheringstatechange', event); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.getConfiguration = function() { | |||
|  |     return this._config; | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.getLocalStreams = function() { | |||
|  |     return this.localStreams; | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.getRemoteStreams = function() { | |||
|  |     return this.remoteStreams; | |||
|  |   }; | |||
|  | 
 | |||
|  |   // internal helper to create a transceiver object.
 | |||
|  |   // (which is not yet the same as the WebRTC 1.0 transceiver)
 | |||
|  |   RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) { | |||
|  |     var hasBundleTransport = this.transceivers.length > 0; | |||
|  |     var transceiver = { | |||
|  |       track: null, | |||
|  |       iceGatherer: null, | |||
|  |       iceTransport: null, | |||
|  |       dtlsTransport: null, | |||
|  |       localCapabilities: null, | |||
|  |       remoteCapabilities: null, | |||
|  |       rtpSender: null, | |||
|  |       rtpReceiver: null, | |||
|  |       kind: kind, | |||
|  |       mid: null, | |||
|  |       sendEncodingParameters: null, | |||
|  |       recvEncodingParameters: null, | |||
|  |       stream: null, | |||
|  |       associatedRemoteMediaStreams: [], | |||
|  |       wantReceive: true | |||
|  |     }; | |||
|  |     if (this.usingBundle && hasBundleTransport) { | |||
|  |       transceiver.iceTransport = this.transceivers[0].iceTransport; | |||
|  |       transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; | |||
|  |     } else { | |||
|  |       var transports = this._createIceAndDtlsTransports(); | |||
|  |       transceiver.iceTransport = transports.iceTransport; | |||
|  |       transceiver.dtlsTransport = transports.dtlsTransport; | |||
|  |     } | |||
|  |     if (!doNotAdd) { | |||
|  |       this.transceivers.push(transceiver); | |||
|  |     } | |||
|  |     return transceiver; | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.addTrack = function(track, stream) { | |||
|  |     if (this._isClosed) { | |||
|  |       throw makeError('InvalidStateError', | |||
|  |           'Attempted to call addTrack on a closed peerconnection.'); | |||
|  |     } | |||
|  | 
 | |||
|  |     var alreadyExists = this.transceivers.find(function(s) { | |||
|  |       return s.track === track; | |||
|  |     }); | |||
|  | 
 | |||
|  |     if (alreadyExists) { | |||
|  |       throw makeError('InvalidAccessError', 'Track already exists.'); | |||
|  |     } | |||
|  | 
 | |||
|  |     var transceiver; | |||
|  |     for (var i = 0; i < this.transceivers.length; i++) { | |||
|  |       if (!this.transceivers[i].track && | |||
|  |           this.transceivers[i].kind === track.kind) { | |||
|  |         transceiver = this.transceivers[i]; | |||
|  |       } | |||
|  |     } | |||
|  |     if (!transceiver) { | |||
|  |       transceiver = this._createTransceiver(track.kind); | |||
|  |     } | |||
|  | 
 | |||
|  |     this._maybeFireNegotiationNeeded(); | |||
|  | 
 | |||
|  |     if (this.localStreams.indexOf(stream) === -1) { | |||
|  |       this.localStreams.push(stream); | |||
|  |     } | |||
|  | 
 | |||
|  |     transceiver.track = track; | |||
|  |     transceiver.stream = stream; | |||
|  |     transceiver.rtpSender = new window.RTCRtpSender(track, | |||
|  |         transceiver.dtlsTransport); | |||
|  |     return transceiver.rtpSender; | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.addStream = function(stream) { | |||
|  |     var pc = this; | |||
|  |     if (edgeVersion >= 15025) { | |||
|  |       stream.getTracks().forEach(function(track) { | |||
|  |         pc.addTrack(track, stream); | |||
|  |       }); | |||
|  |     } else { | |||
|  |       // Clone is necessary for local demos mostly, attaching directly
 | |||
|  |       // to two different senders does not work (build 10547).
 | |||
|  |       // Fixed in 15025 (or earlier)
 | |||
|  |       var clonedStream = stream.clone(); | |||
|  |       stream.getTracks().forEach(function(track, idx) { | |||
|  |         var clonedTrack = clonedStream.getTracks()[idx]; | |||
|  |         track.addEventListener('enabled', function(event) { | |||
|  |           clonedTrack.enabled = event.enabled; | |||
|  |         }); | |||
|  |       }); | |||
|  |       clonedStream.getTracks().forEach(function(track) { | |||
|  |         pc.addTrack(track, clonedStream); | |||
|  |       }); | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.removeTrack = function(sender) { | |||
|  |     if (this._isClosed) { | |||
|  |       throw makeError('InvalidStateError', | |||
|  |           'Attempted to call removeTrack on a closed peerconnection.'); | |||
|  |     } | |||
|  | 
 | |||
|  |     if (!(sender instanceof window.RTCRtpSender)) { | |||
|  |       throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + | |||
|  |           'does not implement interface RTCRtpSender.'); | |||
|  |     } | |||
|  | 
 | |||
|  |     var transceiver = this.transceivers.find(function(t) { | |||
|  |       return t.rtpSender === sender; | |||
|  |     }); | |||
|  | 
 | |||
|  |     if (!transceiver) { | |||
|  |       throw makeError('InvalidAccessError', | |||
|  |           'Sender was not created by this connection.'); | |||
|  |     } | |||
|  |     var stream = transceiver.stream; | |||
|  | 
 | |||
|  |     transceiver.rtpSender.stop(); | |||
|  |     transceiver.rtpSender = null; | |||
|  |     transceiver.track = null; | |||
|  |     transceiver.stream = null; | |||
|  | 
 | |||
|  |     // remove the stream from the set of local streams
 | |||
|  |     var localStreams = this.transceivers.map(function(t) { | |||
|  |       return t.stream; | |||
|  |     }); | |||
|  |     if (localStreams.indexOf(stream) === -1 && | |||
|  |         this.localStreams.indexOf(stream) > -1) { | |||
|  |       this.localStreams.splice(this.localStreams.indexOf(stream), 1); | |||
|  |     } | |||
|  | 
 | |||
|  |     this._maybeFireNegotiationNeeded(); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.removeStream = function(stream) { | |||
|  |     var pc = this; | |||
|  |     stream.getTracks().forEach(function(track) { | |||
|  |       var sender = pc.getSenders().find(function(s) { | |||
|  |         return s.track === track; | |||
|  |       }); | |||
|  |       if (sender) { | |||
|  |         pc.removeTrack(sender); | |||
|  |       } | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.getSenders = function() { | |||
|  |     return this.transceivers.filter(function(transceiver) { | |||
|  |       return !!transceiver.rtpSender; | |||
|  |     }) | |||
|  |     .map(function(transceiver) { | |||
|  |       return transceiver.rtpSender; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.getReceivers = function() { | |||
|  |     return this.transceivers.filter(function(transceiver) { | |||
|  |       return !!transceiver.rtpReceiver; | |||
|  |     }) | |||
|  |     .map(function(transceiver) { | |||
|  |       return transceiver.rtpReceiver; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, | |||
|  |       usingBundle) { | |||
|  |     var pc = this; | |||
|  |     if (usingBundle && sdpMLineIndex > 0) { | |||
|  |       return this.transceivers[0].iceGatherer; | |||
|  |     } else if (this._iceGatherers.length) { | |||
|  |       return this._iceGatherers.shift(); | |||
|  |     } | |||
|  |     var iceGatherer = new window.RTCIceGatherer({ | |||
|  |       iceServers: this._config.iceServers, | |||
|  |       gatherPolicy: this._config.iceTransportPolicy | |||
|  |     }); | |||
|  |     Object.defineProperty(iceGatherer, 'state', | |||
|  |         {value: 'new', writable: true} | |||
|  |     ); | |||
|  | 
 | |||
|  |     this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; | |||
|  |     this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { | |||
|  |       var end = !event.candidate || Object.keys(event.candidate).length === 0; | |||
|  |       // polyfill since RTCIceGatherer.state is not implemented in
 | |||
|  |       // Edge 10547 yet.
 | |||
|  |       iceGatherer.state = end ? 'completed' : 'gathering'; | |||
|  |       if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { | |||
|  |         pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); | |||
|  |       } | |||
|  |     }; | |||
|  |     iceGatherer.addEventListener('localcandidate', | |||
|  |       this.transceivers[sdpMLineIndex].bufferCandidates); | |||
|  |     return iceGatherer; | |||
|  |   }; | |||
|  | 
 | |||
|  |   // start gathering from an RTCIceGatherer.
 | |||
|  |   RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { | |||
|  |     var pc = this; | |||
|  |     var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; | |||
|  |     if (iceGatherer.onlocalcandidate) { | |||
|  |       return; | |||
|  |     } | |||
|  |     var bufferedCandidateEvents = | |||
|  |       this.transceivers[sdpMLineIndex].bufferedCandidateEvents; | |||
|  |     this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; | |||
|  |     iceGatherer.removeEventListener('localcandidate', | |||
|  |       this.transceivers[sdpMLineIndex].bufferCandidates); | |||
|  |     iceGatherer.onlocalcandidate = function(evt) { | |||
|  |       if (pc.usingBundle && sdpMLineIndex > 0) { | |||
|  |         // if we know that we use bundle we can drop candidates with
 | |||
|  |         // ѕdpMLineIndex > 0. If we don't do this then our state gets
 | |||
|  |         // confused since we dispose the extra ice gatherer.
 | |||
|  |         return; | |||
|  |       } | |||
|  |       var event = new Event('icecandidate'); | |||
|  |       event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; | |||
|  | 
 | |||
|  |       var cand = evt.candidate; | |||
|  |       // Edge emits an empty object for RTCIceCandidateComplete‥
 | |||
|  |       var end = !cand || Object.keys(cand).length === 0; | |||
|  |       if (end) { | |||
|  |         // polyfill since RTCIceGatherer.state is not implemented in
 | |||
|  |         // Edge 10547 yet.
 | |||
|  |         if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { | |||
|  |           iceGatherer.state = 'completed'; | |||
|  |         } | |||
|  |       } else { | |||
|  |         if (iceGatherer.state === 'new') { | |||
|  |           iceGatherer.state = 'gathering'; | |||
|  |         } | |||
|  |         // RTCIceCandidate doesn't have a component, needs to be added
 | |||
|  |         cand.component = 1; | |||
|  |         // also the usernameFragment. TODO: update SDP to take both variants.
 | |||
|  |         cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; | |||
|  | 
 | |||
|  |         var serializedCandidate = SDPUtils.writeCandidate(cand); | |||
|  |         event.candidate = Object.assign(event.candidate, | |||
|  |             SDPUtils.parseCandidate(serializedCandidate)); | |||
|  | 
 | |||
|  |         event.candidate.candidate = serializedCandidate; | |||
|  |         event.candidate.toJSON = function() { | |||
|  |           return { | |||
|  |             candidate: event.candidate.candidate, | |||
|  |             sdpMid: event.candidate.sdpMid, | |||
|  |             sdpMLineIndex: event.candidate.sdpMLineIndex, | |||
|  |             usernameFragment: event.candidate.usernameFragment | |||
|  |           }; | |||
|  |         }; | |||
|  |       } | |||
|  | 
 | |||
|  |       // update local description.
 | |||
|  |       var sections = SDPUtils.getMediaSections(pc._localDescription.sdp); | |||
|  |       if (!end) { | |||
|  |         sections[event.candidate.sdpMLineIndex] += | |||
|  |             'a=' + event.candidate.candidate + '\r\n'; | |||
|  |       } else { | |||
|  |         sections[event.candidate.sdpMLineIndex] += | |||
|  |             'a=end-of-candidates\r\n'; | |||
|  |       } | |||
|  |       pc._localDescription.sdp = | |||
|  |           SDPUtils.getDescription(pc._localDescription.sdp) + | |||
|  |           sections.join(''); | |||
|  |       var complete = pc.transceivers.every(function(transceiver) { | |||
|  |         return transceiver.iceGatherer && | |||
|  |             transceiver.iceGatherer.state === 'completed'; | |||
|  |       }); | |||
|  | 
 | |||
|  |       if (pc.iceGatheringState !== 'gathering') { | |||
|  |         pc.iceGatheringState = 'gathering'; | |||
|  |         pc._emitGatheringStateChange(); | |||
|  |       } | |||
|  | 
 | |||
|  |       // Emit candidate. Also emit null candidate when all gatherers are
 | |||
|  |       // complete.
 | |||
|  |       if (!end) { | |||
|  |         pc._dispatchEvent('icecandidate', event); | |||
|  |       } | |||
|  |       if (complete) { | |||
|  |         pc._dispatchEvent('icecandidate', new Event('icecandidate')); | |||
|  |         pc.iceGatheringState = 'complete'; | |||
|  |         pc._emitGatheringStateChange(); | |||
|  |       } | |||
|  |     }; | |||
|  | 
 | |||
|  |     // emit already gathered candidates.
 | |||
|  |     window.setTimeout(function() { | |||
|  |       bufferedCandidateEvents.forEach(function(e) { | |||
|  |         iceGatherer.onlocalcandidate(e); | |||
|  |       }); | |||
|  |     }, 0); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Create ICE transport and DTLS transport.
 | |||
|  |   RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { | |||
|  |     var pc = this; | |||
|  |     var iceTransport = new window.RTCIceTransport(null); | |||
|  |     iceTransport.onicestatechange = function() { | |||
|  |       pc._updateIceConnectionState(); | |||
|  |       pc._updateConnectionState(); | |||
|  |     }; | |||
|  | 
 | |||
|  |     var dtlsTransport = new window.RTCDtlsTransport(iceTransport); | |||
|  |     dtlsTransport.ondtlsstatechange = function() { | |||
|  |       pc._updateConnectionState(); | |||
|  |     }; | |||
|  |     dtlsTransport.onerror = function() { | |||
|  |       // onerror does not set state to failed by itself.
 | |||
|  |       Object.defineProperty(dtlsTransport, 'state', | |||
|  |           {value: 'failed', writable: true}); | |||
|  |       pc._updateConnectionState(); | |||
|  |     }; | |||
|  | 
 | |||
|  |     return { | |||
|  |       iceTransport: iceTransport, | |||
|  |       dtlsTransport: dtlsTransport | |||
|  |     }; | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Destroy ICE gatherer, ICE transport and DTLS transport.
 | |||
|  |   // Without triggering the callbacks.
 | |||
|  |   RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( | |||
|  |       sdpMLineIndex) { | |||
|  |     var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; | |||
|  |     if (iceGatherer) { | |||
|  |       delete iceGatherer.onlocalcandidate; | |||
|  |       delete this.transceivers[sdpMLineIndex].iceGatherer; | |||
|  |     } | |||
|  |     var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; | |||
|  |     if (iceTransport) { | |||
|  |       delete iceTransport.onicestatechange; | |||
|  |       delete this.transceivers[sdpMLineIndex].iceTransport; | |||
|  |     } | |||
|  |     var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; | |||
|  |     if (dtlsTransport) { | |||
|  |       delete dtlsTransport.ondtlsstatechange; | |||
|  |       delete dtlsTransport.onerror; | |||
|  |       delete this.transceivers[sdpMLineIndex].dtlsTransport; | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Start the RTP Sender and Receiver for a transceiver.
 | |||
|  |   RTCPeerConnection.prototype._transceive = function(transceiver, | |||
|  |       send, recv) { | |||
|  |     var params = getCommonCapabilities(transceiver.localCapabilities, | |||
|  |         transceiver.remoteCapabilities); | |||
|  |     if (send && transceiver.rtpSender) { | |||
|  |       params.encodings = transceiver.sendEncodingParameters; | |||
|  |       params.rtcp = { | |||
|  |         cname: SDPUtils.localCName, | |||
|  |         compound: transceiver.rtcpParameters.compound | |||
|  |       }; | |||
|  |       if (transceiver.recvEncodingParameters.length) { | |||
|  |         params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; | |||
|  |       } | |||
|  |       transceiver.rtpSender.send(params); | |||
|  |     } | |||
|  |     if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { | |||
|  |       // remove RTX field in Edge 14942
 | |||
|  |       if (transceiver.kind === 'video' | |||
|  |           && transceiver.recvEncodingParameters | |||
|  |           && edgeVersion < 15019) { | |||
|  |         transceiver.recvEncodingParameters.forEach(function(p) { | |||
|  |           delete p.rtx; | |||
|  |         }); | |||
|  |       } | |||
|  |       if (transceiver.recvEncodingParameters.length) { | |||
|  |         params.encodings = transceiver.recvEncodingParameters; | |||
|  |       } else { | |||
|  |         params.encodings = [{}]; | |||
|  |       } | |||
|  |       params.rtcp = { | |||
|  |         compound: transceiver.rtcpParameters.compound | |||
|  |       }; | |||
|  |       if (transceiver.rtcpParameters.cname) { | |||
|  |         params.rtcp.cname = transceiver.rtcpParameters.cname; | |||
|  |       } | |||
|  |       if (transceiver.sendEncodingParameters.length) { | |||
|  |         params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; | |||
|  |       } | |||
|  |       transceiver.rtpReceiver.receive(params); | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.setLocalDescription = function(description) { | |||
|  |     var pc = this; | |||
|  | 
 | |||
|  |     // Note: pranswer is not supported.
 | |||
|  |     if (['offer', 'answer'].indexOf(description.type) === -1) { | |||
|  |       return Promise.reject(makeError('TypeError', | |||
|  |           'Unsupported type "' + description.type + '"')); | |||
|  |     } | |||
|  | 
 | |||
|  |     if (!isActionAllowedInSignalingState('setLocalDescription', | |||
|  |         description.type, pc.signalingState) || pc._isClosed) { | |||
|  |       return Promise.reject(makeError('InvalidStateError', | |||
|  |           'Can not set local ' + description.type + | |||
|  |           ' in state ' + pc.signalingState)); | |||
|  |     } | |||
|  | 
 | |||
|  |     var sections; | |||
|  |     var sessionpart; | |||
|  |     if (description.type === 'offer') { | |||
|  |       // VERY limited support for SDP munging. Limited to:
 | |||
|  |       // * changing the order of codecs
 | |||
|  |       sections = SDPUtils.splitSections(description.sdp); | |||
|  |       sessionpart = sections.shift(); | |||
|  |       sections.forEach(function(mediaSection, sdpMLineIndex) { | |||
|  |         var caps = SDPUtils.parseRtpParameters(mediaSection); | |||
|  |         pc.transceivers[sdpMLineIndex].localCapabilities = caps; | |||
|  |       }); | |||
|  | 
 | |||
|  |       pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { | |||
|  |         pc._gather(transceiver.mid, sdpMLineIndex); | |||
|  |       }); | |||
|  |     } else if (description.type === 'answer') { | |||
|  |       sections = SDPUtils.splitSections(pc._remoteDescription.sdp); | |||
|  |       sessionpart = sections.shift(); | |||
|  |       var isIceLite = SDPUtils.matchPrefix(sessionpart, | |||
|  |           'a=ice-lite').length > 0; | |||
|  |       sections.forEach(function(mediaSection, sdpMLineIndex) { | |||
|  |         var transceiver = pc.transceivers[sdpMLineIndex]; | |||
|  |         var iceGatherer = transceiver.iceGatherer; | |||
|  |         var iceTransport = transceiver.iceTransport; | |||
|  |         var dtlsTransport = transceiver.dtlsTransport; | |||
|  |         var localCapabilities = transceiver.localCapabilities; | |||
|  |         var remoteCapabilities = transceiver.remoteCapabilities; | |||
|  | 
 | |||
|  |         // treat bundle-only as not-rejected.
 | |||
|  |         var rejected = SDPUtils.isRejected(mediaSection) && | |||
|  |             SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; | |||
|  | 
 | |||
|  |         if (!rejected && !transceiver.rejected) { | |||
|  |           var remoteIceParameters = SDPUtils.getIceParameters( | |||
|  |               mediaSection, sessionpart); | |||
|  |           var remoteDtlsParameters = SDPUtils.getDtlsParameters( | |||
|  |               mediaSection, sessionpart); | |||
|  |           if (isIceLite) { | |||
|  |             remoteDtlsParameters.role = 'server'; | |||
|  |           } | |||
|  | 
 | |||
|  |           if (!pc.usingBundle || sdpMLineIndex === 0) { | |||
|  |             pc._gather(transceiver.mid, sdpMLineIndex); | |||
|  |             if (iceTransport.state === 'new') { | |||
|  |               iceTransport.start(iceGatherer, remoteIceParameters, | |||
|  |                   isIceLite ? 'controlling' : 'controlled'); | |||
|  |             } | |||
|  |             if (dtlsTransport.state === 'new') { | |||
|  |               dtlsTransport.start(remoteDtlsParameters); | |||
|  |             } | |||
|  |           } | |||
|  | 
 | |||
|  |           // Calculate intersection of capabilities.
 | |||
|  |           var params = getCommonCapabilities(localCapabilities, | |||
|  |               remoteCapabilities); | |||
|  | 
 | |||
|  |           // Start the RTCRtpSender. The RTCRtpReceiver for this
 | |||
|  |           // transceiver has already been started in setRemoteDescription.
 | |||
|  |           pc._transceive(transceiver, | |||
|  |               params.codecs.length > 0, | |||
|  |               false); | |||
|  |         } | |||
|  |       }); | |||
|  |     } | |||
|  | 
 | |||
|  |     pc._localDescription = { | |||
|  |       type: description.type, | |||
|  |       sdp: description.sdp | |||
|  |     }; | |||
|  |     if (description.type === 'offer') { | |||
|  |       pc._updateSignalingState('have-local-offer'); | |||
|  |     } else { | |||
|  |       pc._updateSignalingState('stable'); | |||
|  |     } | |||
|  | 
 | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.setRemoteDescription = function(description) { | |||
|  |     var pc = this; | |||
|  | 
 | |||
|  |     // Note: pranswer is not supported.
 | |||
|  |     if (['offer', 'answer'].indexOf(description.type) === -1) { | |||
|  |       return Promise.reject(makeError('TypeError', | |||
|  |           'Unsupported type "' + description.type + '"')); | |||
|  |     } | |||
|  | 
 | |||
|  |     if (!isActionAllowedInSignalingState('setRemoteDescription', | |||
|  |         description.type, pc.signalingState) || pc._isClosed) { | |||
|  |       return Promise.reject(makeError('InvalidStateError', | |||
|  |           'Can not set remote ' + description.type + | |||
|  |           ' in state ' + pc.signalingState)); | |||
|  |     } | |||
|  | 
 | |||
|  |     var streams = {}; | |||
|  |     pc.remoteStreams.forEach(function(stream) { | |||
|  |       streams[stream.id] = stream; | |||
|  |     }); | |||
|  |     var receiverList = []; | |||
|  |     var sections = SDPUtils.splitSections(description.sdp); | |||
|  |     var sessionpart = sections.shift(); | |||
|  |     var isIceLite = SDPUtils.matchPrefix(sessionpart, | |||
|  |         'a=ice-lite').length > 0; | |||
|  |     var usingBundle = SDPUtils.matchPrefix(sessionpart, | |||
|  |         'a=group:BUNDLE ').length > 0; | |||
|  |     pc.usingBundle = usingBundle; | |||
|  |     var iceOptions = SDPUtils.matchPrefix(sessionpart, | |||
|  |         'a=ice-options:')[0]; | |||
|  |     if (iceOptions) { | |||
|  |       pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ') | |||
|  |           .indexOf('trickle') >= 0; | |||
|  |     } else { | |||
|  |       pc.canTrickleIceCandidates = false; | |||
|  |     } | |||
|  | 
 | |||
|  |     sections.forEach(function(mediaSection, sdpMLineIndex) { | |||
|  |       var lines = SDPUtils.splitLines(mediaSection); | |||
|  |       var kind = SDPUtils.getKind(mediaSection); | |||
|  |       // treat bundle-only as not-rejected.
 | |||
|  |       var rejected = SDPUtils.isRejected(mediaSection) && | |||
|  |           SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; | |||
|  |       var protocol = lines[0].substr(2).split(' ')[2]; | |||
|  | 
 | |||
|  |       var direction = SDPUtils.getDirection(mediaSection, sessionpart); | |||
|  |       var remoteMsid = SDPUtils.parseMsid(mediaSection); | |||
|  | 
 | |||
|  |       var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); | |||
|  | 
 | |||
|  |       // Reject datachannels which are not implemented yet.
 | |||
|  |       if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' || | |||
|  |           protocol === 'UDP/DTLS/SCTP'))) { | |||
|  |         // TODO: this is dangerous in the case where a non-rejected m-line
 | |||
|  |         //     becomes rejected.
 | |||
|  |         pc.transceivers[sdpMLineIndex] = { | |||
|  |           mid: mid, | |||
|  |           kind: kind, | |||
|  |           protocol: protocol, | |||
|  |           rejected: true | |||
|  |         }; | |||
|  |         return; | |||
|  |       } | |||
|  | 
 | |||
|  |       if (!rejected && pc.transceivers[sdpMLineIndex] && | |||
|  |           pc.transceivers[sdpMLineIndex].rejected) { | |||
|  |         // recycle a rejected transceiver.
 | |||
|  |         pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); | |||
|  |       } | |||
|  | 
 | |||
|  |       var transceiver; | |||
|  |       var iceGatherer; | |||
|  |       var iceTransport; | |||
|  |       var dtlsTransport; | |||
|  |       var rtpReceiver; | |||
|  |       var sendEncodingParameters; | |||
|  |       var recvEncodingParameters; | |||
|  |       var localCapabilities; | |||
|  | 
 | |||
|  |       var track; | |||
|  |       // FIXME: ensure the mediaSection has rtcp-mux set.
 | |||
|  |       var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); | |||
|  |       var remoteIceParameters; | |||
|  |       var remoteDtlsParameters; | |||
|  |       if (!rejected) { | |||
|  |         remoteIceParameters = SDPUtils.getIceParameters(mediaSection, | |||
|  |             sessionpart); | |||
|  |         remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, | |||
|  |             sessionpart); | |||
|  |         remoteDtlsParameters.role = 'client'; | |||
|  |       } | |||
|  |       recvEncodingParameters = | |||
|  |           SDPUtils.parseRtpEncodingParameters(mediaSection); | |||
|  | 
 | |||
|  |       var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); | |||
|  | 
 | |||
|  |       var isComplete = SDPUtils.matchPrefix(mediaSection, | |||
|  |           'a=end-of-candidates', sessionpart).length > 0; | |||
|  |       var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') | |||
|  |           .map(function(cand) { | |||
|  |             return SDPUtils.parseCandidate(cand); | |||
|  |           }) | |||
|  |           .filter(function(cand) { | |||
|  |             return cand.component === 1; | |||
|  |           }); | |||
|  | 
 | |||
|  |       // Check if we can use BUNDLE and dispose transports.
 | |||
|  |       if ((description.type === 'offer' || description.type === 'answer') && | |||
|  |           !rejected && usingBundle && sdpMLineIndex > 0 && | |||
|  |           pc.transceivers[sdpMLineIndex]) { | |||
|  |         pc._disposeIceAndDtlsTransports(sdpMLineIndex); | |||
|  |         pc.transceivers[sdpMLineIndex].iceGatherer = | |||
|  |             pc.transceivers[0].iceGatherer; | |||
|  |         pc.transceivers[sdpMLineIndex].iceTransport = | |||
|  |             pc.transceivers[0].iceTransport; | |||
|  |         pc.transceivers[sdpMLineIndex].dtlsTransport = | |||
|  |             pc.transceivers[0].dtlsTransport; | |||
|  |         if (pc.transceivers[sdpMLineIndex].rtpSender) { | |||
|  |           pc.transceivers[sdpMLineIndex].rtpSender.setTransport( | |||
|  |               pc.transceivers[0].dtlsTransport); | |||
|  |         } | |||
|  |         if (pc.transceivers[sdpMLineIndex].rtpReceiver) { | |||
|  |           pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( | |||
|  |               pc.transceivers[0].dtlsTransport); | |||
|  |         } | |||
|  |       } | |||
|  |       if (description.type === 'offer' && !rejected) { | |||
|  |         transceiver = pc.transceivers[sdpMLineIndex] || | |||
|  |             pc._createTransceiver(kind); | |||
|  |         transceiver.mid = mid; | |||
|  | 
 | |||
|  |         if (!transceiver.iceGatherer) { | |||
|  |           transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, | |||
|  |               usingBundle); | |||
|  |         } | |||
|  | 
 | |||
|  |         if (cands.length && transceiver.iceTransport.state === 'new') { | |||
|  |           if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { | |||
|  |             transceiver.iceTransport.setRemoteCandidates(cands); | |||
|  |           } else { | |||
|  |             cands.forEach(function(candidate) { | |||
|  |               maybeAddCandidate(transceiver.iceTransport, candidate); | |||
|  |             }); | |||
|  |           } | |||
|  |         } | |||
|  | 
 | |||
|  |         localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); | |||
|  | 
 | |||
|  |         // filter RTX until additional stuff needed for RTX is implemented
 | |||
|  |         // in adapter.js
 | |||
|  |         if (edgeVersion < 15019) { | |||
|  |           localCapabilities.codecs = localCapabilities.codecs.filter( | |||
|  |               function(codec) { | |||
|  |                 return codec.name !== 'rtx'; | |||
|  |               }); | |||
|  |         } | |||
|  | 
 | |||
|  |         sendEncodingParameters = transceiver.sendEncodingParameters || [{ | |||
|  |           ssrc: (2 * sdpMLineIndex + 2) * 1001 | |||
|  |         }]; | |||
|  | 
 | |||
|  |         // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
 | |||
|  |         var isNewTrack = false; | |||
|  |         if (direction === 'sendrecv' || direction === 'sendonly') { | |||
|  |           isNewTrack = !transceiver.rtpReceiver; | |||
|  |           rtpReceiver = transceiver.rtpReceiver || | |||
|  |               new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); | |||
|  | 
 | |||
|  |           if (isNewTrack) { | |||
|  |             var stream; | |||
|  |             track = rtpReceiver.track; | |||
|  |             // FIXME: does not work with Plan B.
 | |||
|  |             if (remoteMsid && remoteMsid.stream === '-') { | |||
|  |               // no-op. a stream id of '-' means: no associated stream.
 | |||
|  |             } else if (remoteMsid) { | |||
|  |               if (!streams[remoteMsid.stream]) { | |||
|  |                 streams[remoteMsid.stream] = new window.MediaStream(); | |||
|  |                 Object.defineProperty(streams[remoteMsid.stream], 'id', { | |||
|  |                   get: function() { | |||
|  |                     return remoteMsid.stream; | |||
|  |                   } | |||
|  |                 }); | |||
|  |               } | |||
|  |               Object.defineProperty(track, 'id', { | |||
|  |                 get: function() { | |||
|  |                   return remoteMsid.track; | |||
|  |                 } | |||
|  |               }); | |||
|  |               stream = streams[remoteMsid.stream]; | |||
|  |             } else { | |||
|  |               if (!streams.default) { | |||
|  |                 streams.default = new window.MediaStream(); | |||
|  |               } | |||
|  |               stream = streams.default; | |||
|  |             } | |||
|  |             if (stream) { | |||
|  |               addTrackToStreamAndFireEvent(track, stream); | |||
|  |               transceiver.associatedRemoteMediaStreams.push(stream); | |||
|  |             } | |||
|  |             receiverList.push([track, rtpReceiver, stream]); | |||
|  |           } | |||
|  |         } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { | |||
|  |           transceiver.associatedRemoteMediaStreams.forEach(function(s) { | |||
|  |             var nativeTrack = s.getTracks().find(function(t) { | |||
|  |               return t.id === transceiver.rtpReceiver.track.id; | |||
|  |             }); | |||
|  |             if (nativeTrack) { | |||
|  |               removeTrackFromStreamAndFireEvent(nativeTrack, s); | |||
|  |             } | |||
|  |           }); | |||
|  |           transceiver.associatedRemoteMediaStreams = []; | |||
|  |         } | |||
|  | 
 | |||
|  |         transceiver.localCapabilities = localCapabilities; | |||
|  |         transceiver.remoteCapabilities = remoteCapabilities; | |||
|  |         transceiver.rtpReceiver = rtpReceiver; | |||
|  |         transceiver.rtcpParameters = rtcpParameters; | |||
|  |         transceiver.sendEncodingParameters = sendEncodingParameters; | |||
|  |         transceiver.recvEncodingParameters = recvEncodingParameters; | |||
|  | 
 | |||
|  |         // Start the RTCRtpReceiver now. The RTPSender is started in
 | |||
|  |         // setLocalDescription.
 | |||
|  |         pc._transceive(pc.transceivers[sdpMLineIndex], | |||
|  |             false, | |||
|  |             isNewTrack); | |||
|  |       } else if (description.type === 'answer' && !rejected) { | |||
|  |         transceiver = pc.transceivers[sdpMLineIndex]; | |||
|  |         iceGatherer = transceiver.iceGatherer; | |||
|  |         iceTransport = transceiver.iceTransport; | |||
|  |         dtlsTransport = transceiver.dtlsTransport; | |||
|  |         rtpReceiver = transceiver.rtpReceiver; | |||
|  |         sendEncodingParameters = transceiver.sendEncodingParameters; | |||
|  |         localCapabilities = transceiver.localCapabilities; | |||
|  | 
 | |||
|  |         pc.transceivers[sdpMLineIndex].recvEncodingParameters = | |||
|  |             recvEncodingParameters; | |||
|  |         pc.transceivers[sdpMLineIndex].remoteCapabilities = | |||
|  |             remoteCapabilities; | |||
|  |         pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; | |||
|  | 
 | |||
|  |         if (cands.length && iceTransport.state === 'new') { | |||
|  |           if ((isIceLite || isComplete) && | |||
|  |               (!usingBundle || sdpMLineIndex === 0)) { | |||
|  |             iceTransport.setRemoteCandidates(cands); | |||
|  |           } else { | |||
|  |             cands.forEach(function(candidate) { | |||
|  |               maybeAddCandidate(transceiver.iceTransport, candidate); | |||
|  |             }); | |||
|  |           } | |||
|  |         } | |||
|  | 
 | |||
|  |         if (!usingBundle || sdpMLineIndex === 0) { | |||
|  |           if (iceTransport.state === 'new') { | |||
|  |             iceTransport.start(iceGatherer, remoteIceParameters, | |||
|  |                 'controlling'); | |||
|  |           } | |||
|  |           if (dtlsTransport.state === 'new') { | |||
|  |             dtlsTransport.start(remoteDtlsParameters); | |||
|  |           } | |||
|  |         } | |||
|  | 
 | |||
|  |         // If the offer contained RTX but the answer did not,
 | |||
|  |         // remove RTX from sendEncodingParameters.
 | |||
|  |         var commonCapabilities = getCommonCapabilities( | |||
|  |           transceiver.localCapabilities, | |||
|  |           transceiver.remoteCapabilities); | |||
|  | 
 | |||
|  |         var hasRtx = commonCapabilities.codecs.filter(function(c) { | |||
|  |           return c.name.toLowerCase() === 'rtx'; | |||
|  |         }).length; | |||
|  |         if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { | |||
|  |           delete transceiver.sendEncodingParameters[0].rtx; | |||
|  |         } | |||
|  | 
 | |||
|  |         pc._transceive(transceiver, | |||
|  |             direction === 'sendrecv' || direction === 'recvonly', | |||
|  |             direction === 'sendrecv' || direction === 'sendonly'); | |||
|  | 
 | |||
|  |         // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
 | |||
|  |         if (rtpReceiver && | |||
|  |             (direction === 'sendrecv' || direction === 'sendonly')) { | |||
|  |           track = rtpReceiver.track; | |||
|  |           if (remoteMsid) { | |||
|  |             if (!streams[remoteMsid.stream]) { | |||
|  |               streams[remoteMsid.stream] = new window.MediaStream(); | |||
|  |             } | |||
|  |             addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); | |||
|  |             receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); | |||
|  |           } else { | |||
|  |             if (!streams.default) { | |||
|  |               streams.default = new window.MediaStream(); | |||
|  |             } | |||
|  |             addTrackToStreamAndFireEvent(track, streams.default); | |||
|  |             receiverList.push([track, rtpReceiver, streams.default]); | |||
|  |           } | |||
|  |         } else { | |||
|  |           // FIXME: actually the receiver should be created later.
 | |||
|  |           delete transceiver.rtpReceiver; | |||
|  |         } | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     if (pc._dtlsRole === undefined) { | |||
|  |       pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; | |||
|  |     } | |||
|  | 
 | |||
|  |     pc._remoteDescription = { | |||
|  |       type: description.type, | |||
|  |       sdp: description.sdp | |||
|  |     }; | |||
|  |     if (description.type === 'offer') { | |||
|  |       pc._updateSignalingState('have-remote-offer'); | |||
|  |     } else { | |||
|  |       pc._updateSignalingState('stable'); | |||
|  |     } | |||
|  |     Object.keys(streams).forEach(function(sid) { | |||
|  |       var stream = streams[sid]; | |||
|  |       if (stream.getTracks().length) { | |||
|  |         if (pc.remoteStreams.indexOf(stream) === -1) { | |||
|  |           pc.remoteStreams.push(stream); | |||
|  |           var event = new Event('addstream'); | |||
|  |           event.stream = stream; | |||
|  |           window.setTimeout(function() { | |||
|  |             pc._dispatchEvent('addstream', event); | |||
|  |           }); | |||
|  |         } | |||
|  | 
 | |||
|  |         receiverList.forEach(function(item) { | |||
|  |           var track = item[0]; | |||
|  |           var receiver = item[1]; | |||
|  |           if (stream.id !== item[2].id) { | |||
|  |             return; | |||
|  |           } | |||
|  |           fireAddTrack(pc, track, receiver, [stream]); | |||
|  |         }); | |||
|  |       } | |||
|  |     }); | |||
|  |     receiverList.forEach(function(item) { | |||
|  |       if (item[2]) { | |||
|  |         return; | |||
|  |       } | |||
|  |       fireAddTrack(pc, item[0], item[1], []); | |||
|  |     }); | |||
|  | 
 | |||
|  |     // check whether addIceCandidate({}) was called within four seconds after
 | |||
|  |     // setRemoteDescription.
 | |||
|  |     window.setTimeout(function() { | |||
|  |       if (!(pc && pc.transceivers)) { | |||
|  |         return; | |||
|  |       } | |||
|  |       pc.transceivers.forEach(function(transceiver) { | |||
|  |         if (transceiver.iceTransport && | |||
|  |             transceiver.iceTransport.state === 'new' && | |||
|  |             transceiver.iceTransport.getRemoteCandidates().length > 0) { | |||
|  |           console.warn('Timeout for addRemoteCandidate. Consider sending ' + | |||
|  |               'an end-of-candidates notification'); | |||
|  |           transceiver.iceTransport.addRemoteCandidate({}); | |||
|  |         } | |||
|  |       }); | |||
|  |     }, 4000); | |||
|  | 
 | |||
|  |     return Promise.resolve(); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.close = function() { | |||
|  |     this.transceivers.forEach(function(transceiver) { | |||
|  |       /* not yet | |||
|  |       if (transceiver.iceGatherer) { | |||
|  |         transceiver.iceGatherer.close(); | |||
|  |       } | |||
|  |       */ | |||
|  |       if (transceiver.iceTransport) { | |||
|  |         transceiver.iceTransport.stop(); | |||
|  |       } | |||
|  |       if (transceiver.dtlsTransport) { | |||
|  |         transceiver.dtlsTransport.stop(); | |||
|  |       } | |||
|  |       if (transceiver.rtpSender) { | |||
|  |         transceiver.rtpSender.stop(); | |||
|  |       } | |||
|  |       if (transceiver.rtpReceiver) { | |||
|  |         transceiver.rtpReceiver.stop(); | |||
|  |       } | |||
|  |     }); | |||
|  |     // FIXME: clean up tracks, local streams, remote streams, etc
 | |||
|  |     this._isClosed = true; | |||
|  |     this._updateSignalingState('closed'); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Update the signaling state.
 | |||
|  |   RTCPeerConnection.prototype._updateSignalingState = function(newState) { | |||
|  |     this.signalingState = newState; | |||
|  |     var event = new Event('signalingstatechange'); | |||
|  |     this._dispatchEvent('signalingstatechange', event); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Determine whether to fire the negotiationneeded event.
 | |||
|  |   RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { | |||
|  |     var pc = this; | |||
|  |     if (this.signalingState !== 'stable' || this.needNegotiation === true) { | |||
|  |       return; | |||
|  |     } | |||
|  |     this.needNegotiation = true; | |||
|  |     window.setTimeout(function() { | |||
|  |       if (pc.needNegotiation) { | |||
|  |         pc.needNegotiation = false; | |||
|  |         var event = new Event('negotiationneeded'); | |||
|  |         pc._dispatchEvent('negotiationneeded', event); | |||
|  |       } | |||
|  |     }, 0); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Update the ice connection state.
 | |||
|  |   RTCPeerConnection.prototype._updateIceConnectionState = function() { | |||
|  |     var newState; | |||
|  |     var states = { | |||
|  |       'new': 0, | |||
|  |       closed: 0, | |||
|  |       checking: 0, | |||
|  |       connected: 0, | |||
|  |       completed: 0, | |||
|  |       disconnected: 0, | |||
|  |       failed: 0 | |||
|  |     }; | |||
|  |     this.transceivers.forEach(function(transceiver) { | |||
|  |       if (transceiver.iceTransport && !transceiver.rejected) { | |||
|  |         states[transceiver.iceTransport.state]++; | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     newState = 'new'; | |||
|  |     if (states.failed > 0) { | |||
|  |       newState = 'failed'; | |||
|  |     } else if (states.checking > 0) { | |||
|  |       newState = 'checking'; | |||
|  |     } else if (states.disconnected > 0) { | |||
|  |       newState = 'disconnected'; | |||
|  |     } else if (states.new > 0) { | |||
|  |       newState = 'new'; | |||
|  |     } else if (states.connected > 0) { | |||
|  |       newState = 'connected'; | |||
|  |     } else if (states.completed > 0) { | |||
|  |       newState = 'completed'; | |||
|  |     } | |||
|  | 
 | |||
|  |     if (newState !== this.iceConnectionState) { | |||
|  |       this.iceConnectionState = newState; | |||
|  |       var event = new Event('iceconnectionstatechange'); | |||
|  |       this._dispatchEvent('iceconnectionstatechange', event); | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   // Update the connection state.
 | |||
|  |   RTCPeerConnection.prototype._updateConnectionState = function() { | |||
|  |     var newState; | |||
|  |     var states = { | |||
|  |       'new': 0, | |||
|  |       closed: 0, | |||
|  |       connecting: 0, | |||
|  |       connected: 0, | |||
|  |       completed: 0, | |||
|  |       disconnected: 0, | |||
|  |       failed: 0 | |||
|  |     }; | |||
|  |     this.transceivers.forEach(function(transceiver) { | |||
|  |       if (transceiver.iceTransport && transceiver.dtlsTransport && | |||
|  |           !transceiver.rejected) { | |||
|  |         states[transceiver.iceTransport.state]++; | |||
|  |         states[transceiver.dtlsTransport.state]++; | |||
|  |       } | |||
|  |     }); | |||
|  |     // ICETransport.completed and connected are the same for this purpose.
 | |||
|  |     states.connected += states.completed; | |||
|  | 
 | |||
|  |     newState = 'new'; | |||
|  |     if (states.failed > 0) { | |||
|  |       newState = 'failed'; | |||
|  |     } else if (states.connecting > 0) { | |||
|  |       newState = 'connecting'; | |||
|  |     } else if (states.disconnected > 0) { | |||
|  |       newState = 'disconnected'; | |||
|  |     } else if (states.new > 0) { | |||
|  |       newState = 'new'; | |||
|  |     } else if (states.connected > 0) { | |||
|  |       newState = 'connected'; | |||
|  |     } | |||
|  | 
 | |||
|  |     if (newState !== this.connectionState) { | |||
|  |       this.connectionState = newState; | |||
|  |       var event = new Event('connectionstatechange'); | |||
|  |       this._dispatchEvent('connectionstatechange', event); | |||
|  |     } | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.createOffer = function() { | |||
|  |     var pc = this; | |||
|  | 
 | |||
|  |     if (pc._isClosed) { | |||
|  |       return Promise.reject(makeError('InvalidStateError', | |||
|  |           'Can not call createOffer after close')); | |||
|  |     } | |||
|  | 
 | |||
|  |     var numAudioTracks = pc.transceivers.filter(function(t) { | |||
|  |       return t.kind === 'audio'; | |||
|  |     }).length; | |||
|  |     var numVideoTracks = pc.transceivers.filter(function(t) { | |||
|  |       return t.kind === 'video'; | |||
|  |     }).length; | |||
|  | 
 | |||
|  |     // Determine number of audio and video tracks we need to send/recv.
 | |||
|  |     var offerOptions = arguments[0]; | |||
|  |     if (offerOptions) { | |||
|  |       // Reject Chrome legacy constraints.
 | |||
|  |       if (offerOptions.mandatory || offerOptions.optional) { | |||
|  |         throw new TypeError( | |||
|  |             'Legacy mandatory/optional constraints not supported.'); | |||
|  |       } | |||
|  |       if (offerOptions.offerToReceiveAudio !== undefined) { | |||
|  |         if (offerOptions.offerToReceiveAudio === true) { | |||
|  |           numAudioTracks = 1; | |||
|  |         } else if (offerOptions.offerToReceiveAudio === false) { | |||
|  |           numAudioTracks = 0; | |||
|  |         } else { | |||
|  |           numAudioTracks = offerOptions.offerToReceiveAudio; | |||
|  |         } | |||
|  |       } | |||
|  |       if (offerOptions.offerToReceiveVideo !== undefined) { | |||
|  |         if (offerOptions.offerToReceiveVideo === true) { | |||
|  |           numVideoTracks = 1; | |||
|  |         } else if (offerOptions.offerToReceiveVideo === false) { | |||
|  |           numVideoTracks = 0; | |||
|  |         } else { | |||
|  |           numVideoTracks = offerOptions.offerToReceiveVideo; | |||
|  |         } | |||
|  |       } | |||
|  |     } | |||
|  | 
 | |||
|  |     pc.transceivers.forEach(function(transceiver) { | |||
|  |       if (transceiver.kind === 'audio') { | |||
|  |         numAudioTracks--; | |||
|  |         if (numAudioTracks < 0) { | |||
|  |           transceiver.wantReceive = false; | |||
|  |         } | |||
|  |       } else if (transceiver.kind === 'video') { | |||
|  |         numVideoTracks--; | |||
|  |         if (numVideoTracks < 0) { | |||
|  |           transceiver.wantReceive = false; | |||
|  |         } | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     // Create M-lines for recvonly streams.
 | |||
|  |     while (numAudioTracks > 0 || numVideoTracks > 0) { | |||
|  |       if (numAudioTracks > 0) { | |||
|  |         pc._createTransceiver('audio'); | |||
|  |         numAudioTracks--; | |||
|  |       } | |||
|  |       if (numVideoTracks > 0) { | |||
|  |         pc._createTransceiver('video'); | |||
|  |         numVideoTracks--; | |||
|  |       } | |||
|  |     } | |||
|  | 
 | |||
|  |     var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, | |||
|  |         pc._sdpSessionVersion++); | |||
|  |     pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { | |||
|  |       // For each track, create an ice gatherer, ice transport,
 | |||
|  |       // dtls transport, potentially rtpsender and rtpreceiver.
 | |||
|  |       var track = transceiver.track; | |||
|  |       var kind = transceiver.kind; | |||
|  |       var mid = transceiver.mid || SDPUtils.generateIdentifier(); | |||
|  |       transceiver.mid = mid; | |||
|  | 
 | |||
|  |       if (!transceiver.iceGatherer) { | |||
|  |         transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, | |||
|  |             pc.usingBundle); | |||
|  |       } | |||
|  | 
 | |||
|  |       var localCapabilities = window.RTCRtpSender.getCapabilities(kind); | |||
|  |       // filter RTX until additional stuff needed for RTX is implemented
 | |||
|  |       // in adapter.js
 | |||
|  |       if (edgeVersion < 15019) { | |||
|  |         localCapabilities.codecs = localCapabilities.codecs.filter( | |||
|  |             function(codec) { | |||
|  |               return codec.name !== 'rtx'; | |||
|  |             }); | |||
|  |       } | |||
|  |       localCapabilities.codecs.forEach(function(codec) { | |||
|  |         // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
 | |||
|  |         // by adding level-asymmetry-allowed=1
 | |||
|  |         if (codec.name === 'H264' && | |||
|  |             codec.parameters['level-asymmetry-allowed'] === undefined) { | |||
|  |           codec.parameters['level-asymmetry-allowed'] = '1'; | |||
|  |         } | |||
|  | 
 | |||
|  |         // for subsequent offers, we might have to re-use the payload
 | |||
|  |         // type of the last offer.
 | |||
|  |         if (transceiver.remoteCapabilities && | |||
|  |             transceiver.remoteCapabilities.codecs) { | |||
|  |           transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) { | |||
|  |             if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && | |||
|  |                 codec.clockRate === remoteCodec.clockRate) { | |||
|  |               codec.preferredPayloadType = remoteCodec.payloadType; | |||
|  |             } | |||
|  |           }); | |||
|  |         } | |||
|  |       }); | |||
|  |       localCapabilities.headerExtensions.forEach(function(hdrExt) { | |||
|  |         var remoteExtensions = transceiver.remoteCapabilities && | |||
|  |             transceiver.remoteCapabilities.headerExtensions || []; | |||
|  |         remoteExtensions.forEach(function(rHdrExt) { | |||
|  |           if (hdrExt.uri === rHdrExt.uri) { | |||
|  |             hdrExt.id = rHdrExt.id; | |||
|  |           } | |||
|  |         }); | |||
|  |       }); | |||
|  | 
 | |||
|  |       // generate an ssrc now, to be used later in rtpSender.send
 | |||
|  |       var sendEncodingParameters = transceiver.sendEncodingParameters || [{ | |||
|  |         ssrc: (2 * sdpMLineIndex + 1) * 1001 | |||
|  |       }]; | |||
|  |       if (track) { | |||
|  |         // add RTX
 | |||
|  |         if (edgeVersion >= 15019 && kind === 'video' && | |||
|  |             !sendEncodingParameters[0].rtx) { | |||
|  |           sendEncodingParameters[0].rtx = { | |||
|  |             ssrc: sendEncodingParameters[0].ssrc + 1 | |||
|  |           }; | |||
|  |         } | |||
|  |       } | |||
|  | 
 | |||
|  |       if (transceiver.wantReceive) { | |||
|  |         transceiver.rtpReceiver = new window.RTCRtpReceiver( | |||
|  |             transceiver.dtlsTransport, kind); | |||
|  |       } | |||
|  | 
 | |||
|  |       transceiver.localCapabilities = localCapabilities; | |||
|  |       transceiver.sendEncodingParameters = sendEncodingParameters; | |||
|  |     }); | |||
|  | 
 | |||
|  |     // always offer BUNDLE and dispose on return if not supported.
 | |||
|  |     if (pc._config.bundlePolicy !== 'max-compat') { | |||
|  |       sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { | |||
|  |         return t.mid; | |||
|  |       }).join(' ') + '\r\n'; | |||
|  |     } | |||
|  |     sdp += 'a=ice-options:trickle\r\n'; | |||
|  | 
 | |||
|  |     pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { | |||
|  |       sdp += writeMediaSection(transceiver, transceiver.localCapabilities, | |||
|  |           'offer', transceiver.stream, pc._dtlsRole); | |||
|  |       sdp += 'a=rtcp-rsize\r\n'; | |||
|  | 
 | |||
|  |       if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && | |||
|  |           (sdpMLineIndex === 0 || !pc.usingBundle)) { | |||
|  |         transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { | |||
|  |           cand.component = 1; | |||
|  |           sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; | |||
|  |         }); | |||
|  | 
 | |||
|  |         if (transceiver.iceGatherer.state === 'completed') { | |||
|  |           sdp += 'a=end-of-candidates\r\n'; | |||
|  |         } | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     var desc = new window.RTCSessionDescription({ | |||
|  |       type: 'offer', | |||
|  |       sdp: sdp | |||
|  |     }); | |||
|  |     return Promise.resolve(desc); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.createAnswer = function() { | |||
|  |     var pc = this; | |||
|  | 
 | |||
|  |     if (pc._isClosed) { | |||
|  |       return Promise.reject(makeError('InvalidStateError', | |||
|  |           'Can not call createAnswer after close')); | |||
|  |     } | |||
|  | 
 | |||
|  |     if (!(pc.signalingState === 'have-remote-offer' || | |||
|  |         pc.signalingState === 'have-local-pranswer')) { | |||
|  |       return Promise.reject(makeError('InvalidStateError', | |||
|  |           'Can not call createAnswer in signalingState ' + pc.signalingState)); | |||
|  |     } | |||
|  | 
 | |||
|  |     var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, | |||
|  |         pc._sdpSessionVersion++); | |||
|  |     if (pc.usingBundle) { | |||
|  |       sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { | |||
|  |         return t.mid; | |||
|  |       }).join(' ') + '\r\n'; | |||
|  |     } | |||
|  |     sdp += 'a=ice-options:trickle\r\n'; | |||
|  | 
 | |||
|  |     var mediaSectionsInOffer = SDPUtils.getMediaSections( | |||
|  |         pc._remoteDescription.sdp).length; | |||
|  |     pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { | |||
|  |       if (sdpMLineIndex + 1 > mediaSectionsInOffer) { | |||
|  |         return; | |||
|  |       } | |||
|  |       if (transceiver.rejected) { | |||
|  |         if (transceiver.kind === 'application') { | |||
|  |           if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt
 | |||
|  |             sdp += 'm=application 0 DTLS/SCTP 5000\r\n'; | |||
|  |           } else { | |||
|  |             sdp += 'm=application 0 ' + transceiver.protocol + | |||
|  |                 ' webrtc-datachannel\r\n'; | |||
|  |           } | |||
|  |         } else if (transceiver.kind === 'audio') { | |||
|  |           sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + | |||
|  |               'a=rtpmap:0 PCMU/8000\r\n'; | |||
|  |         } else if (transceiver.kind === 'video') { | |||
|  |           sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + | |||
|  |               'a=rtpmap:120 VP8/90000\r\n'; | |||
|  |         } | |||
|  |         sdp += 'c=IN IP4 0.0.0.0\r\n' + | |||
|  |             'a=inactive\r\n' + | |||
|  |             'a=mid:' + transceiver.mid + '\r\n'; | |||
|  |         return; | |||
|  |       } | |||
|  | 
 | |||
|  |       // FIXME: look at direction.
 | |||
|  |       if (transceiver.stream) { | |||
|  |         var localTrack; | |||
|  |         if (transceiver.kind === 'audio') { | |||
|  |           localTrack = transceiver.stream.getAudioTracks()[0]; | |||
|  |         } else if (transceiver.kind === 'video') { | |||
|  |           localTrack = transceiver.stream.getVideoTracks()[0]; | |||
|  |         } | |||
|  |         if (localTrack) { | |||
|  |           // add RTX
 | |||
|  |           if (edgeVersion >= 15019 && transceiver.kind === 'video' && | |||
|  |               !transceiver.sendEncodingParameters[0].rtx) { | |||
|  |             transceiver.sendEncodingParameters[0].rtx = { | |||
|  |               ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 | |||
|  |             }; | |||
|  |           } | |||
|  |         } | |||
|  |       } | |||
|  | 
 | |||
|  |       // Calculate intersection of capabilities.
 | |||
|  |       var commonCapabilities = getCommonCapabilities( | |||
|  |           transceiver.localCapabilities, | |||
|  |           transceiver.remoteCapabilities); | |||
|  | 
 | |||
|  |       var hasRtx = commonCapabilities.codecs.filter(function(c) { | |||
|  |         return c.name.toLowerCase() === 'rtx'; | |||
|  |       }).length; | |||
|  |       if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { | |||
|  |         delete transceiver.sendEncodingParameters[0].rtx; | |||
|  |       } | |||
|  | 
 | |||
|  |       sdp += writeMediaSection(transceiver, commonCapabilities, | |||
|  |           'answer', transceiver.stream, pc._dtlsRole); | |||
|  |       if (transceiver.rtcpParameters && | |||
|  |           transceiver.rtcpParameters.reducedSize) { | |||
|  |         sdp += 'a=rtcp-rsize\r\n'; | |||
|  |       } | |||
|  |     }); | |||
|  | 
 | |||
|  |     var desc = new window.RTCSessionDescription({ | |||
|  |       type: 'answer', | |||
|  |       sdp: sdp | |||
|  |     }); | |||
|  |     return Promise.resolve(desc); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.addIceCandidate = function(candidate) { | |||
|  |     var pc = this; | |||
|  |     var sections; | |||
|  |     if (candidate && !(candidate.sdpMLineIndex !== undefined || | |||
|  |         candidate.sdpMid)) { | |||
|  |       return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); | |||
|  |     } | |||
|  | 
 | |||
|  |     // TODO: needs to go into ops queue.
 | |||
|  |     return new Promise(function(resolve, reject) { | |||
|  |       if (!pc._remoteDescription) { | |||
|  |         return reject(makeError('InvalidStateError', | |||
|  |             'Can not add ICE candidate without a remote description')); | |||
|  |       } else if (!candidate || candidate.candidate === '') { | |||
|  |         for (var j = 0; j < pc.transceivers.length; j++) { | |||
|  |           if (pc.transceivers[j].rejected) { | |||
|  |             continue; | |||
|  |           } | |||
|  |           pc.transceivers[j].iceTransport.addRemoteCandidate({}); | |||
|  |           sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); | |||
|  |           sections[j] += 'a=end-of-candidates\r\n'; | |||
|  |           pc._remoteDescription.sdp = | |||
|  |               SDPUtils.getDescription(pc._remoteDescription.sdp) + | |||
|  |               sections.join(''); | |||
|  |           if (pc.usingBundle) { | |||
|  |             break; | |||
|  |           } | |||
|  |         } | |||
|  |       } else { | |||
|  |         var sdpMLineIndex = candidate.sdpMLineIndex; | |||
|  |         if (candidate.sdpMid) { | |||
|  |           for (var i = 0; i < pc.transceivers.length; i++) { | |||
|  |             if (pc.transceivers[i].mid === candidate.sdpMid) { | |||
|  |               sdpMLineIndex = i; | |||
|  |               break; | |||
|  |             } | |||
|  |           } | |||
|  |         } | |||
|  |         var transceiver = pc.transceivers[sdpMLineIndex]; | |||
|  |         if (transceiver) { | |||
|  |           if (transceiver.rejected) { | |||
|  |             return resolve(); | |||
|  |           } | |||
|  |           var cand = Object.keys(candidate.candidate).length > 0 ? | |||
|  |               SDPUtils.parseCandidate(candidate.candidate) : {}; | |||
|  |           // Ignore Chrome's invalid candidates since Edge does not like them.
 | |||
|  |           if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { | |||
|  |             return resolve(); | |||
|  |           } | |||
|  |           // Ignore RTCP candidates, we assume RTCP-MUX.
 | |||
|  |           if (cand.component && cand.component !== 1) { | |||
|  |             return resolve(); | |||
|  |           } | |||
|  |           // when using bundle, avoid adding candidates to the wrong
 | |||
|  |           // ice transport. And avoid adding candidates added in the SDP.
 | |||
|  |           if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && | |||
|  |               transceiver.iceTransport !== pc.transceivers[0].iceTransport)) { | |||
|  |             if (!maybeAddCandidate(transceiver.iceTransport, cand)) { | |||
|  |               return reject(makeError('OperationError', | |||
|  |                   'Can not add ICE candidate')); | |||
|  |             } | |||
|  |           } | |||
|  | 
 | |||
|  |           // update the remoteDescription.
 | |||
|  |           var candidateString = candidate.candidate.trim(); | |||
|  |           if (candidateString.indexOf('a=') === 0) { | |||
|  |             candidateString = candidateString.substr(2); | |||
|  |           } | |||
|  |           sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); | |||
|  |           sections[sdpMLineIndex] += 'a=' + | |||
|  |               (cand.type ? candidateString : 'end-of-candidates') | |||
|  |               + '\r\n'; | |||
|  |           pc._remoteDescription.sdp = | |||
|  |               SDPUtils.getDescription(pc._remoteDescription.sdp) + | |||
|  |               sections.join(''); | |||
|  |         } else { | |||
|  |           return reject(makeError('OperationError', | |||
|  |               'Can not add ICE candidate')); | |||
|  |         } | |||
|  |       } | |||
|  |       resolve(); | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   RTCPeerConnection.prototype.getStats = function(selector) { | |||
|  |     if (selector && selector instanceof window.MediaStreamTrack) { | |||
|  |       var senderOrReceiver = null; | |||
|  |       this.transceivers.forEach(function(transceiver) { | |||
|  |         if (transceiver.rtpSender && | |||
|  |             transceiver.rtpSender.track === selector) { | |||
|  |           senderOrReceiver = transceiver.rtpSender; | |||
|  |         } else if (transceiver.rtpReceiver && | |||
|  |             transceiver.rtpReceiver.track === selector) { | |||
|  |           senderOrReceiver = transceiver.rtpReceiver; | |||
|  |         } | |||
|  |       }); | |||
|  |       if (!senderOrReceiver) { | |||
|  |         throw makeError('InvalidAccessError', 'Invalid selector.'); | |||
|  |       } | |||
|  |       return senderOrReceiver.getStats(); | |||
|  |     } | |||
|  | 
 | |||
|  |     var promises = []; | |||
|  |     this.transceivers.forEach(function(transceiver) { | |||
|  |       ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', | |||
|  |           'dtlsTransport'].forEach(function(method) { | |||
|  |             if (transceiver[method]) { | |||
|  |               promises.push(transceiver[method].getStats()); | |||
|  |             } | |||
|  |           }); | |||
|  |     }); | |||
|  |     return Promise.all(promises).then(function(allStats) { | |||
|  |       var results = new Map(); | |||
|  |       allStats.forEach(function(stats) { | |||
|  |         stats.forEach(function(stat) { | |||
|  |           results.set(stat.id, stat); | |||
|  |         }); | |||
|  |       }); | |||
|  |       return results; | |||
|  |     }); | |||
|  |   }; | |||
|  | 
 | |||
|  |   // fix low-level stat names and return Map instead of object.
 | |||
|  |   var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', | |||
|  |     'RTCIceTransport', 'RTCDtlsTransport']; | |||
|  |   ortcObjects.forEach(function(ortcObjectName) { | |||
|  |     var obj = window[ortcObjectName]; | |||
|  |     if (obj && obj.prototype && obj.prototype.getStats) { | |||
|  |       var nativeGetstats = obj.prototype.getStats; | |||
|  |       obj.prototype.getStats = function() { | |||
|  |         return nativeGetstats.apply(this) | |||
|  |         .then(function(nativeStats) { | |||
|  |           var mapStats = new Map(); | |||
|  |           Object.keys(nativeStats).forEach(function(id) { | |||
|  |             nativeStats[id].type = fixStatsType(nativeStats[id]); | |||
|  |             mapStats.set(id, nativeStats[id]); | |||
|  |           }); | |||
|  |           return mapStats; | |||
|  |         }); | |||
|  |       }; | |||
|  |     } | |||
|  |   }); | |||
|  | 
 | |||
|  |   // legacy callback shims. Should be moved to adapter.js some days.
 | |||
|  |   var methods = ['createOffer', 'createAnswer']; | |||
|  |   methods.forEach(function(method) { | |||
|  |     var nativeMethod = RTCPeerConnection.prototype[method]; | |||
|  |     RTCPeerConnection.prototype[method] = function() { | |||
|  |       var args = arguments; | |||
|  |       if (typeof args[0] === 'function' || | |||
|  |           typeof args[1] === 'function') { // legacy
 | |||
|  |         return nativeMethod.apply(this, [arguments[2]]) | |||
|  |         .then(function(description) { | |||
|  |           if (typeof args[0] === 'function') { | |||
|  |             args[0].apply(null, [description]); | |||
|  |           } | |||
|  |         }, function(error) { | |||
|  |           if (typeof args[1] === 'function') { | |||
|  |             args[1].apply(null, [error]); | |||
|  |           } | |||
|  |         }); | |||
|  |       } | |||
|  |       return nativeMethod.apply(this, arguments); | |||
|  |     }; | |||
|  |   }); | |||
|  | 
 | |||
|  |   methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; | |||
|  |   methods.forEach(function(method) { | |||
|  |     var nativeMethod = RTCPeerConnection.prototype[method]; | |||
|  |     RTCPeerConnection.prototype[method] = function() { | |||
|  |       var args = arguments; | |||
|  |       if (typeof args[1] === 'function' || | |||
|  |           typeof args[2] === 'function') { // legacy
 | |||
|  |         return nativeMethod.apply(this, arguments) | |||
|  |         .then(function() { | |||
|  |           if (typeof args[1] === 'function') { | |||
|  |             args[1].apply(null); | |||
|  |           } | |||
|  |         }, function(error) { | |||
|  |           if (typeof args[2] === 'function') { | |||
|  |             args[2].apply(null, [error]); | |||
|  |           } | |||
|  |         }); | |||
|  |       } | |||
|  |       return nativeMethod.apply(this, arguments); | |||
|  |     }; | |||
|  |   }); | |||
|  | 
 | |||
|  |   // getStats is special. It doesn't have a spec legacy method yet we support
 | |||
|  |   // getStats(something, cb) without error callbacks.
 | |||
|  |   ['getStats'].forEach(function(method) { | |||
|  |     var nativeMethod = RTCPeerConnection.prototype[method]; | |||
|  |     RTCPeerConnection.prototype[method] = function() { | |||
|  |       var args = arguments; | |||
|  |       if (typeof args[1] === 'function') { | |||
|  |         return nativeMethod.apply(this, arguments) | |||
|  |         .then(function() { | |||
|  |           if (typeof args[1] === 'function') { | |||
|  |             args[1].apply(null); | |||
|  |           } | |||
|  |         }); | |||
|  |       } | |||
|  |       return nativeMethod.apply(this, arguments); | |||
|  |     }; | |||
|  |   }); | |||
|  | 
 | |||
|  |   return RTCPeerConnection; | |||
|  | }; | |||
|  | 
 | |||
|  | },{"sdp":17}],17:[function(require,module,exports){ | |||
|  | /* eslint-env node */ | |||
|  | 'use strict'; | |||
|  | 
 | |||
|  | // SDP helpers.
 | |||
|  | var SDPUtils = {}; | |||
|  | 
 | |||
|  | // Generate an alphanumeric identifier for cname or mids.
 | |||
|  | // TODO: use UUIDs instead? https://gist.github.com/jed/982883
 | |||
|  | SDPUtils.generateIdentifier = function() { | |||
|  |   return Math.random().toString(36).substr(2, 10); | |||
|  | }; | |||
|  | 
 | |||
|  | // The RTCP CNAME used by all peerconnections from the same JS.
 | |||
|  | SDPUtils.localCName = SDPUtils.generateIdentifier(); | |||
|  | 
 | |||
|  | // Splits SDP into lines, dealing with both CRLF and LF.
 | |||
|  | SDPUtils.splitLines = function(blob) { | |||
|  |   return blob.trim().split('\n').map(function(line) { | |||
|  |     return line.trim(); | |||
|  |   }); | |||
|  | }; | |||
|  | // Splits SDP into sessionpart and mediasections. Ensures CRLF.
 | |||
|  | SDPUtils.splitSections = function(blob) { | |||
|  |   var parts = blob.split('\nm='); | |||
|  |   return parts.map(function(part, index) { | |||
|  |     return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; | |||
|  |   }); | |||
|  | }; | |||
|  | 
 | |||
|  | // returns the session description.
 | |||
|  | SDPUtils.getDescription = function(blob) { | |||
|  |   var sections = SDPUtils.splitSections(blob); | |||
|  |   return sections && sections[0]; | |||
|  | }; | |||
|  | 
 | |||
|  | // returns the individual media sections.
 | |||
|  | SDPUtils.getMediaSections = function(blob) { | |||
|  |   var sections = SDPUtils.splitSections(blob); | |||
|  |   sections.shift(); | |||
|  |   return sections; | |||
|  | }; | |||
|  | 
 | |||
|  | // Returns lines that start with a certain prefix.
 | |||
|  | SDPUtils.matchPrefix = function(blob, prefix) { | |||
|  |   return SDPUtils.splitLines(blob).filter(function(line) { | |||
|  |     return line.indexOf(prefix) === 0; | |||
|  |   }); | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an ICE candidate line. Sample input:
 | |||
|  | // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
 | |||
|  | // rport 55996"
 | |||
|  | SDPUtils.parseCandidate = function(line) { | |||
|  |   var parts; | |||
|  |   // Parse both variants.
 | |||
|  |   if (line.indexOf('a=candidate:') === 0) { | |||
|  |     parts = line.substring(12).split(' '); | |||
|  |   } else { | |||
|  |     parts = line.substring(10).split(' '); | |||
|  |   } | |||
|  | 
 | |||
|  |   var candidate = { | |||
|  |     foundation: parts[0], | |||
|  |     component: parseInt(parts[1], 10), | |||
|  |     protocol: parts[2].toLowerCase(), | |||
|  |     priority: parseInt(parts[3], 10), | |||
|  |     ip: parts[4], | |||
|  |     address: parts[4], // address is an alias for ip.
 | |||
|  |     port: parseInt(parts[5], 10), | |||
|  |     // skip parts[6] == 'typ'
 | |||
|  |     type: parts[7] | |||
|  |   }; | |||
|  | 
 | |||
|  |   for (var i = 8; i < parts.length; i += 2) { | |||
|  |     switch (parts[i]) { | |||
|  |       case 'raddr': | |||
|  |         candidate.relatedAddress = parts[i + 1]; | |||
|  |         break; | |||
|  |       case 'rport': | |||
|  |         candidate.relatedPort = parseInt(parts[i + 1], 10); | |||
|  |         break; | |||
|  |       case 'tcptype': | |||
|  |         candidate.tcpType = parts[i + 1]; | |||
|  |         break; | |||
|  |       case 'ufrag': | |||
|  |         candidate.ufrag = parts[i + 1]; // for backward compability.
 | |||
|  |         candidate.usernameFragment = parts[i + 1]; | |||
|  |         break; | |||
|  |       default: // extension handling, in particular ufrag
 | |||
|  |         candidate[parts[i]] = parts[i + 1]; | |||
|  |         break; | |||
|  |     } | |||
|  |   } | |||
|  |   return candidate; | |||
|  | }; | |||
|  | 
 | |||
|  | // Translates a candidate object into SDP candidate attribute.
 | |||
|  | SDPUtils.writeCandidate = function(candidate) { | |||
|  |   var sdp = []; | |||
|  |   sdp.push(candidate.foundation); | |||
|  |   sdp.push(candidate.component); | |||
|  |   sdp.push(candidate.protocol.toUpperCase()); | |||
|  |   sdp.push(candidate.priority); | |||
|  |   sdp.push(candidate.address || candidate.ip); | |||
|  |   sdp.push(candidate.port); | |||
|  | 
 | |||
|  |   var type = candidate.type; | |||
|  |   sdp.push('typ'); | |||
|  |   sdp.push(type); | |||
|  |   if (type !== 'host' && candidate.relatedAddress && | |||
|  |       candidate.relatedPort) { | |||
|  |     sdp.push('raddr'); | |||
|  |     sdp.push(candidate.relatedAddress); | |||
|  |     sdp.push('rport'); | |||
|  |     sdp.push(candidate.relatedPort); | |||
|  |   } | |||
|  |   if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { | |||
|  |     sdp.push('tcptype'); | |||
|  |     sdp.push(candidate.tcpType); | |||
|  |   } | |||
|  |   if (candidate.usernameFragment || candidate.ufrag) { | |||
|  |     sdp.push('ufrag'); | |||
|  |     sdp.push(candidate.usernameFragment || candidate.ufrag); | |||
|  |   } | |||
|  |   return 'candidate:' + sdp.join(' '); | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an ice-options line, returns an array of option tags.
 | |||
|  | // a=ice-options:foo bar
 | |||
|  | SDPUtils.parseIceOptions = function(line) { | |||
|  |   return line.substr(14).split(' '); | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
 | |||
|  | // a=rtpmap:111 opus/48000/2
 | |||
|  | SDPUtils.parseRtpMap = function(line) { | |||
|  |   var parts = line.substr(9).split(' '); | |||
|  |   var parsed = { | |||
|  |     payloadType: parseInt(parts.shift(), 10) // was: id
 | |||
|  |   }; | |||
|  | 
 | |||
|  |   parts = parts[0].split('/'); | |||
|  | 
 | |||
|  |   parsed.name = parts[0]; | |||
|  |   parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
 | |||
|  |   parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; | |||
|  |   // legacy alias, got renamed back to channels in ORTC.
 | |||
|  |   parsed.numChannels = parsed.channels; | |||
|  |   return parsed; | |||
|  | }; | |||
|  | 
 | |||
|  | // Generate an a=rtpmap line from RTCRtpCodecCapability or
 | |||
|  | // RTCRtpCodecParameters.
 | |||
|  | SDPUtils.writeRtpMap = function(codec) { | |||
|  |   var pt = codec.payloadType; | |||
|  |   if (codec.preferredPayloadType !== undefined) { | |||
|  |     pt = codec.preferredPayloadType; | |||
|  |   } | |||
|  |   var channels = codec.channels || codec.numChannels || 1; | |||
|  |   return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + | |||
|  |       (channels !== 1 ? '/' + channels : '') + '\r\n'; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an a=extmap line (headerextension from RFC 5285). Sample input:
 | |||
|  | // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
 | |||
|  | // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
 | |||
|  | SDPUtils.parseExtmap = function(line) { | |||
|  |   var parts = line.substr(9).split(' '); | |||
|  |   return { | |||
|  |     id: parseInt(parts[0], 10), | |||
|  |     direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', | |||
|  |     uri: parts[1] | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | // Generates a=extmap line from RTCRtpHeaderExtensionParameters or
 | |||
|  | // RTCRtpHeaderExtension.
 | |||
|  | SDPUtils.writeExtmap = function(headerExtension) { | |||
|  |   return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + | |||
|  |       (headerExtension.direction && headerExtension.direction !== 'sendrecv' | |||
|  |         ? '/' + headerExtension.direction | |||
|  |         : '') + | |||
|  |       ' ' + headerExtension.uri + '\r\n'; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an ftmp line, returns dictionary. Sample input:
 | |||
|  | // a=fmtp:96 vbr=on;cng=on
 | |||
|  | // Also deals with vbr=on; cng=on
 | |||
|  | SDPUtils.parseFmtp = function(line) { | |||
|  |   var parsed = {}; | |||
|  |   var kv; | |||
|  |   var parts = line.substr(line.indexOf(' ') + 1).split(';'); | |||
|  |   for (var j = 0; j < parts.length; j++) { | |||
|  |     kv = parts[j].trim().split('='); | |||
|  |     parsed[kv[0].trim()] = kv[1]; | |||
|  |   } | |||
|  |   return parsed; | |||
|  | }; | |||
|  | 
 | |||
|  | // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
 | |||
|  | SDPUtils.writeFmtp = function(codec) { | |||
|  |   var line = ''; | |||
|  |   var pt = codec.payloadType; | |||
|  |   if (codec.preferredPayloadType !== undefined) { | |||
|  |     pt = codec.preferredPayloadType; | |||
|  |   } | |||
|  |   if (codec.parameters && Object.keys(codec.parameters).length) { | |||
|  |     var params = []; | |||
|  |     Object.keys(codec.parameters).forEach(function(param) { | |||
|  |       if (codec.parameters[param]) { | |||
|  |         params.push(param + '=' + codec.parameters[param]); | |||
|  |       } else { | |||
|  |         params.push(param); | |||
|  |       } | |||
|  |     }); | |||
|  |     line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; | |||
|  |   } | |||
|  |   return line; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
 | |||
|  | // a=rtcp-fb:98 nack rpsi
 | |||
|  | SDPUtils.parseRtcpFb = function(line) { | |||
|  |   var parts = line.substr(line.indexOf(' ') + 1).split(' '); | |||
|  |   return { | |||
|  |     type: parts.shift(), | |||
|  |     parameter: parts.join(' ') | |||
|  |   }; | |||
|  | }; | |||
|  | // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
 | |||
|  | SDPUtils.writeRtcpFb = function(codec) { | |||
|  |   var lines = ''; | |||
|  |   var pt = codec.payloadType; | |||
|  |   if (codec.preferredPayloadType !== undefined) { | |||
|  |     pt = codec.preferredPayloadType; | |||
|  |   } | |||
|  |   if (codec.rtcpFeedback && codec.rtcpFeedback.length) { | |||
|  |     // FIXME: special handling for trr-int?
 | |||
|  |     codec.rtcpFeedback.forEach(function(fb) { | |||
|  |       lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + | |||
|  |       (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + | |||
|  |           '\r\n'; | |||
|  |     }); | |||
|  |   } | |||
|  |   return lines; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses an RFC 5576 ssrc media attribute. Sample input:
 | |||
|  | // a=ssrc:3735928559 cname:something
 | |||
|  | SDPUtils.parseSsrcMedia = function(line) { | |||
|  |   var sp = line.indexOf(' '); | |||
|  |   var parts = { | |||
|  |     ssrc: parseInt(line.substr(7, sp - 7), 10) | |||
|  |   }; | |||
|  |   var colon = line.indexOf(':', sp); | |||
|  |   if (colon > -1) { | |||
|  |     parts.attribute = line.substr(sp + 1, colon - sp - 1); | |||
|  |     parts.value = line.substr(colon + 1); | |||
|  |   } else { | |||
|  |     parts.attribute = line.substr(sp + 1); | |||
|  |   } | |||
|  |   return parts; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.parseSsrcGroup = function(line) { | |||
|  |   var parts = line.substr(13).split(' '); | |||
|  |   return { | |||
|  |     semantics: parts.shift(), | |||
|  |     ssrcs: parts.map(function(ssrc) { | |||
|  |       return parseInt(ssrc, 10); | |||
|  |     }) | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | // Extracts the MID (RFC 5888) from a media section.
 | |||
|  | // returns the MID or undefined if no mid line was found.
 | |||
|  | SDPUtils.getMid = function(mediaSection) { | |||
|  |   var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; | |||
|  |   if (mid) { | |||
|  |     return mid.substr(6); | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.parseFingerprint = function(line) { | |||
|  |   var parts = line.substr(14).split(' '); | |||
|  |   return { | |||
|  |     algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
 | |||
|  |     value: parts[1] | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | // Extracts DTLS parameters from SDP media section or sessionpart.
 | |||
|  | // FIXME: for consistency with other functions this should only
 | |||
|  | //   get the fingerprint line as input. See also getIceParameters.
 | |||
|  | SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { | |||
|  |   var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, | |||
|  |     'a=fingerprint:'); | |||
|  |   // Note: a=setup line is ignored since we use the 'auto' role.
 | |||
|  |   // Note2: 'algorithm' is not case sensitive except in Edge.
 | |||
|  |   return { | |||
|  |     role: 'auto', | |||
|  |     fingerprints: lines.map(SDPUtils.parseFingerprint) | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | // Serializes DTLS parameters to SDP.
 | |||
|  | SDPUtils.writeDtlsParameters = function(params, setupType) { | |||
|  |   var sdp = 'a=setup:' + setupType + '\r\n'; | |||
|  |   params.fingerprints.forEach(function(fp) { | |||
|  |     sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; | |||
|  |   }); | |||
|  |   return sdp; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses a=crypto lines into
 | |||
|  | //   https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members
 | |||
|  | SDPUtils.parseCryptoLine = function(line) { | |||
|  |   var parts = line.substr(9).split(' '); | |||
|  |   return { | |||
|  |     tag: parseInt(parts[0], 10), | |||
|  |     cryptoSuite: parts[1], | |||
|  |     keyParams: parts[2], | |||
|  |     sessionParams: parts.slice(3), | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.writeCryptoLine = function(parameters) { | |||
|  |   return 'a=crypto:' + parameters.tag + ' ' + | |||
|  |     parameters.cryptoSuite + ' ' + | |||
|  |     (typeof parameters.keyParams === 'object' | |||
|  |       ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) | |||
|  |       : parameters.keyParams) + | |||
|  |     (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + | |||
|  |     '\r\n'; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses the crypto key parameters into
 | |||
|  | //   https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam*
 | |||
|  | SDPUtils.parseCryptoKeyParams = function(keyParams) { | |||
|  |   if (keyParams.indexOf('inline:') !== 0) { | |||
|  |     return null; | |||
|  |   } | |||
|  |   var parts = keyParams.substr(7).split('|'); | |||
|  |   return { | |||
|  |     keyMethod: 'inline', | |||
|  |     keySalt: parts[0], | |||
|  |     lifeTime: parts[1], | |||
|  |     mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, | |||
|  |     mkiLength: parts[2] ? parts[2].split(':')[1] : undefined, | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.writeCryptoKeyParams = function(keyParams) { | |||
|  |   return keyParams.keyMethod + ':' | |||
|  |     + keyParams.keySalt + | |||
|  |     (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + | |||
|  |     (keyParams.mkiValue && keyParams.mkiLength | |||
|  |       ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength | |||
|  |       : ''); | |||
|  | }; | |||
|  | 
 | |||
|  | // Extracts all SDES paramters.
 | |||
|  | SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) { | |||
|  |   var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, | |||
|  |     'a=crypto:'); | |||
|  |   return lines.map(SDPUtils.parseCryptoLine); | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses ICE information from SDP media section or sessionpart.
 | |||
|  | // FIXME: for consistency with other functions this should only
 | |||
|  | //   get the ice-ufrag and ice-pwd lines as input.
 | |||
|  | SDPUtils.getIceParameters = function(mediaSection, sessionpart) { | |||
|  |   var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, | |||
|  |     'a=ice-ufrag:')[0]; | |||
|  |   var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, | |||
|  |     'a=ice-pwd:')[0]; | |||
|  |   if (!(ufrag && pwd)) { | |||
|  |     return null; | |||
|  |   } | |||
|  |   return { | |||
|  |     usernameFragment: ufrag.substr(12), | |||
|  |     password: pwd.substr(10), | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | // Serializes ICE parameters to SDP.
 | |||
|  | SDPUtils.writeIceParameters = function(params) { | |||
|  |   return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + | |||
|  |       'a=ice-pwd:' + params.password + '\r\n'; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses the SDP media section and returns RTCRtpParameters.
 | |||
|  | SDPUtils.parseRtpParameters = function(mediaSection) { | |||
|  |   var description = { | |||
|  |     codecs: [], | |||
|  |     headerExtensions: [], | |||
|  |     fecMechanisms: [], | |||
|  |     rtcp: [] | |||
|  |   }; | |||
|  |   var lines = SDPUtils.splitLines(mediaSection); | |||
|  |   var mline = lines[0].split(' '); | |||
|  |   for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
 | |||
|  |     var pt = mline[i]; | |||
|  |     var rtpmapline = SDPUtils.matchPrefix( | |||
|  |       mediaSection, 'a=rtpmap:' + pt + ' ')[0]; | |||
|  |     if (rtpmapline) { | |||
|  |       var codec = SDPUtils.parseRtpMap(rtpmapline); | |||
|  |       var fmtps = SDPUtils.matchPrefix( | |||
|  |         mediaSection, 'a=fmtp:' + pt + ' '); | |||
|  |       // Only the first a=fmtp:<pt> is considered.
 | |||
|  |       codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; | |||
|  |       codec.rtcpFeedback = SDPUtils.matchPrefix( | |||
|  |         mediaSection, 'a=rtcp-fb:' + pt + ' ') | |||
|  |         .map(SDPUtils.parseRtcpFb); | |||
|  |       description.codecs.push(codec); | |||
|  |       // parse FEC mechanisms from rtpmap lines.
 | |||
|  |       switch (codec.name.toUpperCase()) { | |||
|  |         case 'RED': | |||
|  |         case 'ULPFEC': | |||
|  |           description.fecMechanisms.push(codec.name.toUpperCase()); | |||
|  |           break; | |||
|  |         default: // only RED and ULPFEC are recognized as FEC mechanisms.
 | |||
|  |           break; | |||
|  |       } | |||
|  |     } | |||
|  |   } | |||
|  |   SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { | |||
|  |     description.headerExtensions.push(SDPUtils.parseExtmap(line)); | |||
|  |   }); | |||
|  |   // FIXME: parse rtcp.
 | |||
|  |   return description; | |||
|  | }; | |||
|  | 
 | |||
|  | // Generates parts of the SDP media section describing the capabilities /
 | |||
|  | // parameters.
 | |||
|  | SDPUtils.writeRtpDescription = function(kind, caps) { | |||
|  |   var sdp = ''; | |||
|  | 
 | |||
|  |   // Build the mline.
 | |||
|  |   sdp += 'm=' + kind + ' '; | |||
|  |   sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
 | |||
|  |   sdp += ' UDP/TLS/RTP/SAVPF '; | |||
|  |   sdp += caps.codecs.map(function(codec) { | |||
|  |     if (codec.preferredPayloadType !== undefined) { | |||
|  |       return codec.preferredPayloadType; | |||
|  |     } | |||
|  |     return codec.payloadType; | |||
|  |   }).join(' ') + '\r\n'; | |||
|  | 
 | |||
|  |   sdp += 'c=IN IP4 0.0.0.0\r\n'; | |||
|  |   sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; | |||
|  | 
 | |||
|  |   // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
 | |||
|  |   caps.codecs.forEach(function(codec) { | |||
|  |     sdp += SDPUtils.writeRtpMap(codec); | |||
|  |     sdp += SDPUtils.writeFmtp(codec); | |||
|  |     sdp += SDPUtils.writeRtcpFb(codec); | |||
|  |   }); | |||
|  |   var maxptime = 0; | |||
|  |   caps.codecs.forEach(function(codec) { | |||
|  |     if (codec.maxptime > maxptime) { | |||
|  |       maxptime = codec.maxptime; | |||
|  |     } | |||
|  |   }); | |||
|  |   if (maxptime > 0) { | |||
|  |     sdp += 'a=maxptime:' + maxptime + '\r\n'; | |||
|  |   } | |||
|  |   sdp += 'a=rtcp-mux\r\n'; | |||
|  | 
 | |||
|  |   if (caps.headerExtensions) { | |||
|  |     caps.headerExtensions.forEach(function(extension) { | |||
|  |       sdp += SDPUtils.writeExtmap(extension); | |||
|  |     }); | |||
|  |   } | |||
|  |   // FIXME: write fecMechanisms.
 | |||
|  |   return sdp; | |||
|  | }; | |||
|  | 
 | |||
|  | // Parses the SDP media section and returns an array of
 | |||
|  | // RTCRtpEncodingParameters.
 | |||
|  | SDPUtils.parseRtpEncodingParameters = function(mediaSection) { | |||
|  |   var encodingParameters = []; | |||
|  |   var description = SDPUtils.parseRtpParameters(mediaSection); | |||
|  |   var hasRed = description.fecMechanisms.indexOf('RED') !== -1; | |||
|  |   var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; | |||
|  | 
 | |||
|  |   // filter a=ssrc:... cname:, ignore PlanB-msid
 | |||
|  |   var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') | |||
|  |     .map(function(line) { | |||
|  |       return SDPUtils.parseSsrcMedia(line); | |||
|  |     }) | |||
|  |     .filter(function(parts) { | |||
|  |       return parts.attribute === 'cname'; | |||
|  |     }); | |||
|  |   var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; | |||
|  |   var secondarySsrc; | |||
|  | 
 | |||
|  |   var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') | |||
|  |     .map(function(line) { | |||
|  |       var parts = line.substr(17).split(' '); | |||
|  |       return parts.map(function(part) { | |||
|  |         return parseInt(part, 10); | |||
|  |       }); | |||
|  |     }); | |||
|  |   if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { | |||
|  |     secondarySsrc = flows[0][1]; | |||
|  |   } | |||
|  | 
 | |||
|  |   description.codecs.forEach(function(codec) { | |||
|  |     if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { | |||
|  |       var encParam = { | |||
|  |         ssrc: primarySsrc, | |||
|  |         codecPayloadType: parseInt(codec.parameters.apt, 10) | |||
|  |       }; | |||
|  |       if (primarySsrc && secondarySsrc) { | |||
|  |         encParam.rtx = {ssrc: secondarySsrc}; | |||
|  |       } | |||
|  |       encodingParameters.push(encParam); | |||
|  |       if (hasRed) { | |||
|  |         encParam = JSON.parse(JSON.stringify(encParam)); | |||
|  |         encParam.fec = { | |||
|  |           ssrc: primarySsrc, | |||
|  |           mechanism: hasUlpfec ? 'red+ulpfec' : 'red' | |||
|  |         }; | |||
|  |         encodingParameters.push(encParam); | |||
|  |       } | |||
|  |     } | |||
|  |   }); | |||
|  |   if (encodingParameters.length === 0 && primarySsrc) { | |||
|  |     encodingParameters.push({ | |||
|  |       ssrc: primarySsrc | |||
|  |     }); | |||
|  |   } | |||
|  | 
 | |||
|  |   // we support both b=AS and b=TIAS but interpret AS as TIAS.
 | |||
|  |   var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); | |||
|  |   if (bandwidth.length) { | |||
|  |     if (bandwidth[0].indexOf('b=TIAS:') === 0) { | |||
|  |       bandwidth = parseInt(bandwidth[0].substr(7), 10); | |||
|  |     } else if (bandwidth[0].indexOf('b=AS:') === 0) { | |||
|  |       // use formula from JSEP to convert b=AS to TIAS value.
 | |||
|  |       bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 | |||
|  |           - (50 * 40 * 8); | |||
|  |     } else { | |||
|  |       bandwidth = undefined; | |||
|  |     } | |||
|  |     encodingParameters.forEach(function(params) { | |||
|  |       params.maxBitrate = bandwidth; | |||
|  |     }); | |||
|  |   } | |||
|  |   return encodingParameters; | |||
|  | }; | |||
|  | 
 | |||
|  | // parses http://draft.ortc.org/#rtcrtcpparameters*
 | |||
|  | SDPUtils.parseRtcpParameters = function(mediaSection) { | |||
|  |   var rtcpParameters = {}; | |||
|  | 
 | |||
|  |   // Gets the first SSRC. Note tha with RTX there might be multiple
 | |||
|  |   // SSRCs.
 | |||
|  |   var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') | |||
|  |     .map(function(line) { | |||
|  |       return SDPUtils.parseSsrcMedia(line); | |||
|  |     }) | |||
|  |     .filter(function(obj) { | |||
|  |       return obj.attribute === 'cname'; | |||
|  |     })[0]; | |||
|  |   if (remoteSsrc) { | |||
|  |     rtcpParameters.cname = remoteSsrc.value; | |||
|  |     rtcpParameters.ssrc = remoteSsrc.ssrc; | |||
|  |   } | |||
|  | 
 | |||
|  |   // Edge uses the compound attribute instead of reducedSize
 | |||
|  |   // compound is !reducedSize
 | |||
|  |   var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); | |||
|  |   rtcpParameters.reducedSize = rsize.length > 0; | |||
|  |   rtcpParameters.compound = rsize.length === 0; | |||
|  | 
 | |||
|  |   // parses the rtcp-mux attrіbute.
 | |||
|  |   // Note that Edge does not support unmuxed RTCP.
 | |||
|  |   var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); | |||
|  |   rtcpParameters.mux = mux.length > 0; | |||
|  | 
 | |||
|  |   return rtcpParameters; | |||
|  | }; | |||
|  | 
 | |||
|  | // parses either a=msid: or a=ssrc:... msid lines and returns
 | |||
|  | // the id of the MediaStream and MediaStreamTrack.
 | |||
|  | SDPUtils.parseMsid = function(mediaSection) { | |||
|  |   var parts; | |||
|  |   var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); | |||
|  |   if (spec.length === 1) { | |||
|  |     parts = spec[0].substr(7).split(' '); | |||
|  |     return {stream: parts[0], track: parts[1]}; | |||
|  |   } | |||
|  |   var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') | |||
|  |     .map(function(line) { | |||
|  |       return SDPUtils.parseSsrcMedia(line); | |||
|  |     }) | |||
|  |     .filter(function(msidParts) { | |||
|  |       return msidParts.attribute === 'msid'; | |||
|  |     }); | |||
|  |   if (planB.length > 0) { | |||
|  |     parts = planB[0].value.split(' '); | |||
|  |     return {stream: parts[0], track: parts[1]}; | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | // SCTP
 | |||
|  | // parses draft-ietf-mmusic-sctp-sdp-26 first and falls back
 | |||
|  | // to draft-ietf-mmusic-sctp-sdp-05
 | |||
|  | SDPUtils.parseSctpDescription = function(mediaSection) { | |||
|  |   var mline = SDPUtils.parseMLine(mediaSection); | |||
|  |   var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); | |||
|  |   var maxMessageSize; | |||
|  |   if (maxSizeLine.length > 0) { | |||
|  |     maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); | |||
|  |   } | |||
|  |   if (isNaN(maxMessageSize)) { | |||
|  |     maxMessageSize = 65536; | |||
|  |   } | |||
|  |   var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); | |||
|  |   if (sctpPort.length > 0) { | |||
|  |     return { | |||
|  |       port: parseInt(sctpPort[0].substr(12), 10), | |||
|  |       protocol: mline.fmt, | |||
|  |       maxMessageSize: maxMessageSize | |||
|  |     }; | |||
|  |   } | |||
|  |   var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); | |||
|  |   if (sctpMapLines.length > 0) { | |||
|  |     var parts = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:')[0] | |||
|  |       .substr(10) | |||
|  |       .split(' '); | |||
|  |     return { | |||
|  |       port: parseInt(parts[0], 10), | |||
|  |       protocol: parts[1], | |||
|  |       maxMessageSize: maxMessageSize | |||
|  |     }; | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | // SCTP
 | |||
|  | // outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers
 | |||
|  | // support by now receiving in this format, unless we originally parsed
 | |||
|  | // as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line
 | |||
|  | // protocol of DTLS/SCTP -- without UDP/ or TCP/)
 | |||
|  | SDPUtils.writeSctpDescription = function(media, sctp) { | |||
|  |   var output = []; | |||
|  |   if (media.protocol !== 'DTLS/SCTP') { | |||
|  |     output = [ | |||
|  |       'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n', | |||
|  |       'c=IN IP4 0.0.0.0\r\n', | |||
|  |       'a=sctp-port:' + sctp.port + '\r\n' | |||
|  |     ]; | |||
|  |   } else { | |||
|  |     output = [ | |||
|  |       'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n', | |||
|  |       'c=IN IP4 0.0.0.0\r\n', | |||
|  |       'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n' | |||
|  |     ]; | |||
|  |   } | |||
|  |   if (sctp.maxMessageSize !== undefined) { | |||
|  |     output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); | |||
|  |   } | |||
|  |   return output.join(''); | |||
|  | }; | |||
|  | 
 | |||
|  | // Generate a session ID for SDP.
 | |||
|  | // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
 | |||
|  | // recommends using a cryptographically random +ve 64-bit value
 | |||
|  | // but right now this should be acceptable and within the right range
 | |||
|  | SDPUtils.generateSessionId = function() { | |||
|  |   return Math.random().toString().substr(2, 21); | |||
|  | }; | |||
|  | 
 | |||
|  | // Write boilder plate for start of SDP
 | |||
|  | // sessId argument is optional - if not supplied it will
 | |||
|  | // be generated randomly
 | |||
|  | // sessVersion is optional and defaults to 2
 | |||
|  | // sessUser is optional and defaults to 'thisisadapterortc'
 | |||
|  | SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { | |||
|  |   var sessionId; | |||
|  |   var version = sessVer !== undefined ? sessVer : 2; | |||
|  |   if (sessId) { | |||
|  |     sessionId = sessId; | |||
|  |   } else { | |||
|  |     sessionId = SDPUtils.generateSessionId(); | |||
|  |   } | |||
|  |   var user = sessUser || 'thisisadapterortc'; | |||
|  |   // FIXME: sess-id should be an NTP timestamp.
 | |||
|  |   return 'v=0\r\n' + | |||
|  |       'o=' + user + ' ' + sessionId + ' ' + version + | |||
|  |         ' IN IP4 127.0.0.1\r\n' + | |||
|  |       's=-\r\n' + | |||
|  |       't=0 0\r\n'; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { | |||
|  |   var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); | |||
|  | 
 | |||
|  |   // Map ICE parameters (ufrag, pwd) to SDP.
 | |||
|  |   sdp += SDPUtils.writeIceParameters( | |||
|  |     transceiver.iceGatherer.getLocalParameters()); | |||
|  | 
 | |||
|  |   // Map DTLS parameters to SDP.
 | |||
|  |   sdp += SDPUtils.writeDtlsParameters( | |||
|  |     transceiver.dtlsTransport.getLocalParameters(), | |||
|  |     type === 'offer' ? 'actpass' : 'active'); | |||
|  | 
 | |||
|  |   sdp += 'a=mid:' + transceiver.mid + '\r\n'; | |||
|  | 
 | |||
|  |   if (transceiver.direction) { | |||
|  |     sdp += 'a=' + transceiver.direction + '\r\n'; | |||
|  |   } else if (transceiver.rtpSender && transceiver.rtpReceiver) { | |||
|  |     sdp += 'a=sendrecv\r\n'; | |||
|  |   } else if (transceiver.rtpSender) { | |||
|  |     sdp += 'a=sendonly\r\n'; | |||
|  |   } else if (transceiver.rtpReceiver) { | |||
|  |     sdp += 'a=recvonly\r\n'; | |||
|  |   } else { | |||
|  |     sdp += 'a=inactive\r\n'; | |||
|  |   } | |||
|  | 
 | |||
|  |   if (transceiver.rtpSender) { | |||
|  |     // spec.
 | |||
|  |     var msid = 'msid:' + stream.id + ' ' + | |||
|  |         transceiver.rtpSender.track.id + '\r\n'; | |||
|  |     sdp += 'a=' + msid; | |||
|  | 
 | |||
|  |     // for Chrome.
 | |||
|  |     sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + | |||
|  |         ' ' + msid; | |||
|  |     if (transceiver.sendEncodingParameters[0].rtx) { | |||
|  |       sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + | |||
|  |           ' ' + msid; | |||
|  |       sdp += 'a=ssrc-group:FID ' + | |||
|  |           transceiver.sendEncodingParameters[0].ssrc + ' ' + | |||
|  |           transceiver.sendEncodingParameters[0].rtx.ssrc + | |||
|  |           '\r\n'; | |||
|  |     } | |||
|  |   } | |||
|  |   // FIXME: this should be written by writeRtpDescription.
 | |||
|  |   sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + | |||
|  |       ' cname:' + SDPUtils.localCName + '\r\n'; | |||
|  |   if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { | |||
|  |     sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + | |||
|  |         ' cname:' + SDPUtils.localCName + '\r\n'; | |||
|  |   } | |||
|  |   return sdp; | |||
|  | }; | |||
|  | 
 | |||
|  | // Gets the direction from the mediaSection or the sessionpart.
 | |||
|  | SDPUtils.getDirection = function(mediaSection, sessionpart) { | |||
|  |   // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
 | |||
|  |   var lines = SDPUtils.splitLines(mediaSection); | |||
|  |   for (var i = 0; i < lines.length; i++) { | |||
|  |     switch (lines[i]) { | |||
|  |       case 'a=sendrecv': | |||
|  |       case 'a=sendonly': | |||
|  |       case 'a=recvonly': | |||
|  |       case 'a=inactive': | |||
|  |         return lines[i].substr(2); | |||
|  |       default: | |||
|  |         // FIXME: What should happen here?
 | |||
|  |     } | |||
|  |   } | |||
|  |   if (sessionpart) { | |||
|  |     return SDPUtils.getDirection(sessionpart); | |||
|  |   } | |||
|  |   return 'sendrecv'; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.getKind = function(mediaSection) { | |||
|  |   var lines = SDPUtils.splitLines(mediaSection); | |||
|  |   var mline = lines[0].split(' '); | |||
|  |   return mline[0].substr(2); | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.isRejected = function(mediaSection) { | |||
|  |   return mediaSection.split(' ', 2)[1] === '0'; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.parseMLine = function(mediaSection) { | |||
|  |   var lines = SDPUtils.splitLines(mediaSection); | |||
|  |   var parts = lines[0].substr(2).split(' '); | |||
|  |   return { | |||
|  |     kind: parts[0], | |||
|  |     port: parseInt(parts[1], 10), | |||
|  |     protocol: parts[2], | |||
|  |     fmt: parts.slice(3).join(' ') | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | SDPUtils.parseOLine = function(mediaSection) { | |||
|  |   var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; | |||
|  |   var parts = line.substr(2).split(' '); | |||
|  |   return { | |||
|  |     username: parts[0], | |||
|  |     sessionId: parts[1], | |||
|  |     sessionVersion: parseInt(parts[2], 10), | |||
|  |     netType: parts[3], | |||
|  |     addressType: parts[4], | |||
|  |     address: parts[5] | |||
|  |   }; | |||
|  | }; | |||
|  | 
 | |||
|  | // a very naive interpretation of a valid SDP.
 | |||
|  | SDPUtils.isValidSDP = function(blob) { | |||
|  |   if (typeof blob !== 'string' || blob.length === 0) { | |||
|  |     return false; | |||
|  |   } | |||
|  |   var lines = SDPUtils.splitLines(blob); | |||
|  |   for (var i = 0; i < lines.length; i++) { | |||
|  |     if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { | |||
|  |       return false; | |||
|  |     } | |||
|  |     // TODO: check the modifier a bit more.
 | |||
|  |   } | |||
|  |   return true; | |||
|  | }; | |||
|  | 
 | |||
|  | // Expose public methods.
 | |||
|  | if (typeof module === 'object') { | |||
|  |   module.exports = SDPUtils; | |||
|  | } | |||
|  | 
 | |||
|  | },{}]},{},[1])(1) | |||
|  | }); |