MDL-65766 libraries: Upgrade WebRTC to 6.4.0
[moodle.git] / lib / amd / src / adapter.js
1 /**
2  * Description of import/upgrade into Moodle:
3  *
4  * 1. Visit https://github.com/webrtc/adapter/releases.
5  * 2. Check if the version has been updated from what is listed in lib/thirdpartylibs.xml in the Moodle wwwroot.
6  * 3. If it has -
7  *    1. Download the source code.
8  *    2. Copy the content of the file release/adapter.js from the archive (ignore the first line).
9  *    3. Replace the content below "return (function e(t,n,r) .." in this file with the content you copied.
10  *    4. Ensure to update lib/thirdpartylibs.xml with any changes.
11  */
13 // ESLint directives.
14 /* eslint-disable */
16 // JSHint directives.
17 /* jshint ignore:start */
19 define([], function() {
20 return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
21         /*
22          *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
23          *
24          *  Use of this source code is governed by a BSD-style license
25          *  that can be found in the LICENSE file in the root of the source
26          *  tree.
27          */
28         /* eslint-env node */
29         'use strict';
31         var SDPUtils = require('sdp');
33         function fixStatsType(stat) {
34             return {
35                 inboundrtp: 'inbound-rtp',
36                 outboundrtp: 'outbound-rtp',
37                 candidatepair: 'candidate-pair',
38                 localcandidate: 'local-candidate',
39                 remotecandidate: 'remote-candidate'
40             }[stat.type] || stat.type;
41         }
43         function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
44             var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
46             // Map ICE parameters (ufrag, pwd) to SDP.
47             sdp += SDPUtils.writeIceParameters(
48                 transceiver.iceGatherer.getLocalParameters());
50             // Map DTLS parameters to SDP.
51             sdp += SDPUtils.writeDtlsParameters(
52                 transceiver.dtlsTransport.getLocalParameters(),
53                 type === 'offer' ? 'actpass' : dtlsRole || 'active');
55             sdp += 'a=mid:' + transceiver.mid + '\r\n';
57             if (transceiver.rtpSender && transceiver.rtpReceiver) {
58                 sdp += 'a=sendrecv\r\n';
59             } else if (transceiver.rtpSender) {
60                 sdp += 'a=sendonly\r\n';
61             } else if (transceiver.rtpReceiver) {
62                 sdp += 'a=recvonly\r\n';
63             } else {
64                 sdp += 'a=inactive\r\n';
65             }
67             if (transceiver.rtpSender) {
68                 var trackId = transceiver.rtpSender._initialTrackId ||
69                     transceiver.rtpSender.track.id;
70                 transceiver.rtpSender._initialTrackId = trackId;
71                 // spec.
72                 var msid = 'msid:' + (stream ? stream.id : '-') + ' ' +
73                     trackId + '\r\n';
74                 sdp += 'a=' + msid;
75                 // for Chrome. Legacy should no longer be required.
76                 sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
77                     ' ' + msid;
79                 // RTX
80                 if (transceiver.sendEncodingParameters[0].rtx) {
81                     sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
82                         ' ' + msid;
83                     sdp += 'a=ssrc-group:FID ' +
84                         transceiver.sendEncodingParameters[0].ssrc + ' ' +
85                         transceiver.sendEncodingParameters[0].rtx.ssrc +
86                         '\r\n';
87                 }
88             }
89             // FIXME: this should be written by writeRtpDescription.
90             sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
91                 ' cname:' + SDPUtils.localCName + '\r\n';
92             if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
93                 sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
94                     ' cname:' + SDPUtils.localCName + '\r\n';
95             }
96             return sdp;
97         }
99         // Edge does not like
100         // 1) stun: filtered after 14393 unless ?transport=udp is present
101         // 2) turn: that does not have all of turn:host:port?transport=udp
102         // 3) turn: with ipv6 addresses
103         // 4) turn: occurring muliple times
104         function filterIceServers(iceServers, edgeVersion) {
105             var hasTurn = false;
106             iceServers = JSON.parse(JSON.stringify(iceServers));
107             return iceServers.filter(function(server) {
108                 if (server && (server.urls || server.url)) {
109                     var urls = server.urls || server.url;
110                     if (server.url && !server.urls) {
111                         console.warn('RTCIceServer.url is deprecated! Use urls instead.');
112                     }
113                     var isString = typeof urls === 'string';
114                     if (isString) {
115                         urls = [urls];
116                     }
117                     urls = urls.filter(function(url) {
118                         var validTurn = url.indexOf('turn:') === 0 &&
119                             url.indexOf('transport=udp') !== -1 &&
120                             url.indexOf('turn:[') === -1 &&
121                             !hasTurn;
123                         if (validTurn) {
124                             hasTurn = true;
125                             return true;
126                         }
127                         return url.indexOf('stun:') === 0 && edgeVersion >= 14393 &&
128                             url.indexOf('?transport=udp') === -1;
129                     });
131                     delete server.url;
132                     server.urls = isString ? urls[0] : urls;
133                     return !!urls.length;
134                 }
135             });
136         }
138         // Determines the intersection of local and remote capabilities.
139         function getCommonCapabilities(localCapabilities, remoteCapabilities) {
140             var commonCapabilities = {
141                 codecs: [],
142                 headerExtensions: [],
143                 fecMechanisms: []
144             };
146             var findCodecByPayloadType = function(pt, codecs) {
147                 pt = parseInt(pt, 10);
148                 for (var i = 0; i < codecs.length; i++) {
149                     if (codecs[i].payloadType === pt ||
150                         codecs[i].preferredPayloadType === pt) {
151                         return codecs[i];
152                     }
153                 }
154             };
156             var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
157                 var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
158                 var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
159                 return lCodec && rCodec &&
160                     lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
161             };
163             localCapabilities.codecs.forEach(function(lCodec) {
164                 for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
165                     var rCodec = remoteCapabilities.codecs[i];
166                     if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
167                         lCodec.clockRate === rCodec.clockRate) {
168                         if (lCodec.name.toLowerCase() === 'rtx' &&
169                             lCodec.parameters && rCodec.parameters.apt) {
170                             // for RTX we need to find the local rtx that has a apt
171                             // which points to the same local codec as the remote one.
172                             if (!rtxCapabilityMatches(lCodec, rCodec,
173                                     localCapabilities.codecs, remoteCapabilities.codecs)) {
174                                 continue;
175                             }
176                         }
177                         rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
178                         // number of channels is the highest common number of channels
179                         rCodec.numChannels = Math.min(lCodec.numChannels,
180                             rCodec.numChannels);
181                         // push rCodec so we reply with offerer payload type
182                         commonCapabilities.codecs.push(rCodec);
184                         // determine common feedback mechanisms
185                         rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
186                             for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
187                                 if (lCodec.rtcpFeedback[j].type === fb.type &&
188                                     lCodec.rtcpFeedback[j].parameter === fb.parameter) {
189                                     return true;
190                                 }
191                             }
192                             return false;
193                         });
194                         // FIXME: also need to determine .parameters
195                         //  see https://github.com/openpeer/ortc/issues/569
196                         break;
197                     }
198                 }
199             });
201             localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
202                 for (var i = 0; i < remoteCapabilities.headerExtensions.length;
203                      i++) {
204                     var rHeaderExtension = remoteCapabilities.headerExtensions[i];
205                     if (lHeaderExtension.uri === rHeaderExtension.uri) {
206                         commonCapabilities.headerExtensions.push(rHeaderExtension);
207                         break;
208                     }
209                 }
210             });
212             // FIXME: fecMechanisms
213             return commonCapabilities;
214         }
216         // is action=setLocalDescription with type allowed in signalingState
217         function isActionAllowedInSignalingState(action, type, signalingState) {
218             return {
219                 offer: {
220                     setLocalDescription: ['stable', 'have-local-offer'],
221                     setRemoteDescription: ['stable', 'have-remote-offer']
222                 },
223                 answer: {
224                     setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
225                     setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
226                 }
227             }[type][action].indexOf(signalingState) !== -1;
228         }
230         function maybeAddCandidate(iceTransport, candidate) {
231             // Edge's internal representation adds some fields therefore
232             // not all fieldѕ are taken into account.
233             var alreadyAdded = iceTransport.getRemoteCandidates()
234                 .find(function(remoteCandidate) {
235                     return candidate.foundation === remoteCandidate.foundation &&
236                         candidate.ip === remoteCandidate.ip &&
237                         candidate.port === remoteCandidate.port &&
238                         candidate.priority === remoteCandidate.priority &&
239                         candidate.protocol === remoteCandidate.protocol &&
240                         candidate.type === remoteCandidate.type;
241                 });
242             if (!alreadyAdded) {
243                 iceTransport.addRemoteCandidate(candidate);
244             }
245             return !alreadyAdded;
246         }
249         function makeError(name, description) {
250             var e = new Error(description);
251             e.name = name;
252             // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names
253             e.code = {
254                 NotSupportedError: 9,
255                 InvalidStateError: 11,
256                 InvalidAccessError: 15,
257                 TypeError: undefined,
258                 OperationError: undefined
259             }[name];
260             return e;
261         }
263         module.exports = function(window, edgeVersion) {
264             // https://w3c.github.io/mediacapture-main/#mediastream
265             // Helper function to add the track to the stream and
266             // dispatch the event ourselves.
267             function addTrackToStreamAndFireEvent(track, stream) {
268                 stream.addTrack(track);
269                 stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack',
270                     {track: track}));
271             }
273             function removeTrackFromStreamAndFireEvent(track, stream) {
274                 stream.removeTrack(track);
275                 stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack',
276                     {track: track}));
277             }
279             function fireAddTrack(pc, track, receiver, streams) {
280                 var trackEvent = new Event('track');
281                 trackEvent.track = track;
282                 trackEvent.receiver = receiver;
283                 trackEvent.transceiver = {receiver: receiver};
284                 trackEvent.streams = streams;
285                 window.setTimeout(function() {
286                     pc._dispatchEvent('track', trackEvent);
287                 });
288             }
290             var RTCPeerConnection = function(config) {
291                 var pc = this;
293                 var _eventTarget = document.createDocumentFragment();
294                 ['addEventListener', 'removeEventListener', 'dispatchEvent']
295                     .forEach(function(method) {
296                         pc[method] = _eventTarget[method].bind(_eventTarget);
297                     });
299                 this.canTrickleIceCandidates = null;
301                 this.needNegotiation = false;
303                 this.localStreams = [];
304                 this.remoteStreams = [];
306                 this._localDescription = null;
307                 this._remoteDescription = null;
309                 this.signalingState = 'stable';
310                 this.iceConnectionState = 'new';
311                 this.connectionState = 'new';
312                 this.iceGatheringState = 'new';
314                 config = JSON.parse(JSON.stringify(config || {}));
316                 this.usingBundle = config.bundlePolicy === 'max-bundle';
317                 if (config.rtcpMuxPolicy === 'negotiate') {
318                     throw(makeError('NotSupportedError',
319                         'rtcpMuxPolicy \'negotiate\' is not supported'));
320                 } else if (!config.rtcpMuxPolicy) {
321                     config.rtcpMuxPolicy = 'require';
322                 }
324                 switch (config.iceTransportPolicy) {
325                     case 'all':
326                     case 'relay':
327                         break;
328                     default:
329                         config.iceTransportPolicy = 'all';
330                         break;
331                 }
333                 switch (config.bundlePolicy) {
334                     case 'balanced':
335                     case 'max-compat':
336                     case 'max-bundle':
337                         break;
338                     default:
339                         config.bundlePolicy = 'balanced';
340                         break;
341                 }
343                 config.iceServers = filterIceServers(config.iceServers || [], edgeVersion);
345                 this._iceGatherers = [];
346                 if (config.iceCandidatePoolSize) {
347                     for (var i = config.iceCandidatePoolSize; i > 0; i--) {
348                         this._iceGatherers.push(new window.RTCIceGatherer({
349                             iceServers: config.iceServers,
350                             gatherPolicy: config.iceTransportPolicy
351                         }));
352                     }
353                 } else {
354                     config.iceCandidatePoolSize = 0;
355                 }
357                 this._config = config;
359                 // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
360                 // everything that is needed to describe a SDP m-line.
361                 this.transceivers = [];
363                 this._sdpSessionId = SDPUtils.generateSessionId();
364                 this._sdpSessionVersion = 0;
366                 this._dtlsRole = undefined; // role for a=setup to use in answers.
368                 this._isClosed = false;
369             };
371             Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', {
372                 configurable: true,
373                 get: function() {
374                     return this._localDescription;
375                 }
376             });
377             Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', {
378                 configurable: true,
379                 get: function() {
380                     return this._remoteDescription;
381                 }
382             });
384             // set up event handlers on prototype
385             RTCPeerConnection.prototype.onicecandidate = null;
386             RTCPeerConnection.prototype.onaddstream = null;
387             RTCPeerConnection.prototype.ontrack = null;
388             RTCPeerConnection.prototype.onremovestream = null;
389             RTCPeerConnection.prototype.onsignalingstatechange = null;
390             RTCPeerConnection.prototype.oniceconnectionstatechange = null;
391             RTCPeerConnection.prototype.onconnectionstatechange = null;
392             RTCPeerConnection.prototype.onicegatheringstatechange = null;
393             RTCPeerConnection.prototype.onnegotiationneeded = null;
394             RTCPeerConnection.prototype.ondatachannel = null;
396             RTCPeerConnection.prototype._dispatchEvent = function(name, event) {
397                 if (this._isClosed) {
398                     return;
399                 }
400                 this.dispatchEvent(event);
401                 if (typeof this['on' + name] === 'function') {
402                     this['on' + name](event);
403                 }
404             };
406             RTCPeerConnection.prototype._emitGatheringStateChange = function() {
407                 var event = new Event('icegatheringstatechange');
408                 this._dispatchEvent('icegatheringstatechange', event);
409             };
411             RTCPeerConnection.prototype.getConfiguration = function() {
412                 return this._config;
413             };
415             RTCPeerConnection.prototype.getLocalStreams = function() {
416                 return this.localStreams;
417             };
419             RTCPeerConnection.prototype.getRemoteStreams = function() {
420                 return this.remoteStreams;
421             };
423             // internal helper to create a transceiver object.
424             // (which is not yet the same as the WebRTC 1.0 transceiver)
425             RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) {
426                 var hasBundleTransport = this.transceivers.length > 0;
427                 var transceiver = {
428                     track: null,
429                     iceGatherer: null,
430                     iceTransport: null,
431                     dtlsTransport: null,
432                     localCapabilities: null,
433                     remoteCapabilities: null,
434                     rtpSender: null,
435                     rtpReceiver: null,
436                     kind: kind,
437                     mid: null,
438                     sendEncodingParameters: null,
439                     recvEncodingParameters: null,
440                     stream: null,
441                     associatedRemoteMediaStreams: [],
442                     wantReceive: true
443                 };
444                 if (this.usingBundle && hasBundleTransport) {
445                     transceiver.iceTransport = this.transceivers[0].iceTransport;
446                     transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
447                 } else {
448                     var transports = this._createIceAndDtlsTransports();
449                     transceiver.iceTransport = transports.iceTransport;
450                     transceiver.dtlsTransport = transports.dtlsTransport;
451                 }
452                 if (!doNotAdd) {
453                     this.transceivers.push(transceiver);
454                 }
455                 return transceiver;
456             };
458             RTCPeerConnection.prototype.addTrack = function(track, stream) {
459                 if (this._isClosed) {
460                     throw makeError('InvalidStateError',
461                         'Attempted to call addTrack on a closed peerconnection.');
462                 }
464                 var alreadyExists = this.transceivers.find(function(s) {
465                     return s.track === track;
466                 });
468                 if (alreadyExists) {
469                     throw makeError('InvalidAccessError', 'Track already exists.');
470                 }
472                 var transceiver;
473                 for (var i = 0; i < this.transceivers.length; i++) {
474                     if (!this.transceivers[i].track &&
475                         this.transceivers[i].kind === track.kind) {
476                         transceiver = this.transceivers[i];
477                     }
478                 }
479                 if (!transceiver) {
480                     transceiver = this._createTransceiver(track.kind);
481                 }
483                 this._maybeFireNegotiationNeeded();
485                 if (this.localStreams.indexOf(stream) === -1) {
486                     this.localStreams.push(stream);
487                 }
489                 transceiver.track = track;
490                 transceiver.stream = stream;
491                 transceiver.rtpSender = new window.RTCRtpSender(track,
492                     transceiver.dtlsTransport);
493                 return transceiver.rtpSender;
494             };
496             RTCPeerConnection.prototype.addStream = function(stream) {
497                 var pc = this;
498                 if (edgeVersion >= 15025) {
499                     stream.getTracks().forEach(function(track) {
500                         pc.addTrack(track, stream);
501                     });
502                 } else {
503                     // Clone is necessary for local demos mostly, attaching directly
504                     // to two different senders does not work (build 10547).
505                     // Fixed in 15025 (or earlier)
506                     var clonedStream = stream.clone();
507                     stream.getTracks().forEach(function(track, idx) {
508                         var clonedTrack = clonedStream.getTracks()[idx];
509                         track.addEventListener('enabled', function(event) {
510                             clonedTrack.enabled = event.enabled;
511                         });
512                     });
513                     clonedStream.getTracks().forEach(function(track) {
514                         pc.addTrack(track, clonedStream);
515                     });
516                 }
517             };
519             RTCPeerConnection.prototype.removeTrack = function(sender) {
520                 if (this._isClosed) {
521                     throw makeError('InvalidStateError',
522                         'Attempted to call removeTrack on a closed peerconnection.');
523                 }
525                 if (!(sender instanceof window.RTCRtpSender)) {
526                     throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' +
527                         'does not implement interface RTCRtpSender.');
528                 }
530                 var transceiver = this.transceivers.find(function(t) {
531                     return t.rtpSender === sender;
532                 });
534                 if (!transceiver) {
535                     throw makeError('InvalidAccessError',
536                         'Sender was not created by this connection.');
537                 }
538                 var stream = transceiver.stream;
540                 transceiver.rtpSender.stop();
541                 transceiver.rtpSender = null;
542                 transceiver.track = null;
543                 transceiver.stream = null;
545                 // remove the stream from the set of local streams
546                 var localStreams = this.transceivers.map(function(t) {
547                     return t.stream;
548                 });
549                 if (localStreams.indexOf(stream) === -1 &&
550                     this.localStreams.indexOf(stream) > -1) {
551                     this.localStreams.splice(this.localStreams.indexOf(stream), 1);
552                 }
554                 this._maybeFireNegotiationNeeded();
555             };
557             RTCPeerConnection.prototype.removeStream = function(stream) {
558                 var pc = this;
559                 stream.getTracks().forEach(function(track) {
560                     var sender = pc.getSenders().find(function(s) {
561                         return s.track === track;
562                     });
563                     if (sender) {
564                         pc.removeTrack(sender);
565                     }
566                 });
567             };
569             RTCPeerConnection.prototype.getSenders = function() {
570                 return this.transceivers.filter(function(transceiver) {
571                     return !!transceiver.rtpSender;
572                 })
573                     .map(function(transceiver) {
574                         return transceiver.rtpSender;
575                     });
576             };
578             RTCPeerConnection.prototype.getReceivers = function() {
579                 return this.transceivers.filter(function(transceiver) {
580                     return !!transceiver.rtpReceiver;
581                 })
582                     .map(function(transceiver) {
583                         return transceiver.rtpReceiver;
584                     });
585             };
588             RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,
589                                                                       usingBundle) {
590                 var pc = this;
591                 if (usingBundle && sdpMLineIndex > 0) {
592                     return this.transceivers[0].iceGatherer;
593                 } else if (this._iceGatherers.length) {
594                     return this._iceGatherers.shift();
595                 }
596                 var iceGatherer = new window.RTCIceGatherer({
597                     iceServers: this._config.iceServers,
598                     gatherPolicy: this._config.iceTransportPolicy
599                 });
600                 Object.defineProperty(iceGatherer, 'state',
601                     {value: 'new', writable: true}
602                 );
604                 this.transceivers[sdpMLineIndex].bufferedCandidateEvents = [];
605                 this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {
606                     var end = !event.candidate || Object.keys(event.candidate).length === 0;
607                     // polyfill since RTCIceGatherer.state is not implemented in
608                     // Edge 10547 yet.
609                     iceGatherer.state = end ? 'completed' : 'gathering';
610                     if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) {
611                         pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event);
612                     }
613                 };
614                 iceGatherer.addEventListener('localcandidate',
615                     this.transceivers[sdpMLineIndex].bufferCandidates);
616                 return iceGatherer;
617             };
619             // start gathering from an RTCIceGatherer.
620             RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
621                 var pc = this;
622                 var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
623                 if (iceGatherer.onlocalcandidate) {
624                     return;
625                 }
626                 var bufferedCandidateEvents =
627                     this.transceivers[sdpMLineIndex].bufferedCandidateEvents;
628                 this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null;
629                 iceGatherer.removeEventListener('localcandidate',
630                     this.transceivers[sdpMLineIndex].bufferCandidates);
631                 iceGatherer.onlocalcandidate = function(evt) {
632                     if (pc.usingBundle && sdpMLineIndex > 0) {
633                         // if we know that we use bundle we can drop candidates with
634                         // ѕdpMLineIndex > 0. If we don't do this then our state gets
635                         // confused since we dispose the extra ice gatherer.
636                         return;
637                     }
638                     var event = new Event('icecandidate');
639                     event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
641                     var cand = evt.candidate;
642                     // Edge emits an empty object for RTCIceCandidateComplete‥
643                     var end = !cand || Object.keys(cand).length === 0;
644                     if (end) {
645                         // polyfill since RTCIceGatherer.state is not implemented in
646                         // Edge 10547 yet.
647                         if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {
648                             iceGatherer.state = 'completed';
649                         }
650                     } else {
651                         if (iceGatherer.state === 'new') {
652                             iceGatherer.state = 'gathering';
653                         }
654                         // RTCIceCandidate doesn't have a component, needs to be added
655                         cand.component = 1;
656                         // also the usernameFragment. TODO: update SDP to take both variants.
657                         cand.ufrag = iceGatherer.getLocalParameters().usernameFragment;
659                         var serializedCandidate = SDPUtils.writeCandidate(cand);
660                         event.candidate = Object.assign(event.candidate,
661                             SDPUtils.parseCandidate(serializedCandidate));
663                         event.candidate.candidate = serializedCandidate;
664                         event.candidate.toJSON = function() {
665                             return {
666                                 candidate: event.candidate.candidate,
667                                 sdpMid: event.candidate.sdpMid,
668                                 sdpMLineIndex: event.candidate.sdpMLineIndex,
669                                 usernameFragment: event.candidate.usernameFragment
670                             };
671                         };
672                     }
674                     // update local description.
675                     var sections = SDPUtils.getMediaSections(pc._localDescription.sdp);
676                     if (!end) {
677                         sections[event.candidate.sdpMLineIndex] +=
678                             'a=' + event.candidate.candidate + '\r\n';
679                     } else {
680                         sections[event.candidate.sdpMLineIndex] +=
681                             'a=end-of-candidates\r\n';
682                     }
683                     pc._localDescription.sdp =
684                         SDPUtils.getDescription(pc._localDescription.sdp) +
685                         sections.join('');
686                     var complete = pc.transceivers.every(function(transceiver) {
687                         return transceiver.iceGatherer &&
688                             transceiver.iceGatherer.state === 'completed';
689                     });
691                     if (pc.iceGatheringState !== 'gathering') {
692                         pc.iceGatheringState = 'gathering';
693                         pc._emitGatheringStateChange();
694                     }
696                     // Emit candidate. Also emit null candidate when all gatherers are
697                     // complete.
698                     if (!end) {
699                         pc._dispatchEvent('icecandidate', event);
700                     }
701                     if (complete) {
702                         pc._dispatchEvent('icecandidate', new Event('icecandidate'));
703                         pc.iceGatheringState = 'complete';
704                         pc._emitGatheringStateChange();
705                     }
706                 };
708                 // emit already gathered candidates.
709                 window.setTimeout(function() {
710                     bufferedCandidateEvents.forEach(function(e) {
711                         iceGatherer.onlocalcandidate(e);
712                     });
713                 }, 0);
714             };
716             // Create ICE transport and DTLS transport.
717             RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
718                 var pc = this;
719                 var iceTransport = new window.RTCIceTransport(null);
720                 iceTransport.onicestatechange = function() {
721                     pc._updateIceConnectionState();
722                     pc._updateConnectionState();
723                 };
725                 var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
726                 dtlsTransport.ondtlsstatechange = function() {
727                     pc._updateConnectionState();
728                 };
729                 dtlsTransport.onerror = function() {
730                     // onerror does not set state to failed by itself.
731                     Object.defineProperty(dtlsTransport, 'state',
732                         {value: 'failed', writable: true});
733                     pc._updateConnectionState();
734                 };
736                 return {
737                     iceTransport: iceTransport,
738                     dtlsTransport: dtlsTransport
739                 };
740             };
742             // Destroy ICE gatherer, ICE transport and DTLS transport.
743             // Without triggering the callbacks.
744             RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
745                 sdpMLineIndex) {
746                 var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
747                 if (iceGatherer) {
748                     delete iceGatherer.onlocalcandidate;
749                     delete this.transceivers[sdpMLineIndex].iceGatherer;
750                 }
751                 var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
752                 if (iceTransport) {
753                     delete iceTransport.onicestatechange;
754                     delete this.transceivers[sdpMLineIndex].iceTransport;
755                 }
756                 var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
757                 if (dtlsTransport) {
758                     delete dtlsTransport.ondtlsstatechange;
759                     delete dtlsTransport.onerror;
760                     delete this.transceivers[sdpMLineIndex].dtlsTransport;
761                 }
762             };
764             // Start the RTP Sender and Receiver for a transceiver.
765             RTCPeerConnection.prototype._transceive = function(transceiver,
766                                                                send, recv) {
767                 var params = getCommonCapabilities(transceiver.localCapabilities,
768                     transceiver.remoteCapabilities);
769                 if (send && transceiver.rtpSender) {
770                     params.encodings = transceiver.sendEncodingParameters;
771                     params.rtcp = {
772                         cname: SDPUtils.localCName,
773                         compound: transceiver.rtcpParameters.compound
774                     };
775                     if (transceiver.recvEncodingParameters.length) {
776                         params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
777                     }
778                     transceiver.rtpSender.send(params);
779                 }
780                 if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
781                     // remove RTX field in Edge 14942
782                     if (transceiver.kind === 'video'
783                         && transceiver.recvEncodingParameters
784                         && edgeVersion < 15019) {
785                         transceiver.recvEncodingParameters.forEach(function(p) {
786                             delete p.rtx;
787                         });
788                     }
789                     if (transceiver.recvEncodingParameters.length) {
790                         params.encodings = transceiver.recvEncodingParameters;
791                     } else {
792                         params.encodings = [{}];
793                     }
794                     params.rtcp = {
795                         compound: transceiver.rtcpParameters.compound
796                     };
797                     if (transceiver.rtcpParameters.cname) {
798                         params.rtcp.cname = transceiver.rtcpParameters.cname;
799                     }
800                     if (transceiver.sendEncodingParameters.length) {
801                         params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
802                     }
803                     transceiver.rtpReceiver.receive(params);
804                 }
805             };
807             RTCPeerConnection.prototype.setLocalDescription = function(description) {
808                 var pc = this;
810                 // Note: pranswer is not supported.
811                 if (['offer', 'answer'].indexOf(description.type) === -1) {
812                     return Promise.reject(makeError('TypeError',
813                         'Unsupported type "' + description.type + '"'));
814                 }
816                 if (!isActionAllowedInSignalingState('setLocalDescription',
817                         description.type, pc.signalingState) || pc._isClosed) {
818                     return Promise.reject(makeError('InvalidStateError',
819                         'Can not set local ' + description.type +
820                         ' in state ' + pc.signalingState));
821                 }
823                 var sections;
824                 var sessionpart;
825                 if (description.type === 'offer') {
826                     // VERY limited support for SDP munging. Limited to:
827                     // * changing the order of codecs
828                     sections = SDPUtils.splitSections(description.sdp);
829                     sessionpart = sections.shift();
830                     sections.forEach(function(mediaSection, sdpMLineIndex) {
831                         var caps = SDPUtils.parseRtpParameters(mediaSection);
832                         pc.transceivers[sdpMLineIndex].localCapabilities = caps;
833                     });
835                     pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
836                         pc._gather(transceiver.mid, sdpMLineIndex);
837                     });
838                 } else if (description.type === 'answer') {
839                     sections = SDPUtils.splitSections(pc._remoteDescription.sdp);
840                     sessionpart = sections.shift();
841                     var isIceLite = SDPUtils.matchPrefix(sessionpart,
842                         'a=ice-lite').length > 0;
843                     sections.forEach(function(mediaSection, sdpMLineIndex) {
844                         var transceiver = pc.transceivers[sdpMLineIndex];
845                         var iceGatherer = transceiver.iceGatherer;
846                         var iceTransport = transceiver.iceTransport;
847                         var dtlsTransport = transceiver.dtlsTransport;
848                         var localCapabilities = transceiver.localCapabilities;
849                         var remoteCapabilities = transceiver.remoteCapabilities;
851                         // treat bundle-only as not-rejected.
852                         var rejected = SDPUtils.isRejected(mediaSection) &&
853                             SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
855                         if (!rejected && !transceiver.rejected) {
856                             var remoteIceParameters = SDPUtils.getIceParameters(
857                                 mediaSection, sessionpart);
858                             var remoteDtlsParameters = SDPUtils.getDtlsParameters(
859                                 mediaSection, sessionpart);
860                             if (isIceLite) {
861                                 remoteDtlsParameters.role = 'server';
862                             }
864                             if (!pc.usingBundle || sdpMLineIndex === 0) {
865                                 pc._gather(transceiver.mid, sdpMLineIndex);
866                                 if (iceTransport.state === 'new') {
867                                     iceTransport.start(iceGatherer, remoteIceParameters,
868                                         isIceLite ? 'controlling' : 'controlled');
869                                 }
870                                 if (dtlsTransport.state === 'new') {
871                                     dtlsTransport.start(remoteDtlsParameters);
872                                 }
873                             }
875                             // Calculate intersection of capabilities.
876                             var params = getCommonCapabilities(localCapabilities,
877                                 remoteCapabilities);
879                             // Start the RTCRtpSender. The RTCRtpReceiver for this
880                             // transceiver has already been started in setRemoteDescription.
881                             pc._transceive(transceiver,
882                                 params.codecs.length > 0,
883                                 false);
884                         }
885                     });
886                 }
888                 pc._localDescription = {
889                     type: description.type,
890                     sdp: description.sdp
891                 };
892                 if (description.type === 'offer') {
893                     pc._updateSignalingState('have-local-offer');
894                 } else {
895                     pc._updateSignalingState('stable');
896                 }
898                 return Promise.resolve();
899             };
901             RTCPeerConnection.prototype.setRemoteDescription = function(description) {
902                 var pc = this;
904                 // Note: pranswer is not supported.
905                 if (['offer', 'answer'].indexOf(description.type) === -1) {
906                     return Promise.reject(makeError('TypeError',
907                         'Unsupported type "' + description.type + '"'));
908                 }
910                 if (!isActionAllowedInSignalingState('setRemoteDescription',
911                         description.type, pc.signalingState) || pc._isClosed) {
912                     return Promise.reject(makeError('InvalidStateError',
913                         'Can not set remote ' + description.type +
914                         ' in state ' + pc.signalingState));
915                 }
917                 var streams = {};
918                 pc.remoteStreams.forEach(function(stream) {
919                     streams[stream.id] = stream;
920                 });
921                 var receiverList = [];
922                 var sections = SDPUtils.splitSections(description.sdp);
923                 var sessionpart = sections.shift();
924                 var isIceLite = SDPUtils.matchPrefix(sessionpart,
925                     'a=ice-lite').length > 0;
926                 var usingBundle = SDPUtils.matchPrefix(sessionpart,
927                     'a=group:BUNDLE ').length > 0;
928                 pc.usingBundle = usingBundle;
929                 var iceOptions = SDPUtils.matchPrefix(sessionpart,
930                     'a=ice-options:')[0];
931                 if (iceOptions) {
932                     pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
933                         .indexOf('trickle') >= 0;
934                 } else {
935                     pc.canTrickleIceCandidates = false;
936                 }
938                 sections.forEach(function(mediaSection, sdpMLineIndex) {
939                     var lines = SDPUtils.splitLines(mediaSection);
940                     var kind = SDPUtils.getKind(mediaSection);
941                     // treat bundle-only as not-rejected.
942                     var rejected = SDPUtils.isRejected(mediaSection) &&
943                         SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
944                     var protocol = lines[0].substr(2).split(' ')[2];
946                     var direction = SDPUtils.getDirection(mediaSection, sessionpart);
947                     var remoteMsid = SDPUtils.parseMsid(mediaSection);
949                     var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
951                     // Reject datachannels which are not implemented yet.
952                     if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' ||
953                             protocol === 'UDP/DTLS/SCTP'))) {
954                         // TODO: this is dangerous in the case where a non-rejected m-line
955                         //     becomes rejected.
956                         pc.transceivers[sdpMLineIndex] = {
957                             mid: mid,
958                             kind: kind,
959                             protocol: protocol,
960                             rejected: true
961                         };
962                         return;
963                     }
965                     if (!rejected && pc.transceivers[sdpMLineIndex] &&
966                         pc.transceivers[sdpMLineIndex].rejected) {
967                         // recycle a rejected transceiver.
968                         pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true);
969                     }
971                     var transceiver;
972                     var iceGatherer;
973                     var iceTransport;
974                     var dtlsTransport;
975                     var rtpReceiver;
976                     var sendEncodingParameters;
977                     var recvEncodingParameters;
978                     var localCapabilities;
980                     var track;
981                     // FIXME: ensure the mediaSection has rtcp-mux set.
982                     var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
983                     var remoteIceParameters;
984                     var remoteDtlsParameters;
985                     if (!rejected) {
986                         remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
987                             sessionpart);
988                         remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
989                             sessionpart);
990                         remoteDtlsParameters.role = 'client';
991                     }
992                     recvEncodingParameters =
993                         SDPUtils.parseRtpEncodingParameters(mediaSection);
995                     var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
997                     var isComplete = SDPUtils.matchPrefix(mediaSection,
998                         'a=end-of-candidates', sessionpart).length > 0;
999                     var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
1000                         .map(function(cand) {
1001                             return SDPUtils.parseCandidate(cand);
1002                         })
1003                         .filter(function(cand) {
1004                             return cand.component === 1;
1005                         });
1007                     // Check if we can use BUNDLE and dispose transports.
1008                     if ((description.type === 'offer' || description.type === 'answer') &&
1009                         !rejected && usingBundle && sdpMLineIndex > 0 &&
1010                         pc.transceivers[sdpMLineIndex]) {
1011                         pc._disposeIceAndDtlsTransports(sdpMLineIndex);
1012                         pc.transceivers[sdpMLineIndex].iceGatherer =
1013                             pc.transceivers[0].iceGatherer;
1014                         pc.transceivers[sdpMLineIndex].iceTransport =
1015                             pc.transceivers[0].iceTransport;
1016                         pc.transceivers[sdpMLineIndex].dtlsTransport =
1017                             pc.transceivers[0].dtlsTransport;
1018                         if (pc.transceivers[sdpMLineIndex].rtpSender) {
1019                             pc.transceivers[sdpMLineIndex].rtpSender.setTransport(
1020                                 pc.transceivers[0].dtlsTransport);
1021                         }
1022                         if (pc.transceivers[sdpMLineIndex].rtpReceiver) {
1023                             pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
1024                                 pc.transceivers[0].dtlsTransport);
1025                         }
1026                     }
1027                     if (description.type === 'offer' && !rejected) {
1028                         transceiver = pc.transceivers[sdpMLineIndex] ||
1029                             pc._createTransceiver(kind);
1030                         transceiver.mid = mid;
1032                         if (!transceiver.iceGatherer) {
1033                             transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
1034                                 usingBundle);
1035                         }
1037                         if (cands.length && transceiver.iceTransport.state === 'new') {
1038                             if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
1039                                 transceiver.iceTransport.setRemoteCandidates(cands);
1040                             } else {
1041                                 cands.forEach(function(candidate) {
1042                                     maybeAddCandidate(transceiver.iceTransport, candidate);
1043                                 });
1044                             }
1045                         }
1047                         localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
1049                         // filter RTX until additional stuff needed for RTX is implemented
1050                         // in adapter.js
1051                         if (edgeVersion < 15019) {
1052                             localCapabilities.codecs = localCapabilities.codecs.filter(
1053                                 function(codec) {
1054                                     return codec.name !== 'rtx';
1055                                 });
1056                         }
1058                         sendEncodingParameters = transceiver.sendEncodingParameters || [{
1059                             ssrc: (2 * sdpMLineIndex + 2) * 1001
1060                         }];
1062                         // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
1063                         var isNewTrack = false;
1064                         if (direction === 'sendrecv' || direction === 'sendonly') {
1065                             isNewTrack = !transceiver.rtpReceiver;
1066                             rtpReceiver = transceiver.rtpReceiver ||
1067                                 new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);
1069                             if (isNewTrack) {
1070                                 var stream;
1071                                 track = rtpReceiver.track;
1072                                 // FIXME: does not work with Plan B.
1073                                 if (remoteMsid && remoteMsid.stream === '-') {
1074                                     // no-op. a stream id of '-' means: no associated stream.
1075                                 } else if (remoteMsid) {
1076                                     if (!streams[remoteMsid.stream]) {
1077                                         streams[remoteMsid.stream] = new window.MediaStream();
1078                                         Object.defineProperty(streams[remoteMsid.stream], 'id', {
1079                                             get: function() {
1080                                                 return remoteMsid.stream;
1081                                             }
1082                                         });
1083                                     }
1084                                     Object.defineProperty(track, 'id', {
1085                                         get: function() {
1086                                             return remoteMsid.track;
1087                                         }
1088                                     });
1089                                     stream = streams[remoteMsid.stream];
1090                                 } else {
1091                                     if (!streams.default) {
1092                                         streams.default = new window.MediaStream();
1093                                     }
1094                                     stream = streams.default;
1095                                 }
1096                                 if (stream) {
1097                                     addTrackToStreamAndFireEvent(track, stream);
1098                                     transceiver.associatedRemoteMediaStreams.push(stream);
1099                                 }
1100                                 receiverList.push([track, rtpReceiver, stream]);
1101                             }
1102                         } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) {
1103                             transceiver.associatedRemoteMediaStreams.forEach(function(s) {
1104                                 var nativeTrack = s.getTracks().find(function(t) {
1105                                     return t.id === transceiver.rtpReceiver.track.id;
1106                                 });
1107                                 if (nativeTrack) {
1108                                     removeTrackFromStreamAndFireEvent(nativeTrack, s);
1109                                 }
1110                             });
1111                             transceiver.associatedRemoteMediaStreams = [];
1112                         }
1114                         transceiver.localCapabilities = localCapabilities;
1115                         transceiver.remoteCapabilities = remoteCapabilities;
1116                         transceiver.rtpReceiver = rtpReceiver;
1117                         transceiver.rtcpParameters = rtcpParameters;
1118                         transceiver.sendEncodingParameters = sendEncodingParameters;
1119                         transceiver.recvEncodingParameters = recvEncodingParameters;
1121                         // Start the RTCRtpReceiver now. The RTPSender is started in
1122                         // setLocalDescription.
1123                         pc._transceive(pc.transceivers[sdpMLineIndex],
1124                             false,
1125                             isNewTrack);
1126                     } else if (description.type === 'answer' && !rejected) {
1127                         transceiver = pc.transceivers[sdpMLineIndex];
1128                         iceGatherer = transceiver.iceGatherer;
1129                         iceTransport = transceiver.iceTransport;
1130                         dtlsTransport = transceiver.dtlsTransport;
1131                         rtpReceiver = transceiver.rtpReceiver;
1132                         sendEncodingParameters = transceiver.sendEncodingParameters;
1133                         localCapabilities = transceiver.localCapabilities;
1135                         pc.transceivers[sdpMLineIndex].recvEncodingParameters =
1136                             recvEncodingParameters;
1137                         pc.transceivers[sdpMLineIndex].remoteCapabilities =
1138                             remoteCapabilities;
1139                         pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
1141                         if (cands.length && iceTransport.state === 'new') {
1142                             if ((isIceLite || isComplete) &&
1143                                 (!usingBundle || sdpMLineIndex === 0)) {
1144                                 iceTransport.setRemoteCandidates(cands);
1145                             } else {
1146                                 cands.forEach(function(candidate) {
1147                                     maybeAddCandidate(transceiver.iceTransport, candidate);
1148                                 });
1149                             }
1150                         }
1152                         if (!usingBundle || sdpMLineIndex === 0) {
1153                             if (iceTransport.state === 'new') {
1154                                 iceTransport.start(iceGatherer, remoteIceParameters,
1155                                     'controlling');
1156                             }
1157                             if (dtlsTransport.state === 'new') {
1158                                 dtlsTransport.start(remoteDtlsParameters);
1159                             }
1160                         }
1162                         // If the offer contained RTX but the answer did not,
1163                         // remove RTX from sendEncodingParameters.
1164                         var commonCapabilities = getCommonCapabilities(
1165                             transceiver.localCapabilities,
1166                             transceiver.remoteCapabilities);
1168                         var hasRtx = commonCapabilities.codecs.filter(function(c) {
1169                             return c.name.toLowerCase() === 'rtx';
1170                         }).length;
1171                         if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
1172                             delete transceiver.sendEncodingParameters[0].rtx;
1173                         }
1175                         pc._transceive(transceiver,
1176                             direction === 'sendrecv' || direction === 'recvonly',
1177                             direction === 'sendrecv' || direction === 'sendonly');
1179                         // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
1180                         if (rtpReceiver &&
1181                             (direction === 'sendrecv' || direction === 'sendonly')) {
1182                             track = rtpReceiver.track;
1183                             if (remoteMsid) {
1184                                 if (!streams[remoteMsid.stream]) {
1185                                     streams[remoteMsid.stream] = new window.MediaStream();
1186                                 }
1187                                 addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]);
1188                                 receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
1189                             } else {
1190                                 if (!streams.default) {
1191                                     streams.default = new window.MediaStream();
1192                                 }
1193                                 addTrackToStreamAndFireEvent(track, streams.default);
1194                                 receiverList.push([track, rtpReceiver, streams.default]);
1195                             }
1196                         } else {
1197                             // FIXME: actually the receiver should be created later.
1198                             delete transceiver.rtpReceiver;
1199                         }
1200                     }
1201                 });
1203                 if (pc._dtlsRole === undefined) {
1204                     pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive';
1205                 }
1207                 pc._remoteDescription = {
1208                     type: description.type,
1209                     sdp: description.sdp
1210                 };
1211                 if (description.type === 'offer') {
1212                     pc._updateSignalingState('have-remote-offer');
1213                 } else {
1214                     pc._updateSignalingState('stable');
1215                 }
1216                 Object.keys(streams).forEach(function(sid) {
1217                     var stream = streams[sid];
1218                     if (stream.getTracks().length) {
1219                         if (pc.remoteStreams.indexOf(stream) === -1) {
1220                             pc.remoteStreams.push(stream);
1221                             var event = new Event('addstream');
1222                             event.stream = stream;
1223                             window.setTimeout(function() {
1224                                 pc._dispatchEvent('addstream', event);
1225                             });
1226                         }
1228                         receiverList.forEach(function(item) {
1229                             var track = item[0];
1230                             var receiver = item[1];
1231                             if (stream.id !== item[2].id) {
1232                                 return;
1233                             }
1234                             fireAddTrack(pc, track, receiver, [stream]);
1235                         });
1236                     }
1237                 });
1238                 receiverList.forEach(function(item) {
1239                     if (item[2]) {
1240                         return;
1241                     }
1242                     fireAddTrack(pc, item[0], item[1], []);
1243                 });
1245                 // check whether addIceCandidate({}) was called within four seconds after
1246                 // setRemoteDescription.
1247                 window.setTimeout(function() {
1248                     if (!(pc && pc.transceivers)) {
1249                         return;
1250                     }
1251                     pc.transceivers.forEach(function(transceiver) {
1252                         if (transceiver.iceTransport &&
1253                             transceiver.iceTransport.state === 'new' &&
1254                             transceiver.iceTransport.getRemoteCandidates().length > 0) {
1255                             console.warn('Timeout for addRemoteCandidate. Consider sending ' +
1256                                 'an end-of-candidates notification');
1257                             transceiver.iceTransport.addRemoteCandidate({});
1258                         }
1259                     });
1260                 }, 4000);
1262                 return Promise.resolve();
1263             };
1265             RTCPeerConnection.prototype.close = function() {
1266                 this.transceivers.forEach(function(transceiver) {
1267                     /* not yet
1268                     if (transceiver.iceGatherer) {
1269                         transceiver.iceGatherer.close();
1270                     }
1271                     */
1272                     if (transceiver.iceTransport) {
1273                         transceiver.iceTransport.stop();
1274                     }
1275                     if (transceiver.dtlsTransport) {
1276                         transceiver.dtlsTransport.stop();
1277                     }
1278                     if (transceiver.rtpSender) {
1279                         transceiver.rtpSender.stop();
1280                     }
1281                     if (transceiver.rtpReceiver) {
1282                         transceiver.rtpReceiver.stop();
1283                     }
1284                 });
1285                 // FIXME: clean up tracks, local streams, remote streams, etc
1286                 this._isClosed = true;
1287                 this._updateSignalingState('closed');
1288             };
1290             // Update the signaling state.
1291             RTCPeerConnection.prototype._updateSignalingState = function(newState) {
1292                 this.signalingState = newState;
1293                 var event = new Event('signalingstatechange');
1294                 this._dispatchEvent('signalingstatechange', event);
1295             };
1297             // Determine whether to fire the negotiationneeded event.
1298             RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
1299                 var pc = this;
1300                 if (this.signalingState !== 'stable' || this.needNegotiation === true) {
1301                     return;
1302                 }
1303                 this.needNegotiation = true;
1304                 window.setTimeout(function() {
1305                     if (pc.needNegotiation) {
1306                         pc.needNegotiation = false;
1307                         var event = new Event('negotiationneeded');
1308                         pc._dispatchEvent('negotiationneeded', event);
1309                     }
1310                 }, 0);
1311             };
1313             // Update the ice connection state.
1314             RTCPeerConnection.prototype._updateIceConnectionState = function() {
1315                 var newState;
1316                 var states = {
1317                     'new': 0,
1318                     closed: 0,
1319                     checking: 0,
1320                     connected: 0,
1321                     completed: 0,
1322                     disconnected: 0,
1323                     failed: 0
1324                 };
1325                 this.transceivers.forEach(function(transceiver) {
1326                     states[transceiver.iceTransport.state]++;
1327                 });
1329                 newState = 'new';
1330                 if (states.failed > 0) {
1331                     newState = 'failed';
1332                 } else if (states.checking > 0) {
1333                     newState = 'checking';
1334                 } else if (states.disconnected > 0) {
1335                     newState = 'disconnected';
1336                 } else if (states.new > 0) {
1337                     newState = 'new';
1338                 } else if (states.connected > 0) {
1339                     newState = 'connected';
1340                 } else if (states.completed > 0) {
1341                     newState = 'completed';
1342                 }
1344                 if (newState !== this.iceConnectionState) {
1345                     this.iceConnectionState = newState;
1346                     var event = new Event('iceconnectionstatechange');
1347                     this._dispatchEvent('iceconnectionstatechange', event);
1348                 }
1349             };
1351             // Update the connection state.
1352             RTCPeerConnection.prototype._updateConnectionState = function() {
1353                 var newState;
1354                 var states = {
1355                     'new': 0,
1356                     closed: 0,
1357                     connecting: 0,
1358                     connected: 0,
1359                     completed: 0,
1360                     disconnected: 0,
1361                     failed: 0
1362                 };
1363                 this.transceivers.forEach(function(transceiver) {
1364                     states[transceiver.iceTransport.state]++;
1365                     states[transceiver.dtlsTransport.state]++;
1366                 });
1367                 // ICETransport.completed and connected are the same for this purpose.
1368                 states.connected += states.completed;
1370                 newState = 'new';
1371                 if (states.failed > 0) {
1372                     newState = 'failed';
1373                 } else if (states.connecting > 0) {
1374                     newState = 'connecting';
1375                 } else if (states.disconnected > 0) {
1376                     newState = 'disconnected';
1377                 } else if (states.new > 0) {
1378                     newState = 'new';
1379                 } else if (states.connected > 0) {
1380                     newState = 'connected';
1381                 }
1383                 if (newState !== this.connectionState) {
1384                     this.connectionState = newState;
1385                     var event = new Event('connectionstatechange');
1386                     this._dispatchEvent('connectionstatechange', event);
1387                 }
1388             };
1390             RTCPeerConnection.prototype.createOffer = function() {
1391                 var pc = this;
1393                 if (pc._isClosed) {
1394                     return Promise.reject(makeError('InvalidStateError',
1395                         'Can not call createOffer after close'));
1396                 }
1398                 var numAudioTracks = pc.transceivers.filter(function(t) {
1399                     return t.kind === 'audio';
1400                 }).length;
1401                 var numVideoTracks = pc.transceivers.filter(function(t) {
1402                     return t.kind === 'video';
1403                 }).length;
1405                 // Determine number of audio and video tracks we need to send/recv.
1406                 var offerOptions = arguments[0];
1407                 if (offerOptions) {
1408                     // Reject Chrome legacy constraints.
1409                     if (offerOptions.mandatory || offerOptions.optional) {
1410                         throw new TypeError(
1411                             'Legacy mandatory/optional constraints not supported.');
1412                     }
1413                     if (offerOptions.offerToReceiveAudio !== undefined) {
1414                         if (offerOptions.offerToReceiveAudio === true) {
1415                             numAudioTracks = 1;
1416                         } else if (offerOptions.offerToReceiveAudio === false) {
1417                             numAudioTracks = 0;
1418                         } else {
1419                             numAudioTracks = offerOptions.offerToReceiveAudio;
1420                         }
1421                     }
1422                     if (offerOptions.offerToReceiveVideo !== undefined) {
1423                         if (offerOptions.offerToReceiveVideo === true) {
1424                             numVideoTracks = 1;
1425                         } else if (offerOptions.offerToReceiveVideo === false) {
1426                             numVideoTracks = 0;
1427                         } else {
1428                             numVideoTracks = offerOptions.offerToReceiveVideo;
1429                         }
1430                     }
1431                 }
1433                 pc.transceivers.forEach(function(transceiver) {
1434                     if (transceiver.kind === 'audio') {
1435                         numAudioTracks--;
1436                         if (numAudioTracks < 0) {
1437                             transceiver.wantReceive = false;
1438                         }
1439                     } else if (transceiver.kind === 'video') {
1440                         numVideoTracks--;
1441                         if (numVideoTracks < 0) {
1442                             transceiver.wantReceive = false;
1443                         }
1444                     }
1445                 });
1447                 // Create M-lines for recvonly streams.
1448                 while (numAudioTracks > 0 || numVideoTracks > 0) {
1449                     if (numAudioTracks > 0) {
1450                         pc._createTransceiver('audio');
1451                         numAudioTracks--;
1452                     }
1453                     if (numVideoTracks > 0) {
1454                         pc._createTransceiver('video');
1455                         numVideoTracks--;
1456                     }
1457                 }
1459                 var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,
1460                     pc._sdpSessionVersion++);
1461                 pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
1462                     // For each track, create an ice gatherer, ice transport,
1463                     // dtls transport, potentially rtpsender and rtpreceiver.
1464                     var track = transceiver.track;
1465                     var kind = transceiver.kind;
1466                     var mid = transceiver.mid || SDPUtils.generateIdentifier();
1467                     transceiver.mid = mid;
1469                     if (!transceiver.iceGatherer) {
1470                         transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
1471                             pc.usingBundle);
1472                     }
1474                     var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
1475                     // filter RTX until additional stuff needed for RTX is implemented
1476                     // in adapter.js
1477                     if (edgeVersion < 15019) {
1478                         localCapabilities.codecs = localCapabilities.codecs.filter(
1479                             function(codec) {
1480                                 return codec.name !== 'rtx';
1481                             });
1482                     }
1483                     localCapabilities.codecs.forEach(function(codec) {
1484                         // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
1485                         // by adding level-asymmetry-allowed=1
1486                         if (codec.name === 'H264' &&
1487                             codec.parameters['level-asymmetry-allowed'] === undefined) {
1488                             codec.parameters['level-asymmetry-allowed'] = '1';
1489                         }
1491                         // for subsequent offers, we might have to re-use the payload
1492                         // type of the last offer.
1493                         if (transceiver.remoteCapabilities &&
1494                             transceiver.remoteCapabilities.codecs) {
1495                             transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) {
1496                                 if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() &&
1497                                     codec.clockRate === remoteCodec.clockRate) {
1498                                     codec.preferredPayloadType = remoteCodec.payloadType;
1499                                 }
1500                             });
1501                         }
1502                     });
1503                     localCapabilities.headerExtensions.forEach(function(hdrExt) {
1504                         var remoteExtensions = transceiver.remoteCapabilities &&
1505                             transceiver.remoteCapabilities.headerExtensions || [];
1506                         remoteExtensions.forEach(function(rHdrExt) {
1507                             if (hdrExt.uri === rHdrExt.uri) {
1508                                 hdrExt.id = rHdrExt.id;
1509                             }
1510                         });
1511                     });
1513                     // generate an ssrc now, to be used later in rtpSender.send
1514                     var sendEncodingParameters = transceiver.sendEncodingParameters || [{
1515                         ssrc: (2 * sdpMLineIndex + 1) * 1001
1516                     }];
1517                     if (track) {
1518                         // add RTX
1519                         if (edgeVersion >= 15019 && kind === 'video' &&
1520                             !sendEncodingParameters[0].rtx) {
1521                             sendEncodingParameters[0].rtx = {
1522                                 ssrc: sendEncodingParameters[0].ssrc + 1
1523                             };
1524                         }
1525                     }
1527                     if (transceiver.wantReceive) {
1528                         transceiver.rtpReceiver = new window.RTCRtpReceiver(
1529                             transceiver.dtlsTransport, kind);
1530                     }
1532                     transceiver.localCapabilities = localCapabilities;
1533                     transceiver.sendEncodingParameters = sendEncodingParameters;
1534                 });
1536                 // always offer BUNDLE and dispose on return if not supported.
1537                 if (pc._config.bundlePolicy !== 'max-compat') {
1538                     sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) {
1539                         return t.mid;
1540                     }).join(' ') + '\r\n';
1541                 }
1542                 sdp += 'a=ice-options:trickle\r\n';
1544                 pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
1545                     sdp += writeMediaSection(transceiver, transceiver.localCapabilities,
1546                         'offer', transceiver.stream, pc._dtlsRole);
1547                     sdp += 'a=rtcp-rsize\r\n';
1549                     if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' &&
1550                         (sdpMLineIndex === 0 || !pc.usingBundle)) {
1551                         transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {
1552                             cand.component = 1;
1553                             sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
1554                         });
1556                         if (transceiver.iceGatherer.state === 'completed') {
1557                             sdp += 'a=end-of-candidates\r\n';
1558                         }
1559                     }
1560                 });
1562                 var desc = new window.RTCSessionDescription({
1563                     type: 'offer',
1564                     sdp: sdp
1565                 });
1566                 return Promise.resolve(desc);
1567             };
1569             RTCPeerConnection.prototype.createAnswer = function() {
1570                 var pc = this;
1572                 if (pc._isClosed) {
1573                     return Promise.reject(makeError('InvalidStateError',
1574                         'Can not call createAnswer after close'));
1575                 }
1577                 if (!(pc.signalingState === 'have-remote-offer' ||
1578                         pc.signalingState === 'have-local-pranswer')) {
1579                     return Promise.reject(makeError('InvalidStateError',
1580                         'Can not call createAnswer in signalingState ' + pc.signalingState));
1581                 }
1583                 var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,
1584                     pc._sdpSessionVersion++);
1585                 if (pc.usingBundle) {
1586                     sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) {
1587                         return t.mid;
1588                     }).join(' ') + '\r\n';
1589                 }
1590                 sdp += 'a=ice-options:trickle\r\n';
1592                 var mediaSectionsInOffer = SDPUtils.getMediaSections(
1593                     pc._remoteDescription.sdp).length;
1594                 pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
1595                     if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
1596                         return;
1597                     }
1598                     if (transceiver.rejected) {
1599                         if (transceiver.kind === 'application') {
1600                             if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt
1601                                 sdp += 'm=application 0 DTLS/SCTP 5000\r\n';
1602                             } else {
1603                                 sdp += 'm=application 0 ' + transceiver.protocol +
1604                                     ' webrtc-datachannel\r\n';
1605                             }
1606                         } else if (transceiver.kind === 'audio') {
1607                             sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' +
1608                                 'a=rtpmap:0 PCMU/8000\r\n';
1609                         } else if (transceiver.kind === 'video') {
1610                             sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' +
1611                                 'a=rtpmap:120 VP8/90000\r\n';
1612                         }
1613                         sdp += 'c=IN IP4 0.0.0.0\r\n' +
1614                             'a=inactive\r\n' +
1615                             'a=mid:' + transceiver.mid + '\r\n';
1616                         return;
1617                     }
1619                     // FIXME: look at direction.
1620                     if (transceiver.stream) {
1621                         var localTrack;
1622                         if (transceiver.kind === 'audio') {
1623                             localTrack = transceiver.stream.getAudioTracks()[0];
1624                         } else if (transceiver.kind === 'video') {
1625                             localTrack = transceiver.stream.getVideoTracks()[0];
1626                         }
1627                         if (localTrack) {
1628                             // add RTX
1629                             if (edgeVersion >= 15019 && transceiver.kind === 'video' &&
1630                                 !transceiver.sendEncodingParameters[0].rtx) {
1631                                 transceiver.sendEncodingParameters[0].rtx = {
1632                                     ssrc: transceiver.sendEncodingParameters[0].ssrc + 1
1633                                 };
1634                             }
1635                         }
1636                     }
1638                     // Calculate intersection of capabilities.
1639                     var commonCapabilities = getCommonCapabilities(
1640                         transceiver.localCapabilities,
1641                         transceiver.remoteCapabilities);
1643                     var hasRtx = commonCapabilities.codecs.filter(function(c) {
1644                         return c.name.toLowerCase() === 'rtx';
1645                     }).length;
1646                     if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
1647                         delete transceiver.sendEncodingParameters[0].rtx;
1648                     }
1650                     sdp += writeMediaSection(transceiver, commonCapabilities,
1651                         'answer', transceiver.stream, pc._dtlsRole);
1652                     if (transceiver.rtcpParameters &&
1653                         transceiver.rtcpParameters.reducedSize) {
1654                         sdp += 'a=rtcp-rsize\r\n';
1655                     }
1656                 });
1658                 var desc = new window.RTCSessionDescription({
1659                     type: 'answer',
1660                     sdp: sdp
1661                 });
1662                 return Promise.resolve(desc);
1663             };
1665             RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
1666                 var pc = this;
1667                 var sections;
1668                 if (candidate && !(candidate.sdpMLineIndex !== undefined ||
1669                         candidate.sdpMid)) {
1670                     return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required'));
1671                 }
1673                 // TODO: needs to go into ops queue.
1674                 return new Promise(function(resolve, reject) {
1675                     if (!pc._remoteDescription) {
1676                         return reject(makeError('InvalidStateError',
1677                             'Can not add ICE candidate without a remote description'));
1678                     } else if (!candidate || candidate.candidate === '') {
1679                         for (var j = 0; j < pc.transceivers.length; j++) {
1680                             if (pc.transceivers[j].rejected) {
1681                                 continue;
1682                             }
1683                             pc.transceivers[j].iceTransport.addRemoteCandidate({});
1684                             sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp);
1685                             sections[j] += 'a=end-of-candidates\r\n';
1686                             pc._remoteDescription.sdp =
1687                                 SDPUtils.getDescription(pc._remoteDescription.sdp) +
1688                                 sections.join('');
1689                             if (pc.usingBundle) {
1690                                 break;
1691                             }
1692                         }
1693                     } else {
1694                         var sdpMLineIndex = candidate.sdpMLineIndex;
1695                         if (candidate.sdpMid) {
1696                             for (var i = 0; i < pc.transceivers.length; i++) {
1697                                 if (pc.transceivers[i].mid === candidate.sdpMid) {
1698                                     sdpMLineIndex = i;
1699                                     break;
1700                                 }
1701                             }
1702                         }
1703                         var transceiver = pc.transceivers[sdpMLineIndex];
1704                         if (transceiver) {
1705                             if (transceiver.rejected) {
1706                                 return resolve();
1707                             }
1708                             var cand = Object.keys(candidate.candidate).length > 0 ?
1709                                 SDPUtils.parseCandidate(candidate.candidate) : {};
1710                             // Ignore Chrome's invalid candidates since Edge does not like them.
1711                             if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
1712                                 return resolve();
1713                             }
1714                             // Ignore RTCP candidates, we assume RTCP-MUX.
1715                             if (cand.component && cand.component !== 1) {
1716                                 return resolve();
1717                             }
1718                             // when using bundle, avoid adding candidates to the wrong
1719                             // ice transport. And avoid adding candidates added in the SDP.
1720                             if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 &&
1721                                     transceiver.iceTransport !== pc.transceivers[0].iceTransport)) {
1722                                 if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
1723                                     return reject(makeError('OperationError',
1724                                         'Can not add ICE candidate'));
1725                                 }
1726                             }
1728                             // update the remoteDescription.
1729                             var candidateString = candidate.candidate.trim();
1730                             if (candidateString.indexOf('a=') === 0) {
1731                                 candidateString = candidateString.substr(2);
1732                             }
1733                             sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp);
1734                             sections[sdpMLineIndex] += 'a=' +
1735                                 (cand.type ? candidateString : 'end-of-candidates')
1736                                 + '\r\n';
1737                             pc._remoteDescription.sdp =
1738                                 SDPUtils.getDescription(pc._remoteDescription.sdp) +
1739                                 sections.join('');
1740                         } else {
1741                             return reject(makeError('OperationError',
1742                                 'Can not add ICE candidate'));
1743                         }
1744                     }
1745                     resolve();
1746                 });
1747             };
1749             RTCPeerConnection.prototype.getStats = function(selector) {
1750                 if (selector && selector instanceof window.MediaStreamTrack) {
1751                     var senderOrReceiver = null;
1752                     this.transceivers.forEach(function(transceiver) {
1753                         if (transceiver.rtpSender &&
1754                             transceiver.rtpSender.track === selector) {
1755                             senderOrReceiver = transceiver.rtpSender;
1756                         } else if (transceiver.rtpReceiver &&
1757                             transceiver.rtpReceiver.track === selector) {
1758                             senderOrReceiver = transceiver.rtpReceiver;
1759                         }
1760                     });
1761                     if (!senderOrReceiver) {
1762                         throw makeError('InvalidAccessError', 'Invalid selector.');
1763                     }
1764                     return senderOrReceiver.getStats();
1765                 }
1767                 var promises = [];
1768                 this.transceivers.forEach(function(transceiver) {
1769                     ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
1770                         'dtlsTransport'].forEach(function(method) {
1771                         if (transceiver[method]) {
1772                             promises.push(transceiver[method].getStats());
1773                         }
1774                     });
1775                 });
1776                 return Promise.all(promises).then(function(allStats) {
1777                     var results = new Map();
1778                     allStats.forEach(function(stats) {
1779                         stats.forEach(function(stat) {
1780                             results.set(stat.id, stat);
1781                         });
1782                     });
1783                     return results;
1784                 });
1785             };
1787             // fix low-level stat names and return Map instead of object.
1788             var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer',
1789                 'RTCIceTransport', 'RTCDtlsTransport'];
1790             ortcObjects.forEach(function(ortcObjectName) {
1791                 var obj = window[ortcObjectName];
1792                 if (obj && obj.prototype && obj.prototype.getStats) {
1793                     var nativeGetstats = obj.prototype.getStats;
1794                     obj.prototype.getStats = function() {
1795                         return nativeGetstats.apply(this)
1796                             .then(function(nativeStats) {
1797                                 var mapStats = new Map();
1798                                 Object.keys(nativeStats).forEach(function(id) {
1799                                     nativeStats[id].type = fixStatsType(nativeStats[id]);
1800                                     mapStats.set(id, nativeStats[id]);
1801                                 });
1802                                 return mapStats;
1803                             });
1804                     };
1805                 }
1806             });
1808             // legacy callback shims. Should be moved to adapter.js some days.
1809             var methods = ['createOffer', 'createAnswer'];
1810             methods.forEach(function(method) {
1811                 var nativeMethod = RTCPeerConnection.prototype[method];
1812                 RTCPeerConnection.prototype[method] = function() {
1813                     var args = arguments;
1814                     if (typeof args[0] === 'function' ||
1815                         typeof args[1] === 'function') { // legacy
1816                         return nativeMethod.apply(this, [arguments[2]])
1817                             .then(function(description) {
1818                                 if (typeof args[0] === 'function') {
1819                                     args[0].apply(null, [description]);
1820                                 }
1821                             }, function(error) {
1822                                 if (typeof args[1] === 'function') {
1823                                     args[1].apply(null, [error]);
1824                                 }
1825                             });
1826                     }
1827                     return nativeMethod.apply(this, arguments);
1828                 };
1829             });
1831             methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'];
1832             methods.forEach(function(method) {
1833                 var nativeMethod = RTCPeerConnection.prototype[method];
1834                 RTCPeerConnection.prototype[method] = function() {
1835                     var args = arguments;
1836                     if (typeof args[1] === 'function' ||
1837                         typeof args[2] === 'function') { // legacy
1838                         return nativeMethod.apply(this, arguments)
1839                             .then(function() {
1840                                 if (typeof args[1] === 'function') {
1841                                     args[1].apply(null);
1842                                 }
1843                             }, function(error) {
1844                                 if (typeof args[2] === 'function') {
1845                                     args[2].apply(null, [error]);
1846                                 }
1847                             });
1848                     }
1849                     return nativeMethod.apply(this, arguments);
1850                 };
1851             });
1853             // getStats is special. It doesn't have a spec legacy method yet we support
1854             // getStats(something, cb) without error callbacks.
1855             ['getStats'].forEach(function(method) {
1856                 var nativeMethod = RTCPeerConnection.prototype[method];
1857                 RTCPeerConnection.prototype[method] = function() {
1858                     var args = arguments;
1859                     if (typeof args[1] === 'function') {
1860                         return nativeMethod.apply(this, arguments)
1861                             .then(function() {
1862                                 if (typeof args[1] === 'function') {
1863                                     args[1].apply(null);
1864                                 }
1865                             });
1866                     }
1867                     return nativeMethod.apply(this, arguments);
1868                 };
1869             });
1871             return RTCPeerConnection;
1872         };
1874     },{"sdp":2}],2:[function(require,module,exports){
1875         /* eslint-env node */
1876         'use strict';
1878         // SDP helpers.
1879         var SDPUtils = {};
1881         // Generate an alphanumeric identifier for cname or mids.
1882         // TODO: use UUIDs instead? https://gist.github.com/jed/982883
1883         SDPUtils.generateIdentifier = function() {
1884             return Math.random().toString(36).substr(2, 10);
1885         };
1887         // The RTCP CNAME used by all peerconnections from the same JS.
1888         SDPUtils.localCName = SDPUtils.generateIdentifier();
1890         // Splits SDP into lines, dealing with both CRLF and LF.
1891         SDPUtils.splitLines = function(blob) {
1892             return blob.trim().split('\n').map(function(line) {
1893                 return line.trim();
1894             });
1895         };
1896         // Splits SDP into sessionpart and mediasections. Ensures CRLF.
1897         SDPUtils.splitSections = function(blob) {
1898             var parts = blob.split('\nm=');
1899             return parts.map(function(part, index) {
1900                 return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
1901             });
1902         };
1904         // returns the session description.
1905         SDPUtils.getDescription = function(blob) {
1906             var sections = SDPUtils.splitSections(blob);
1907             return sections && sections[0];
1908         };
1910         // returns the individual media sections.
1911         SDPUtils.getMediaSections = function(blob) {
1912             var sections = SDPUtils.splitSections(blob);
1913             sections.shift();
1914             return sections;
1915         };
1917         // Returns lines that start with a certain prefix.
1918         SDPUtils.matchPrefix = function(blob, prefix) {
1919             return SDPUtils.splitLines(blob).filter(function(line) {
1920                 return line.indexOf(prefix) === 0;
1921             });
1922         };
1924         // Parses an ICE candidate line. Sample input:
1925         // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
1926         // rport 55996"
1927         SDPUtils.parseCandidate = function(line) {
1928             var parts;
1929             // Parse both variants.
1930             if (line.indexOf('a=candidate:') === 0) {
1931                 parts = line.substring(12).split(' ');
1932             } else {
1933                 parts = line.substring(10).split(' ');
1934             }
1936             var candidate = {
1937                 foundation: parts[0],
1938                 component: parseInt(parts[1], 10),
1939                 protocol: parts[2].toLowerCase(),
1940                 priority: parseInt(parts[3], 10),
1941                 ip: parts[4],
1942                 address: parts[4], // address is an alias for ip.
1943                 port: parseInt(parts[5], 10),
1944                 // skip parts[6] == 'typ'
1945                 type: parts[7]
1946             };
1948             for (var i = 8; i < parts.length; i += 2) {
1949                 switch (parts[i]) {
1950                     case 'raddr':
1951                         candidate.relatedAddress = parts[i + 1];
1952                         break;
1953                     case 'rport':
1954                         candidate.relatedPort = parseInt(parts[i + 1], 10);
1955                         break;
1956                     case 'tcptype':
1957                         candidate.tcpType = parts[i + 1];
1958                         break;
1959                     case 'ufrag':
1960                         candidate.ufrag = parts[i + 1]; // for backward compability.
1961                         candidate.usernameFragment = parts[i + 1];
1962                         break;
1963                     default: // extension handling, in particular ufrag
1964                         candidate[parts[i]] = parts[i + 1];
1965                         break;
1966                 }
1967             }
1968             return candidate;
1969         };
1971         // Translates a candidate object into SDP candidate attribute.
1972         SDPUtils.writeCandidate = function(candidate) {
1973             var sdp = [];
1974             sdp.push(candidate.foundation);
1975             sdp.push(candidate.component);
1976             sdp.push(candidate.protocol.toUpperCase());
1977             sdp.push(candidate.priority);
1978             sdp.push(candidate.address || candidate.ip);
1979             sdp.push(candidate.port);
1981             var type = candidate.type;
1982             sdp.push('typ');
1983             sdp.push(type);
1984             if (type !== 'host' && candidate.relatedAddress &&
1985                 candidate.relatedPort) {
1986                 sdp.push('raddr');
1987                 sdp.push(candidate.relatedAddress);
1988                 sdp.push('rport');
1989                 sdp.push(candidate.relatedPort);
1990             }
1991             if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
1992                 sdp.push('tcptype');
1993                 sdp.push(candidate.tcpType);
1994             }
1995             if (candidate.usernameFragment || candidate.ufrag) {
1996                 sdp.push('ufrag');
1997                 sdp.push(candidate.usernameFragment || candidate.ufrag);
1998             }
1999             return 'candidate:' + sdp.join(' ');
2000         };
2002         // Parses an ice-options line, returns an array of option tags.
2003         // a=ice-options:foo bar
2004         SDPUtils.parseIceOptions = function(line) {
2005             return line.substr(14).split(' ');
2006         };
2008         // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
2009         // a=rtpmap:111 opus/48000/2
2010         SDPUtils.parseRtpMap = function(line) {
2011             var parts = line.substr(9).split(' ');
2012             var parsed = {
2013                 payloadType: parseInt(parts.shift(), 10) // was: id
2014             };
2016             parts = parts[0].split('/');
2018             parsed.name = parts[0];
2019             parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
2020             parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
2021             // legacy alias, got renamed back to channels in ORTC.
2022             parsed.numChannels = parsed.channels;
2023             return parsed;
2024         };
2026         // Generate an a=rtpmap line from RTCRtpCodecCapability or
2027         // RTCRtpCodecParameters.
2028         SDPUtils.writeRtpMap = function(codec) {
2029             var pt = codec.payloadType;
2030             if (codec.preferredPayloadType !== undefined) {
2031                 pt = codec.preferredPayloadType;
2032             }
2033             var channels = codec.channels || codec.numChannels || 1;
2034             return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
2035                 (channels !== 1 ? '/' + channels : '') + '\r\n';
2036         };
2038         // Parses an a=extmap line (headerextension from RFC 5285). Sample input:
2039         // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
2040         // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
2041         SDPUtils.parseExtmap = function(line) {
2042             var parts = line.substr(9).split(' ');
2043             return {
2044                 id: parseInt(parts[0], 10),
2045                 direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
2046                 uri: parts[1]
2047             };
2048         };
2050         // Generates a=extmap line from RTCRtpHeaderExtensionParameters or
2051         // RTCRtpHeaderExtension.
2052         SDPUtils.writeExtmap = function(headerExtension) {
2053             return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
2054                 (headerExtension.direction && headerExtension.direction !== 'sendrecv'
2055                     ? '/' + headerExtension.direction
2056                     : '') +
2057                 ' ' + headerExtension.uri + '\r\n';
2058         };
2060         // Parses an ftmp line, returns dictionary. Sample input:
2061         // a=fmtp:96 vbr=on;cng=on
2062         // Also deals with vbr=on; cng=on
2063         SDPUtils.parseFmtp = function(line) {
2064             var parsed = {};
2065             var kv;
2066             var parts = line.substr(line.indexOf(' ') + 1).split(';');
2067             for (var j = 0; j < parts.length; j++) {
2068                 kv = parts[j].trim().split('=');
2069                 parsed[kv[0].trim()] = kv[1];
2070             }
2071             return parsed;
2072         };
2074         // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
2075         SDPUtils.writeFmtp = function(codec) {
2076             var line = '';
2077             var pt = codec.payloadType;
2078             if (codec.preferredPayloadType !== undefined) {
2079                 pt = codec.preferredPayloadType;
2080             }
2081             if (codec.parameters && Object.keys(codec.parameters).length) {
2082                 var params = [];
2083                 Object.keys(codec.parameters).forEach(function(param) {
2084                     if (codec.parameters[param]) {
2085                         params.push(param + '=' + codec.parameters[param]);
2086                     } else {
2087                         params.push(param);
2088                     }
2089                 });
2090                 line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
2091             }
2092             return line;
2093         };
2095         // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
2096         // a=rtcp-fb:98 nack rpsi
2097         SDPUtils.parseRtcpFb = function(line) {
2098             var parts = line.substr(line.indexOf(' ') + 1).split(' ');
2099             return {
2100                 type: parts.shift(),
2101                 parameter: parts.join(' ')
2102             };
2103         };
2104         // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
2105         SDPUtils.writeRtcpFb = function(codec) {
2106             var lines = '';
2107             var pt = codec.payloadType;
2108             if (codec.preferredPayloadType !== undefined) {
2109                 pt = codec.preferredPayloadType;
2110             }
2111             if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
2112                 // FIXME: special handling for trr-int?
2113                 codec.rtcpFeedback.forEach(function(fb) {
2114                     lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
2115                         (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
2116                         '\r\n';
2117                 });
2118             }
2119             return lines;
2120         };
2122         // Parses an RFC 5576 ssrc media attribute. Sample input:
2123         // a=ssrc:3735928559 cname:something
2124         SDPUtils.parseSsrcMedia = function(line) {
2125             var sp = line.indexOf(' ');
2126             var parts = {
2127                 ssrc: parseInt(line.substr(7, sp - 7), 10)
2128             };
2129             var colon = line.indexOf(':', sp);
2130             if (colon > -1) {
2131                 parts.attribute = line.substr(sp + 1, colon - sp - 1);
2132                 parts.value = line.substr(colon + 1);
2133             } else {
2134                 parts.attribute = line.substr(sp + 1);
2135             }
2136             return parts;
2137         };
2139         SDPUtils.parseSsrcGroup = function(line) {
2140             var parts = line.substr(13).split(' ');
2141             return {
2142                 semantics: parts.shift(),
2143                 ssrcs: parts.map(function(ssrc) {
2144                     return parseInt(ssrc, 10);
2145                 })
2146             };
2147         };
2149         // Extracts the MID (RFC 5888) from a media section.
2150         // returns the MID or undefined if no mid line was found.
2151         SDPUtils.getMid = function(mediaSection) {
2152             var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
2153             if (mid) {
2154                 return mid.substr(6);
2155             }
2156         };
2158         SDPUtils.parseFingerprint = function(line) {
2159             var parts = line.substr(14).split(' ');
2160             return {
2161                 algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
2162                 value: parts[1]
2163             };
2164         };
2166         // Extracts DTLS parameters from SDP media section or sessionpart.
2167         // FIXME: for consistency with other functions this should only
2168         //   get the fingerprint line as input. See also getIceParameters.
2169         SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
2170             var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
2171                 'a=fingerprint:');
2172             // Note: a=setup line is ignored since we use the 'auto' role.
2173             // Note2: 'algorithm' is not case sensitive except in Edge.
2174             return {
2175                 role: 'auto',
2176                 fingerprints: lines.map(SDPUtils.parseFingerprint)
2177             };
2178         };
2180         // Serializes DTLS parameters to SDP.
2181         SDPUtils.writeDtlsParameters = function(params, setupType) {
2182             var sdp = 'a=setup:' + setupType + '\r\n';
2183             params.fingerprints.forEach(function(fp) {
2184                 sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
2185             });
2186             return sdp;
2187         };
2188         // Parses ICE information from SDP media section or sessionpart.
2189         // FIXME: for consistency with other functions this should only
2190         //   get the ice-ufrag and ice-pwd lines as input.
2191         SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
2192             var lines = SDPUtils.splitLines(mediaSection);
2193             // Search in session part, too.
2194             lines = lines.concat(SDPUtils.splitLines(sessionpart));
2195             var iceParameters = {
2196                 usernameFragment: lines.filter(function(line) {
2197                     return line.indexOf('a=ice-ufrag:') === 0;
2198                 })[0].substr(12),
2199                 password: lines.filter(function(line) {
2200                     return line.indexOf('a=ice-pwd:') === 0;
2201                 })[0].substr(10)
2202             };
2203             return iceParameters;
2204         };
2206         // Serializes ICE parameters to SDP.
2207         SDPUtils.writeIceParameters = function(params) {
2208             return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
2209                 'a=ice-pwd:' + params.password + '\r\n';
2210         };
2212         // Parses the SDP media section and returns RTCRtpParameters.
2213         SDPUtils.parseRtpParameters = function(mediaSection) {
2214             var description = {
2215                 codecs: [],
2216                 headerExtensions: [],
2217                 fecMechanisms: [],
2218                 rtcp: []
2219             };
2220             var lines = SDPUtils.splitLines(mediaSection);
2221             var mline = lines[0].split(' ');
2222             for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
2223                 var pt = mline[i];
2224                 var rtpmapline = SDPUtils.matchPrefix(
2225                     mediaSection, 'a=rtpmap:' + pt + ' ')[0];
2226                 if (rtpmapline) {
2227                     var codec = SDPUtils.parseRtpMap(rtpmapline);
2228                     var fmtps = SDPUtils.matchPrefix(
2229                         mediaSection, 'a=fmtp:' + pt + ' ');
2230                     // Only the first a=fmtp:<pt> is considered.
2231                     codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
2232                     codec.rtcpFeedback = SDPUtils.matchPrefix(
2233                         mediaSection, 'a=rtcp-fb:' + pt + ' ')
2234                         .map(SDPUtils.parseRtcpFb);
2235                     description.codecs.push(codec);
2236                     // parse FEC mechanisms from rtpmap lines.
2237                     switch (codec.name.toUpperCase()) {
2238                         case 'RED':
2239                         case 'ULPFEC':
2240                             description.fecMechanisms.push(codec.name.toUpperCase());
2241                             break;
2242                         default: // only RED and ULPFEC are recognized as FEC mechanisms.
2243                             break;
2244                     }
2245                 }
2246             }
2247             SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
2248                 description.headerExtensions.push(SDPUtils.parseExtmap(line));
2249             });
2250             // FIXME: parse rtcp.
2251             return description;
2252         };
2254         // Generates parts of the SDP media section describing the capabilities /
2255         // parameters.
2256         SDPUtils.writeRtpDescription = function(kind, caps) {
2257             var sdp = '';
2259             // Build the mline.
2260             sdp += 'm=' + kind + ' ';
2261             sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
2262             sdp += ' UDP/TLS/RTP/SAVPF ';
2263             sdp += caps.codecs.map(function(codec) {
2264                 if (codec.preferredPayloadType !== undefined) {
2265                     return codec.preferredPayloadType;
2266                 }
2267                 return codec.payloadType;
2268             }).join(' ') + '\r\n';
2270             sdp += 'c=IN IP4 0.0.0.0\r\n';
2271             sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
2273             // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
2274             caps.codecs.forEach(function(codec) {
2275                 sdp += SDPUtils.writeRtpMap(codec);
2276                 sdp += SDPUtils.writeFmtp(codec);
2277                 sdp += SDPUtils.writeRtcpFb(codec);
2278             });
2279             var maxptime = 0;
2280             caps.codecs.forEach(function(codec) {
2281                 if (codec.maxptime > maxptime) {
2282                     maxptime = codec.maxptime;
2283                 }
2284             });
2285             if (maxptime > 0) {
2286                 sdp += 'a=maxptime:' + maxptime + '\r\n';
2287             }
2288             sdp += 'a=rtcp-mux\r\n';
2290             if (caps.headerExtensions) {
2291                 caps.headerExtensions.forEach(function(extension) {
2292                     sdp += SDPUtils.writeExtmap(extension);
2293                 });
2294             }
2295             // FIXME: write fecMechanisms.
2296             return sdp;
2297         };
2299         // Parses the SDP media section and returns an array of
2300         // RTCRtpEncodingParameters.
2301         SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
2302             var encodingParameters = [];
2303             var description = SDPUtils.parseRtpParameters(mediaSection);
2304             var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
2305             var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
2307             // filter a=ssrc:... cname:, ignore PlanB-msid
2308             var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
2309                 .map(function(line) {
2310                     return SDPUtils.parseSsrcMedia(line);
2311                 })
2312                 .filter(function(parts) {
2313                     return parts.attribute === 'cname';
2314                 });
2315             var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
2316             var secondarySsrc;
2318             var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
2319                 .map(function(line) {
2320                     var parts = line.substr(17).split(' ');
2321                     return parts.map(function(part) {
2322                         return parseInt(part, 10);
2323                     });
2324                 });
2325             if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
2326                 secondarySsrc = flows[0][1];
2327             }
2329             description.codecs.forEach(function(codec) {
2330                 if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
2331                     var encParam = {
2332                         ssrc: primarySsrc,
2333                         codecPayloadType: parseInt(codec.parameters.apt, 10)
2334                     };
2335                     if (primarySsrc && secondarySsrc) {
2336                         encParam.rtx = {ssrc: secondarySsrc};
2337                     }
2338                     encodingParameters.push(encParam);
2339                     if (hasRed) {
2340                         encParam = JSON.parse(JSON.stringify(encParam));
2341                         encParam.fec = {
2342                             ssrc: primarySsrc,
2343                             mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
2344                         };
2345                         encodingParameters.push(encParam);
2346                     }
2347                 }
2348             });
2349             if (encodingParameters.length === 0 && primarySsrc) {
2350                 encodingParameters.push({
2351                     ssrc: primarySsrc
2352                 });
2353             }
2355             // we support both b=AS and b=TIAS but interpret AS as TIAS.
2356             var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
2357             if (bandwidth.length) {
2358                 if (bandwidth[0].indexOf('b=TIAS:') === 0) {
2359                     bandwidth = parseInt(bandwidth[0].substr(7), 10);
2360                 } else if (bandwidth[0].indexOf('b=AS:') === 0) {
2361                     // use formula from JSEP to convert b=AS to TIAS value.
2362                     bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
2363                         - (50 * 40 * 8);
2364                 } else {
2365                     bandwidth = undefined;
2366                 }
2367                 encodingParameters.forEach(function(params) {
2368                     params.maxBitrate = bandwidth;
2369                 });
2370             }
2371             return encodingParameters;
2372         };
2374         // parses http://draft.ortc.org/#rtcrtcpparameters*
2375         SDPUtils.parseRtcpParameters = function(mediaSection) {
2376             var rtcpParameters = {};
2378             // Gets the first SSRC. Note tha with RTX there might be multiple
2379             // SSRCs.
2380             var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
2381                 .map(function(line) {
2382                     return SDPUtils.parseSsrcMedia(line);
2383                 })
2384                 .filter(function(obj) {
2385                     return obj.attribute === 'cname';
2386                 })[0];
2387             if (remoteSsrc) {
2388                 rtcpParameters.cname = remoteSsrc.value;
2389                 rtcpParameters.ssrc = remoteSsrc.ssrc;
2390             }
2392             // Edge uses the compound attribute instead of reducedSize
2393             // compound is !reducedSize
2394             var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
2395             rtcpParameters.reducedSize = rsize.length > 0;
2396             rtcpParameters.compound = rsize.length === 0;
2398             // parses the rtcp-mux attrіbute.
2399             // Note that Edge does not support unmuxed RTCP.
2400             var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
2401             rtcpParameters.mux = mux.length > 0;
2403             return rtcpParameters;
2404         };
2406         // parses either a=msid: or a=ssrc:... msid lines and returns
2407         // the id of the MediaStream and MediaStreamTrack.
2408         SDPUtils.parseMsid = function(mediaSection) {
2409             var parts;
2410             var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
2411             if (spec.length === 1) {
2412                 parts = spec[0].substr(7).split(' ');
2413                 return {stream: parts[0], track: parts[1]};
2414             }
2415             var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
2416                 .map(function(line) {
2417                     return SDPUtils.parseSsrcMedia(line);
2418                 })
2419                 .filter(function(msidParts) {
2420                     return msidParts.attribute === 'msid';
2421                 });
2422             if (planB.length > 0) {
2423                 parts = planB[0].value.split(' ');
2424                 return {stream: parts[0], track: parts[1]};
2425             }
2426         };
2428         // Generate a session ID for SDP.
2429         // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
2430         // recommends using a cryptographically random +ve 64-bit value
2431         // but right now this should be acceptable and within the right range
2432         SDPUtils.generateSessionId = function() {
2433             return Math.random().toString().substr(2, 21);
2434         };
2436         // Write boilder plate for start of SDP
2437         // sessId argument is optional - if not supplied it will
2438         // be generated randomly
2439         // sessVersion is optional and defaults to 2
2440         // sessUser is optional and defaults to 'thisisadapterortc'
2441         SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {
2442             var sessionId;
2443             var version = sessVer !== undefined ? sessVer : 2;
2444             if (sessId) {
2445                 sessionId = sessId;
2446             } else {
2447                 sessionId = SDPUtils.generateSessionId();
2448             }
2449             var user = sessUser || 'thisisadapterortc';
2450             // FIXME: sess-id should be an NTP timestamp.
2451             return 'v=0\r\n' +
2452                 'o=' + user + ' ' + sessionId + ' ' + version +
2453                 ' IN IP4 127.0.0.1\r\n' +
2454                 's=-\r\n' +
2455                 't=0 0\r\n';
2456         };
2458         SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
2459             var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
2461             // Map ICE parameters (ufrag, pwd) to SDP.
2462             sdp += SDPUtils.writeIceParameters(
2463                 transceiver.iceGatherer.getLocalParameters());
2465             // Map DTLS parameters to SDP.
2466             sdp += SDPUtils.writeDtlsParameters(
2467                 transceiver.dtlsTransport.getLocalParameters(),
2468                 type === 'offer' ? 'actpass' : 'active');
2470             sdp += 'a=mid:' + transceiver.mid + '\r\n';
2472             if (transceiver.direction) {
2473                 sdp += 'a=' + transceiver.direction + '\r\n';
2474             } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
2475                 sdp += 'a=sendrecv\r\n';
2476             } else if (transceiver.rtpSender) {
2477                 sdp += 'a=sendonly\r\n';
2478             } else if (transceiver.rtpReceiver) {
2479                 sdp += 'a=recvonly\r\n';
2480             } else {
2481                 sdp += 'a=inactive\r\n';
2482             }
2484             if (transceiver.rtpSender) {
2485                 // spec.
2486                 var msid = 'msid:' + stream.id + ' ' +
2487                     transceiver.rtpSender.track.id + '\r\n';
2488                 sdp += 'a=' + msid;
2490                 // for Chrome.
2491                 sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
2492                     ' ' + msid;
2493                 if (transceiver.sendEncodingParameters[0].rtx) {
2494                     sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
2495                         ' ' + msid;
2496                     sdp += 'a=ssrc-group:FID ' +
2497                         transceiver.sendEncodingParameters[0].ssrc + ' ' +
2498                         transceiver.sendEncodingParameters[0].rtx.ssrc +
2499                         '\r\n';
2500                 }
2501             }
2502             // FIXME: this should be written by writeRtpDescription.
2503             sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
2504                 ' cname:' + SDPUtils.localCName + '\r\n';
2505             if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
2506                 sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
2507                     ' cname:' + SDPUtils.localCName + '\r\n';
2508             }
2509             return sdp;
2510         };
2512         // Gets the direction from the mediaSection or the sessionpart.
2513         SDPUtils.getDirection = function(mediaSection, sessionpart) {
2514             // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
2515             var lines = SDPUtils.splitLines(mediaSection);
2516             for (var i = 0; i < lines.length; i++) {
2517                 switch (lines[i]) {
2518                     case 'a=sendrecv':
2519                     case 'a=sendonly':
2520                     case 'a=recvonly':
2521                     case 'a=inactive':
2522                         return lines[i].substr(2);
2523                     default:
2524                     // FIXME: What should happen here?
2525                 }
2526             }
2527             if (sessionpart) {
2528                 return SDPUtils.getDirection(sessionpart);
2529             }
2530             return 'sendrecv';
2531         };
2533         SDPUtils.getKind = function(mediaSection) {
2534             var lines = SDPUtils.splitLines(mediaSection);
2535             var mline = lines[0].split(' ');
2536             return mline[0].substr(2);
2537         };
2539         SDPUtils.isRejected = function(mediaSection) {
2540             return mediaSection.split(' ', 2)[1] === '0';
2541         };
2543         SDPUtils.parseMLine = function(mediaSection) {
2544             var lines = SDPUtils.splitLines(mediaSection);
2545             var parts = lines[0].substr(2).split(' ');
2546             return {
2547                 kind: parts[0],
2548                 port: parseInt(parts[1], 10),
2549                 protocol: parts[2],
2550                 fmt: parts.slice(3).join(' ')
2551             };
2552         };
2554         SDPUtils.parseOLine = function(mediaSection) {
2555             var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0];
2556             var parts = line.substr(2).split(' ');
2557             return {
2558                 username: parts[0],
2559                 sessionId: parts[1],
2560                 sessionVersion: parseInt(parts[2], 10),
2561                 netType: parts[3],
2562                 addressType: parts[4],
2563                 address: parts[5]
2564             };
2565         };
2567         // a very naive interpretation of a valid SDP.
2568         SDPUtils.isValidSDP = function(blob) {
2569             if (typeof blob !== 'string' || blob.length === 0) {
2570                 return false;
2571             }
2572             var lines = SDPUtils.splitLines(blob);
2573             for (var i = 0; i < lines.length; i++) {
2574                 if (lines[i].length < 2 || lines[i].charAt(1) !== '=') {
2575                     return false;
2576                 }
2577                 // TODO: check the modifier a bit more.
2578             }
2579             return true;
2580         };
2582         // Expose public methods.
2583         if (typeof module === 'object') {
2584             module.exports = SDPUtils;
2585         }
2587     },{}],3:[function(require,module,exports){
2588         (function (global){
2589             /*
2590              *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2591              *
2592              *  Use of this source code is governed by a BSD-style license
2593              *  that can be found in the LICENSE file in the root of the source
2594              *  tree.
2595              */
2596             /* eslint-env node */
2598             'use strict';
2600             var adapterFactory = require('./adapter_factory.js');
2601             module.exports = adapterFactory({window: global.window});
2603         }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2604     },{"./adapter_factory.js":4}],4:[function(require,module,exports){
2605         /*
2606          *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2607          *
2608          *  Use of this source code is governed by a BSD-style license
2609          *  that can be found in the LICENSE file in the root of the source
2610          *  tree.
2611          */
2612         /* eslint-env node */
2614         'use strict';
2616         var utils = require('./utils');
2617         // Shimming starts here.
2618         module.exports = function(dependencies, opts) {
2619             var window = dependencies && dependencies.window;
2621             var options = {
2622                 shimChrome: true,
2623                 shimFirefox: true,
2624                 shimEdge: true,
2625                 shimSafari: true,
2626             };
2628             for (var key in opts) {
2629                 if (hasOwnProperty.call(opts, key)) {
2630                     options[key] = opts[key];
2631                 }
2632             }
2634             // Utils.
2635             var logging = utils.log;
2636             var browserDetails = utils.detectBrowser(window);
2638             // Uncomment the line below if you want logging to occur, including logging
2639             // for the switch statement below. Can also be turned on in the browser via
2640             // adapter.disableLog(false), but then logging from the switch statement below
2641             // will not appear.
2642             // require('./utils').disableLog(false);
2644             // Browser shims.
2645             var chromeShim = require('./chrome/chrome_shim') || null;
2646             var edgeShim = require('./edge/edge_shim') || null;
2647             var firefoxShim = require('./firefox/firefox_shim') || null;
2648             var safariShim = require('./safari/safari_shim') || null;
2649             var commonShim = require('./common_shim') || null;
2651             // Export to the adapter global object visible in the browser.
2652             var adapter = {
2653                 browserDetails: browserDetails,
2654                 commonShim: commonShim,
2655                 extractVersion: utils.extractVersion,
2656                 disableLog: utils.disableLog,
2657                 disableWarnings: utils.disableWarnings
2658             };
2660             // Shim browser if found.
2661             switch (browserDetails.browser) {
2662                 case 'chrome':
2663                     if (!chromeShim || !chromeShim.shimPeerConnection ||
2664                         !options.shimChrome) {
2665                         logging('Chrome shim is not included in this adapter release.');
2666                         return adapter;
2667                     }
2668                     logging('adapter.js shimming chrome.');
2669                     // Export to the adapter global object visible in the browser.
2670                     adapter.browserShim = chromeShim;
2671                     commonShim.shimCreateObjectURL(window);
2673                     chromeShim.shimGetUserMedia(window);
2674                     chromeShim.shimMediaStream(window);
2675                     chromeShim.shimSourceObject(window);
2676                     chromeShim.shimPeerConnection(window);
2677                     chromeShim.shimOnTrack(window);
2678                     chromeShim.shimAddTrackRemoveTrack(window);
2679                     chromeShim.shimGetSendersWithDtmf(window);
2680                     chromeShim.shimSenderReceiverGetStats(window);
2681                     chromeShim.fixNegotiationNeeded(window);
2683                     commonShim.shimRTCIceCandidate(window);
2684                     commonShim.shimMaxMessageSize(window);
2685                     commonShim.shimSendThrowTypeError(window);
2686                     break;
2687                 case 'firefox':
2688                     if (!firefoxShim || !firefoxShim.shimPeerConnection ||
2689                         !options.shimFirefox) {
2690                         logging('Firefox shim is not included in this adapter release.');
2691                         return adapter;
2692                     }
2693                     logging('adapter.js shimming firefox.');
2694                     // Export to the adapter global object visible in the browser.
2695                     adapter.browserShim = firefoxShim;
2696                     commonShim.shimCreateObjectURL(window);
2698                     firefoxShim.shimGetUserMedia(window);
2699                     firefoxShim.shimSourceObject(window);
2700                     firefoxShim.shimPeerConnection(window);
2701                     firefoxShim.shimOnTrack(window);
2702                     firefoxShim.shimRemoveStream(window);
2703                     firefoxShim.shimSenderGetStats(window);
2704                     firefoxShim.shimReceiverGetStats(window);
2705                     firefoxShim.shimRTCDataChannel(window);
2707                     commonShim.shimRTCIceCandidate(window);
2708                     commonShim.shimMaxMessageSize(window);
2709                     commonShim.shimSendThrowTypeError(window);
2710                     break;
2711                 case 'edge':
2712                     if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
2713                         logging('MS edge shim is not included in this adapter release.');
2714                         return adapter;
2715                     }
2716                     logging('adapter.js shimming edge.');
2717                     // Export to the adapter global object visible in the browser.
2718                     adapter.browserShim = edgeShim;
2719                     commonShim.shimCreateObjectURL(window);
2721                     edgeShim.shimGetUserMedia(window);
2722                     edgeShim.shimPeerConnection(window);
2723                     edgeShim.shimReplaceTrack(window);
2725                     // the edge shim implements the full RTCIceCandidate object.
2727                     commonShim.shimMaxMessageSize(window);
2728                     commonShim.shimSendThrowTypeError(window);
2729                     break;
2730                 case 'safari':
2731                     if (!safariShim || !options.shimSafari) {
2732                         logging('Safari shim is not included in this adapter release.');
2733                         return adapter;
2734                     }
2735                     logging('adapter.js shimming safari.');
2736                     // Export to the adapter global object visible in the browser.
2737                     adapter.browserShim = safariShim;
2738                     commonShim.shimCreateObjectURL(window);
2740                     safariShim.shimRTCIceServerUrls(window);
2741                     safariShim.shimCreateOfferLegacy(window);
2742                     safariShim.shimCallbacksAPI(window);
2743                     safariShim.shimLocalStreamsAPI(window);
2744                     safariShim.shimRemoteStreamsAPI(window);
2745                     safariShim.shimTrackEventTransceiver(window);
2746                     safariShim.shimGetUserMedia(window);
2748                     commonShim.shimRTCIceCandidate(window);
2749                     commonShim.shimMaxMessageSize(window);
2750                     commonShim.shimSendThrowTypeError(window);
2751                     break;
2752                 default:
2753                     logging('Unsupported browser!');
2754                     break;
2755             }
2757             return adapter;
2758         };
2760     },{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":11,"./safari/safari_shim":13,"./utils":14}],5:[function(require,module,exports){
2762         /*
2763          *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2764          *
2765          *  Use of this source code is governed by a BSD-style license
2766          *  that can be found in the LICENSE file in the root of the source
2767          *  tree.
2768          */
2769         /* eslint-env node */
2770         'use strict';
2771         var utils = require('../utils.js');
2772         var logging = utils.log;
2774         /* iterates the stats graph recursively. */
2775         function walkStats(stats, base, resultSet) {
2776             if (!base || resultSet.has(base.id)) {
2777                 return;
2778             }
2779             resultSet.set(base.id, base);
2780             Object.keys(base).forEach(function(name) {
2781                 if (name.endsWith('Id')) {
2782                     walkStats(stats, stats.get(base[name]), resultSet);
2783                 } else if (name.endsWith('Ids')) {
2784                     base[name].forEach(function(id) {
2785                         walkStats(stats, stats.get(id), resultSet);
2786                     });
2787                 }
2788             });
2789         }
2791         /* filter getStats for a sender/receiver track. */
2792         function filterStats(result, track, outbound) {
2793             var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';
2794             var filteredResult = new Map();
2795             if (track === null) {
2796                 return filteredResult;
2797             }
2798             var trackStats = [];
2799             result.forEach(function(value) {
2800                 if (value.type === 'track' &&
2801                     value.trackIdentifier === track.id) {
2802                     trackStats.push(value);
2803                 }
2804             });
2805             trackStats.forEach(function(trackStat) {
2806                 result.forEach(function(stats) {
2807                     if (stats.type === streamStatsType && stats.trackId === trackStat.id) {
2808                         walkStats(result, stats, filteredResult);
2809                     }
2810                 });
2811             });
2812             return filteredResult;
2813         }
2815         module.exports = {
2816             shimGetUserMedia: require('./getusermedia'),
2817             shimMediaStream: function(window) {
2818                 window.MediaStream = window.MediaStream || window.webkitMediaStream;
2819             },
2821             shimOnTrack: function(window) {
2822                 if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
2823                         window.RTCPeerConnection.prototype)) {
2824                     Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
2825                         get: function() {
2826                             return this._ontrack;
2827                         },
2828                         set: function(f) {
2829                             if (this._ontrack) {
2830                                 this.removeEventListener('track', this._ontrack);
2831                             }
2832                             this.addEventListener('track', this._ontrack = f);
2833                         },
2834                         enumerable: true,
2835                         configurable: true
2836                     });
2837                     var origSetRemoteDescription =
2838                         window.RTCPeerConnection.prototype.setRemoteDescription;
2839                     window.RTCPeerConnection.prototype.setRemoteDescription = function() {
2840                         var pc = this;
2841                         if (!pc._ontrackpoly) {
2842                             pc._ontrackpoly = function(e) {
2843                                 // onaddstream does not fire when a track is added to an existing
2844                                 // stream. But stream.onaddtrack is implemented so we use that.
2845                                 e.stream.addEventListener('addtrack', function(te) {
2846                                     var receiver;
2847                                     if (window.RTCPeerConnection.prototype.getReceivers) {
2848                                         receiver = pc.getReceivers().find(function(r) {
2849                                             return r.track && r.track.id === te.track.id;
2850                                         });
2851                                     } else {
2852                                         receiver = {track: te.track};
2853                                     }
2855                                     var event = new Event('track');
2856                                     event.track = te.track;
2857                                     event.receiver = receiver;
2858                                     event.transceiver = {receiver: receiver};
2859                                     event.streams = [e.stream];
2860                                     pc.dispatchEvent(event);
2861                                 });
2862                                 e.stream.getTracks().forEach(function(track) {
2863                                     var receiver;
2864                                     if (window.RTCPeerConnection.prototype.getReceivers) {
2865                                         receiver = pc.getReceivers().find(function(r) {
2866                                             return r.track && r.track.id === track.id;
2867                                         });
2868                                     } else {
2869                                         receiver = {track: track};
2870                                     }
2871                                     var event = new Event('track');
2872                                     event.track = track;
2873                                     event.receiver = receiver;
2874                                     event.transceiver = {receiver: receiver};
2875                                     event.streams = [e.stream];
2876                                     pc.dispatchEvent(event);
2877                                 });
2878                             };
2879                             pc.addEventListener('addstream', pc._ontrackpoly);
2880                         }
2881                         return origSetRemoteDescription.apply(pc, arguments);
2882                     };
2883                 } else {
2884                     // even if RTCRtpTransceiver is in window, it is only used and
2885                     // emitted in unified-plan. Unfortunately this means we need
2886                     // to unconditionally wrap the event.
2887                     utils.wrapPeerConnectionEvent(window, 'track', function(e) {
2888                         if (!e.transceiver) {
2889                             Object.defineProperty(e, 'transceiver',
2890                                 {value: {receiver: e.receiver}});
2891                         }
2892                         return e;
2893                     });
2894                 }
2895             },
2897             shimGetSendersWithDtmf: function(window) {
2898                 // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
2899                 if (typeof window === 'object' && window.RTCPeerConnection &&
2900                     !('getSenders' in window.RTCPeerConnection.prototype) &&
2901                     'createDTMFSender' in window.RTCPeerConnection.prototype) {
2902                     var shimSenderWithDtmf = function(pc, track) {
2903                         return {
2904                             track: track,
2905                             get dtmf() {
2906                                 if (this._dtmf === undefined) {
2907                                     if (track.kind === 'audio') {
2908                                         this._dtmf = pc.createDTMFSender(track);
2909                                     } else {
2910                                         this._dtmf = null;
2911                                     }
2912                                 }
2913                                 return this._dtmf;
2914                             },
2915                             _pc: pc
2916                         };
2917                     };
2919                     // augment addTrack when getSenders is not available.
2920                     if (!window.RTCPeerConnection.prototype.getSenders) {
2921                         window.RTCPeerConnection.prototype.getSenders = function() {
2922                             this._senders = this._senders || [];
2923                             return this._senders.slice(); // return a copy of the internal state.
2924                         };
2925                         var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
2926                         window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
2927                             var pc = this;
2928                             var sender = origAddTrack.apply(pc, arguments);
2929                             if (!sender) {
2930                                 sender = shimSenderWithDtmf(pc, track);
2931                                 pc._senders.push(sender);
2932                             }
2933                             return sender;
2934                         };
2936                         var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
2937                         window.RTCPeerConnection.prototype.removeTrack = function(sender) {
2938                             var pc = this;
2939                             origRemoveTrack.apply(pc, arguments);
2940                             var idx = pc._senders.indexOf(sender);
2941                             if (idx !== -1) {
2942    &