{"action":"created","comment":{"url":"https://api.github.com/repos/webrtc/adapter/pulls/comments/44729200","id":44729200,"diff_hunk":"@@ -484,8 +486,1119 @@ if (typeof window === 'undefined' || !window.navigator) {\n   webrtcDetectedVersion =\n     parseInt(navigator.userAgent.match(/Edge\\/(\\d+).(\\d+)$/)[2], 10);\n \n-  // the minimum version still supported by adapter.\n-  webrtcMinimumVersion = 12;\n+  // The minimum version still supported by adapter.\n+  // This is the build number for Edge.\n+  webrtcMinimumVersion = 10547;\n+\n+  if (RTCIceGatherer) {\n+    // Generate an alphanumeric identifier for cname or mids.\n+    // TODO: use UUIDs instead? https://gist.github.com/jed/982883\n+    var generateIdentifier = function() {\n+      return Math.random().toString(36).substr(2, 10);\n+    };\n+\n+    // The RTCP CNAME used by all peerconnections from the same JS.\n+    var localCName = generateIdentifier();\n+\n+    // SDP helpers - to be moved into separate module.\n+    var SDPUtils = {};\n+\n+    // Splits SDP into lines, dealing with both CRLF and LF.\n+    SDPUtils.splitLines = function(blob) {\n+      return blob.trim().split('\\n').map(function(line) {\n+        return line.trim();\n+      });\n+    };\n+\n+    // Splits SDP into sessionpart and mediasections. Ensures CRLF.\n+    SDPUtils.splitSections = function(blob) {\n+      var parts = blob.split('\\r\\nm=');\n+      return parts.map(function(part, index) {\n+        return (index > 0 ? 'm=' + part : part).trim() + '\\r\\n';\n+      });\n+    };\n+\n+    // Returns lines that start with a certain prefix.\n+    SDPUtils.matchPrefix = function(blob, prefix) {\n+      return SDPUtils.splitLines(blob).filter(function(line) {\n+        return line.indexOf(prefix) === 0;\n+      });\n+    };\n+\n+    // Parses an ICE candidate line. Sample input:\n+    // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 rport 55996\"\n+    SDPUtils.parseCandidate = function(line) {\n+      var parts;\n+      // Parse both variants.\n+      if (line.indexOf('a=candidate:') === 0) {\n+        parts = line.substring(12).split(' ');\n+      } else {\n+        parts = line.substring(10).split(' ');\n+      }\n+\n+      var candidate = {\n+        foundation: parts[0],\n+        component: parts[1],\n+        protocol: parts[2].toLowerCase(),\n+        priority: parseInt(parts[3], 10),\n+        ip: parts[4],\n+        port: parseInt(parts[5], 10),\n+        // skip parts[6] == 'typ'\n+        type: parts[7]\n+      };\n+\n+      for (var i = 8; i < parts.length; i += 2) {\n+        switch (parts[i]) {\n+          case 'raddr':\n+            candidate.relatedAddress = parts[i + 1];\n+            break;\n+          case 'rport':\n+            candidate.relatedPort = parseInt(parts[i + 1], 10);\n+            break;\n+          case 'tcptype':\n+            candidate.tcpType = parts[i + 1];\n+            break;\n+          default: // Unknown extensions are silently ignored.\n+            break;\n+        }\n+      }\n+      return candidate;\n+    };\n+\n+    // Translates candidate object into SDP candidate attribute.\n+    SDPUtils.writeCandidate = function(candidate) {\n+      var sdp = [];\n+      sdp.push(candidate.foundation);\n+      sdp.push(candidate.component);\n+      sdp.push(candidate.protocol.toUpperCase());\n+      sdp.push(candidate.priority);\n+      sdp.push(candidate.ip);\n+      sdp.push(candidate.port);\n+\n+      var type = candidate.type;\n+      sdp.push('typ');\n+      sdp.push(type);\n+      if (type !== 'host' && candidate.relatedAddress &&\n+          candidate.relatedPort) {\n+        sdp.push('raddr');\n+        sdp.push(candidate.relatedAddress); // was: relAddr\n+        sdp.push('rport');\n+        sdp.push(candidate.relatedPort); // was: relPort\n+      }\n+      if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {\n+        sdp.push('tcptype');\n+        sdp.push(candidate.tcpType);\n+      }\n+      return 'candidate:' + sdp.join(' ');\n+    };\n+\n+    // Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input:\n+    // a=rtpmap:111 opus/48000/2\n+    SDPUtils.parseRtpMap = function(line) {\n+      var parts = line.substr(9).split(' ');\n+      var parsed = {\n+        payloadType: parseInt(parts.shift(), 10) // was: id\n+      };\n+\n+      parts = parts[0].split('/');\n+\n+      parsed.name = parts[0];\n+      parsed.clockRate = parseInt(parts[1], 10); // was: clockrate\n+      parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels\n+      return parsed;\n+    };\n+\n+    // Generate a=rtpmap line from RTCRtpCodecCapability or RTCRtpCodecParameters.\n+    SDPUtils.writeRtpMap = function(codec) {\n+      var pt = codec.payloadType;\n+      if (codec.preferredPayloadType !== undefined) {\n+        pt = codec.preferredPayloadType;\n+      }\n+      return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +\n+          (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\\r\\n';\n+    };\n+\n+    // Parses a ftmp line, returns dictionary. Sample input:\n+    // a=fmtp:96 vbr=on;cng=on\n+    // Also deals with vbr=on; cng=on\n+    SDPUtils.parseFmtp = function(line) {\n+      var parsed = {};\n+      var kv;\n+      var parts = line.substr(line.indexOf(' ') + 1).split(';');\n+      for (var j = 0; j < parts.length; j++) {\n+        kv = parts[j].trim().split('=');\n+        parsed[kv[0].trim()] = kv[1];\n+      }\n+      return parsed;\n+    };\n+\n+    // Generate a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.\n+    SDPUtils.writeFtmp = function(codec) {\n+      var line = '';\n+      var pt = codec.payloadType;\n+      if (codec.preferredPayloadType !== undefined) {\n+        pt = codec.preferredPayloadType;\n+      }\n+      if (codec.parameters && codec.parameters.length) {\n+        var params = [];\n+        Object.keys(codec.parameters).forEach(function(param) {\n+          params.push(param + '=' + codec.parameters[param]);\n+        });\n+        line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\\r\\n';\n+      }\n+      return line;\n+    };\n+\n+    // Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:\n+    // a=rtcp-fb:98 nack rpsi\n+    SDPUtils.parseRtcpFb = function(line) {\n+      var parts = line.substr(line.indexOf(' ') + 1).split(' ');\n+      return {\n+        type: parts.shift(),\n+        parameter: parts.join(' ')\n+      };\n+    };\n+    // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.\n+    SDPUtils.writeRtcpFb = function(codec) {\n+      var lines = '';\n+      var pt = codec.payloadType;\n+      if (codec.preferredPayloadType !== undefined) {\n+        pt = codec.preferredPayloadType;\n+      }\n+      if (codec.rtcpFeedback && codec.rtcpFeedback.length) {\n+        // FIXME: special handling for trr-int?\n+        codec.rtcpFeedback.forEach(function(fb) {\n+          lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' + fb.parameter +\n+              '\\r\\n';\n+        });\n+      }\n+      return lines;\n+    };\n+\n+    // parses a RFC 5576 ssrc media attribute. Sample input:\n+    // a=ssrc:3735928559 cname:something\n+    SDPUtils.parseSsrcMedia = function(line) {\n+      var sp = line.indexOf(' ');\n+      var parts = {\n+        ssrc: line.substr(7, sp - 7),\n+      };\n+      var colon = line.indexOf(':', sp);\n+      if (colon > -1) {\n+        parts.attribute = line.substr(sp + 1, colon - sp - 1);\n+        parts.value = line.substr(colon + 1);\n+      } else {\n+        parts.attribute = line.substr(sp + 1);\n+      }\n+      return parts;\n+    };\n+\n+    // Extracts DTLS parameters from SDP media section or sessionpart.\n+    // FIXME: for consistency with other functions this should only\n+    //   get the fingerprint line as input. See also getIceParameters.\n+    SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {\n+      var lines = SDPUtils.splitLines(mediaSection);\n+      lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too.\n+      var fpLine = lines.filter(function(line) {\n+        return line.indexOf('a=fingerprint:') === 0;\n+      });\n+      fpLine = fpLine[0].substr(14);\n+      var dtlsParameters = {\n+        role: 'auto',\n+        fingerprints: [{\n+          algorithm: fpLine.split(' ')[0],\n+          value: fpLine.split(' ')[1]\n+        }]\n+      };\n+      return dtlsParameters;\n+    };\n+\n+    // Serializes DTLS parameters to SDP.\n+    SDPUtils.writeDtlsParameters = function(params, setupType) {\n+      var sdp = 'a=setup:' + setupType + '\\r\\n';\n+      params.fingerprints.forEach(function(fp) {\n+        sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\\r\\n';\n+      });\n+      return sdp;\n+    };\n+    // Parses ICE information from SDP media section or sessionpart.\n+    // FIXME: for consistency with other functions this should only\n+    //   get the ice-ufrag and ice-pwd lines as input.\n+    SDPUtils.getIceParameters = function(mediaSection, sessionpart) {\n+      var lines = SDPUtils.splitLines(mediaSection);\n+      lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too.\n+      var iceParameters = {\n+        usernameFragment: lines.filter(function(line) {\n+          return line.indexOf('a=ice-ufrag:') === 0;\n+        })[0].substr(12),\n+        password: lines.filter(function(line) {\n+          return line.indexOf('a=ice-pwd:') === 0;\n+        })[0].substr(10),\n+      };\n+      return iceParameters;\n+    };\n+\n+    // Serializes ICE parameters to SDP.\n+    SDPUtils.writeIceParameters = function(params) {\n+      return 'a=ice-ufrag:' + params.usernameFragment + '\\r\\n' +\n+          'a=ice-pwd:' + params.password + '\\r\\n';\n+    };\n+\n+    // Parses the SDP media section and return RTCRtpParameters.\n+    SDPUtils.parseRtpDescription = function(mediaSection) {\n+      var description = {\n+        codecs: [],\n+        headerExtensions: [],\n+        fecMechanisms: [],\n+        rtcp: []\n+      };\n+      var lines = SDPUtils.splitLines(mediaSection);\n+      var mline = lines[0].substr(2).split(' ');\n+      for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]\n+        var line = SDPUtils.matchPrefix(\n+            mediaSection, 'a=rtpmap:' + mline[i] + ' ')[0];\n+        if (line) {\n+          var codec = SDPUtils.parseRtpMap(line);\n+          var fmtp = SDPUtils.matchPrefix(\n+              mediaSection, 'a=fmtp:' + mline[i] + ' ');\n+          codec.parameters = fmtp.length ? SDPUtils.parseFmtp(fmtp[0]) : {};\n+          codec.rtcpFeedback = SDPUtils.matchPrefix(\n+              mediaSection, 'a=rtcp-fb:' + mline[i] + ' ')\n+            .map(SDPUtils.parseRtcpFb);\n+          description.codecs.push(codec);\n+        }\n+      }\n+      // FIXME: parse headerExtensions, fecMechanisms and rtcp.\n+      return description;\n+    };\n+\n+    // Generates parts of the SDP media section describing the capabilities / parameters.\n+    SDPUtils.writeRtpDescription = function(kind, caps) {\n+      var sdp = '';\n+\n+      // Build the mline.\n+      sdp += 'm=' + kind + ' ';\n+      sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.\n+      sdp += ' UDP/TLS/RTP/SAVPF ';\n+      sdp += caps.codecs.map(function(codec) {\n+        if (codec.preferredPayloadType !== undefined) {\n+          return codec.preferredPayloadType;\n+        }\n+        return codec.payloadType;\n+      }).join(' ') + '\\r\\n';\n+\n+      // FIXME: those should be IPv6 ::\n+      sdp += 'c=IN IP4 0.0.0.0\\r\\n';\n+      sdp += 'a=rtcp:9 IN IP4 0.0.0.0\\r\\n';\n+\n+      // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.\n+      caps.codecs.forEach(function(codec) {\n+        sdp += SDPUtils.writeRtpMap(codec);\n+        sdp += SDPUtils.writeFtmp(codec);\n+        sdp += SDPUtils.writeRtcpFb(codec);\n+      });\n+      // FIXME: add headerExtensions, fecMechanismş and rtcp.\n+      sdp += 'a=rtcp-mux\\r\\n';\n+      return sdp;\n+    };\n+\n+    SDPUtils.writeSessionBoilerplate = function() {\n+      // FIXME: sess-id should be an NTP timestamp.\n+      return 'v=0\\r\\n' +\n+          'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\\r\\n' +\n+          's=-\\r\\n' +\n+          't=0 0\\r\\n';\n+    };\n+\n+    SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {\n+      var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);\n+\n+      // Map ICE parameters (ufrag, pwd) to SDP.\n+      sdp += SDPUtils.writeIceParameters(\n+          transceiver.iceGatherer.getLocalParameters());\n+\n+      // Map DTLS parameters to SDP.\n+      sdp += SDPUtils.writeDtlsParameters(\n+          transceiver.dtlsTransport.getLocalParameters(),\n+          type === 'offer' ? 'actpass' : 'active');\n+\n+      sdp += 'a=mid:' + transceiver.mid + '\\r\\n';\n+\n+      if (transceiver.rtpSender && transceiver.rtpReceiver) {\n+        sdp += 'a=sendrecv\\r\\n';\n+      } else if (transceiver.rtpSender) {\n+        sdp += 'a=sendonly\\r\\n';\n+      } else if (transceiver.rtpReceiver) {\n+        sdp += 'a=recvonly\\r\\n';\n+      } else {\n+        sdp += 'a=inactive\\r\\n';\n+      }\n+\n+      if (transceiver.rtpSender) {\n+        sdp += 'a=msid:' + stream.id + ' ' +\n+            transceiver.rtpSender.track.id + '\\r\\n';\n+        sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + 'msid:' +\n+            stream.id + ' ' +\n+            transceiver.rtpSender.track.id + '\\r\\n';\n+      }\n+      // FIXME: this should be written by writeRtpDescription.\n+      sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' +\n+          localCName + '\\r\\n';\n+      return sdp;\n+    };\n+\n+    // Gets the direction from the mediaSection or the sessionpart.\n+    SDPUtils.getDirection = function(mediaSection, sessionpart) {\n+      // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.\n+      var lines = SDPUtils.splitLines(mediaSection);\n+      for (var i = 0; i < lines.length; i++) {\n+        switch (lines[i]) {\n+          case 'a=sendrecv':\n+          case 'a=sendonly':\n+          case 'a=recvonly':\n+          case 'a=inactive':\n+            return lines[i].substr(2);\n+        }\n+      }\n+      if (sessionpart) {\n+        return SDPUtils.getDirection(sessionpart);\n+      }\n+      return 'sendrecv';\n+    };\n+\n+    // ORTC defines an RTCIceCandidate object but no constructor.\n+    // Not implemented in Edge.\n+    if (!window.RTCIceCandidate) {\n+      window.RTCIceCandidate = function(args) {\n+        return args;\n+      };\n+    }\n+    // ORTC does not have a session description object but\n+    // other browsers (i.e. Chrome) that will support both PC and ORTC\n+    // in the future might have this defined already.\n+    if (!window.RTCSessionDescription) {\n+      window.RTCSessionDescription = function(args) {\n+        return args;\n+      };\n+    }\n+\n+    window.RTCPeerConnection = function(config) {\n+      var self = this;\n+\n+      this.onicecandidate = null;\n+      this.onaddstream = null;\n+      this.onremovestream = null;\n+      this.onsignalingstatechange = null;\n+      this.oniceconnectionstatechange = null;\n+      this.onnegotiationneeded = null;\n+      this.ondatachannel = null;\n+\n+      this.localStreams = [];\n+      this.remoteStreams = [];\n+      this.getLocalStreams = function() { return self.localStreams; };\n+      this.getRemoteStreams = function() { return self.remoteStreams; };\n+\n+      this.localDescription = new RTCSessionDescription({\n+        type: '',\n+        sdp: ''\n+      });\n+      this.remoteDescription = new RTCSessionDescription({\n+        type: '',\n+        sdp: ''\n+      });\n+      this.signalingState = 'stable';\n+      this.iceConnectionState = 'new';\n+\n+      this.iceOptions = {\n+        gatherPolicy: 'all',\n+        iceServers: []\n+      };\n+      if (config && config.iceTransportPolicy) {\n+        switch (config.iceTransportPolicy) {\n+          case 'all':\n+          case 'relay':\n+            this.iceOptions.gatherPolicy = config.iceTransportPolicy;\n+            break;\n+          case 'none':\n+            // FIXME: remove once implementation and spec have added this.\n+            throw new TypeError('iceTransportPolicy \"none\" not supported');\n+        }\n+      }\n+      if (config && config.iceServers) {\n+        this.iceOptions.iceServers = config.iceServers;\n+      }\n+\n+      // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...\n+      // everything that is needed to describe a SDP m-line.\n+      this.transceivers = [];\n+\n+      // since the iceGatherer is currently created in createOffer but we\n+      // must not emit candidates until after setLocalDescription we buffer\n+      // them in this array.\n+      this._localIceCandidatesBuffer = [];\n+    };\n+\n+    window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {\n+      var self = this;\n+      // FIXME: need to apply ice candidates in a way which is async but in-order\n+      this._localIceCandidatesBuffer.forEach(function(event) {\n+        if (self.onicecandidate !== null) {\n+          self.onicecandidate(event);\n+        }\n+      });\n+      this._localIceCandidatesBuffer = [];\n+    };\n+\n+    window.RTCPeerConnection.prototype.addStream = function(stream) {\n+      // Clone is necessary for local demos mostly, attaching directly\n+      // to two different senders does not work (build 10547).\n+      this.localStreams.push(stream.clone());\n+      this._maybeFireNegotiationNeeded();\n+    };\n+\n+    window.RTCPeerConnection.prototype.removeStream = function(stream) {\n+      var idx = this.localStreams.indexOf(stream);\n+      if (idx > -1) {\n+        this.localStreams.splice(idx, 1);\n+        this._maybeFireNegotiationNeeded();\n+      }\n+    };\n+\n+    // Calculates the intersection of local and remote capabilities.\n+    window.RTCPeerConnection.prototype._getCommonCapabilities =\n+        function(localCapabilities, remoteCapabilities) {\n+      var commonCapabilities = {\n+        codecs: [],\n+        headerExtensions: [],\n+        fecMechanisms: []\n+      };\n+      localCapabilities.codecs.forEach(function(lCodec) {\n+        for (var i = 0; i < remoteCapabilities.codecs.length; i++) {\n+          var rCodec = remoteCapabilities.codecs[i];\n+          if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&\n+              lCodec.clockRate === rCodec.clockRate &&\n+              lCodec.numChannels === rCodec.numChannels) {\n+            // push rCodec so we reply with offerer payload type\n+            commonCapabilities.codecs.push(rCodec);\n+\n+            // FIXME: also need to calculate intersection between\n+            // .rtcpFeedback and .parameters\n+            break;\n+          }\n+        }\n+      });\n+\n+      localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {\n+        for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) {\n+          var rHeaderExtension = remoteCapabilities.headerExtensions[i];\n+          if (lHeaderExtension.uri === rHeaderExtension.uri) {\n+            commonCapabilities.headerExtensions.push(rHeaderExtension);\n+            break;\n+          }\n+        }\n+      });\n+\n+      // FIXME: fecMechanisms\n+      return commonCapabilities;\n+    };\n+\n+    // Create ICE gatherer, ICE transport and DTLS transport.\n+    window.RTCPeerConnection.prototype._createIceAndDtlsTransports =\n+        function(mid, sdpMLineIndex) {\n+      var self = this;\n+      var iceGatherer = new RTCIceGatherer(self.iceOptions);\n+      var iceTransport = new RTCIceTransport(iceGatherer);\n+      iceGatherer.onlocalcandidate = function(evt) {\n+        var event = {};\n+        event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};\n+\n+        var cand = evt.candidate;\n+        // Edge emits an empty object for RTCIceCandidateComplete‥\n+        if (!cand || Object.keys(cand).length === 0) {\n+          // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet.\n+          if (iceGatherer.state === undefined) {\n+            iceGatherer.state = 'completed';\n+          }\n+\n+          // Emit a candidate with type endOfCandidates to make the samples work.\n+          // Edge requires addIceCandidate with this empty candidate to start checking.\n+          // The real solution is to signal end-of-candidates to the other side when\n+          // getting the null candidate but some apps (like the samples) don't do that.\n+          event.candidate.candidate =\n+              'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';\n+        } else {\n+          // RTCIceCandidate doesn't have a component, needs to be added\n+          cand.component = iceTransport.component === 'RTCP' ? 2 : 1;\n+          event.candidate.candidate = SDPUtils.writeCandidate(cand);\n+        }\n+\n+        var complete = self.transceivers.every(function(transceiver) {\n+          return transceiver.iceGatherer &&\n+              transceiver.iceGatherer.state === 'completed';\n+        });\n+        // FIXME: update .localDescription with candidate and (potentially) end-of-candidates.\n+        //     to make this harder, the gatherer might emit candidates before localdescription\n+        //     is set. To make things worse, gather.getLocalCandidates still errors in\n+        //     Edge 10547 when no candidates have been gathered yet.\n+\n+        if (self.onicecandidate !== null) {\n+          // Emit candidate if localDescription is set.\n+          // Also emits null candidate when all gatherers are complete.\n+          if (self.localDescription && self.localDescription.type === '') {\n+            self._localIceCandidatesBuffer.push(event);\n+            if (complete) {\n+              self._localIceCandidatesBuffer.push({});\n+            }\n+          } else {\n+            self.onicecandidate(event);\n+            if (complete) {\n+              self.onicecandidate({});\n+            }\n+          }\n+        }\n+      };\n+      iceTransport.onicestatechange = function() {\n+        self._updateConnectionState();\n+      };\n+\n+      var dtlsTransport = new RTCDtlsTransport(iceTransport);\n+      dtlsTransport.ondtlsstatechange = function() {\n+        self._updateConnectionState();\n+      };\n+      dtlsTransport.onerror = function() {\n+        // onerror does not set state to failed by itself.\n+        dtlsTransport.state = 'failed';\n+        self._updateConnectionState();\n+      };\n+\n+      return {\n+        iceGatherer: iceGatherer,\n+        iceTransport: iceTransport,\n+        dtlsTransport: dtlsTransport\n+      };\n+    };\n+\n+    // Start the RTP Sender and Receiver for a transceiver.\n+    window.RTCPeerConnection.prototype._transceive = function(transceiver,\n+        send, recv) {\n+      var params = this._getCommonCapabilities(transceiver.localCapabilities,\n+          transceiver.remoteCapabilities);\n+      if (send && transceiver.rtpSender) {\n+        params.encodings = [{\n+          ssrc: transceiver.sendSsrc\n+        }];\n+        params.rtcp = {\n+          cname: localCName,\n+          ssrc: transceiver.recvSsrc\n+        };\n+        transceiver.rtpSender.send(params);\n+      }\n+      if (recv && transceiver.rtpReceiver) {\n+        params.encodings = [{\n+          ssrc: transceiver.recvSsrc\n+        }];\n+        params.rtcp = {\n+          cname: transceiver.cname,\n+          ssrc: transceiver.sendSsrc\n+        };\n+        transceiver.rtpReceiver.receive(params);\n+      }\n+    };\n+\n+    window.RTCPeerConnection.prototype.setLocalDescription =\n+        function(description) {\n+      var self = this;\n+      if (description.type === 'offer') {\n+        if (!this._pendingOffer) {\n+        } else {\n+          this.transceivers = this._pendingOffer;\n+          delete this._pendingOffer;\n+        }\n+      } else if (description.type === 'answer') {\n+        var sections = SDPUtils.splitSections(self.remoteDescription.sdp);\n+        var sessionpart = sections.shift();\n+        sections.forEach(function(mediaSection, sdpMLineIndex) {\n+          var transceiver = self.transceivers[sdpMLineIndex];\n+          var iceGatherer = transceiver.iceGatherer;\n+          var iceTransport = transceiver.iceTransport;\n+          var dtlsTransport = transceiver.dtlsTransport;\n+          var localCapabilities = transceiver.localCapabilities;\n+          var remoteCapabilities = transceiver.remoteCapabilities;\n+\n+          var remoteIceParameters = SDPUtils.getIceParameters(mediaSection,\n+              sessionpart);\n+          iceTransport.start(iceGatherer, remoteIceParameters, 'controlled');\n+\n+          var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,\n+              sessionpart);\n+          dtlsTransport.start(remoteDtlsParameters);\n+\n+          // Calculate intersection of capabilities.\n+          var params = self._getCommonCapabilities(localCapabilities,\n+              remoteCapabilities);\n+          self._transceive(transceiver,\n+              params.codecs.length > 0,\n+              false);\n+        });\n+      }\n+\n+      this.localDescription = description;\n+      switch (description.type) {\n+        case 'offer':\n+          this._updateSignalingState('have-local-offer');\n+          break;\n+        case 'answer':\n+          this._updateSignalingState('stable');\n+          break;\n+        default:\n+          throw new TypeError('unsupported type \"' + description.type + '\"');\n+      }\n+\n+      window.setTimeout(this._emitBufferedCandidates.bind(this), 50);","path":"adapter.js","position":684,"original_position":684,"commit_id":"795da688e6d136d5ec515c6eb5921fd6f4c96258","original_commit_id":"795da688e6d136d5ec515c6eb5921fd6f4c96258","user":{"login":"fippo","id":289731,"avatar_url":"https://avatars.githubusercontent.com/u/289731?v=3","gravatar_id":"","url":"https://api.github.com/users/fippo","html_url":"https://github.com/fippo","followers_url":"https://api.github.com/users/fippo/followers","following_url":"https://api.github.com/users/fippo/following{/other_user}","gists_url":"https://api.github.com/users/fippo/gists{/gist_id}","starred_url":"https://api.github.com/users/fippo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fippo/subscriptions","organizations_url":"https://api.github.com/users/fippo/orgs","repos_url":"https://api.github.com/users/fippo/repos","events_url":"https://api.github.com/users/fippo/events{/privacy}","received_events_url":"https://api.github.com/users/fippo/received_events","type":"User","site_admin":false},"body":"FIXME: find a better way (or add a comment)","created_at":"2015-11-12T23:09:23Z","updated_at":"2015-11-12T23:09:23Z","html_url":"https://github.com/webrtc/adapter/pull/136#discussion_r44729200","pull_request_url":"https://api.github.com/repos/webrtc/adapter/pulls/136","_links":{"self":{"href":"https://api.github.com/repos/webrtc/adapter/pulls/comments/44729200"},"html":{"href":"https://github.com/webrtc/adapter/pull/136#discussion_r44729200"},"pull_request":{"href":"https://api.github.com/repos/webrtc/adapter/pulls/136"}}},"pull_request":{"url":"https://api.github.com/repos/webrtc/adapter/pulls/136","id":45233903,"html_url":"https://github.com/webrtc/adapter/pull/136","diff_url":"https://github.com/webrtc/adapter/pull/136.diff","patch_url":"https://github.com/webrtc/adapter/pull/136.patch","issue_url":"https://api.github.com/repos/webrtc/adapter/issues/136","number":136,"state":"open","locked":false,"title":"shim rtcpeerconnection ontop of ortc","user":{"login":"fippo","id":289731,"avatar_url":"https://avatars.githubusercontent.com/u/289731?v=3","gravatar_id":"","url":"https://api.github.com/users/fippo","html_url":"https://github.com/fippo","followers_url":"https://api.github.com/users/fippo/followers","following_url":"https://api.github.com/users/fippo/following{/other_user}","gists_url":"https://api.github.com/users/fippo/gists{/gist_id}","starred_url":"https://api.github.com/users/fippo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fippo/subscriptions","organizations_url":"https://api.github.com/users/fippo/orgs","repos_url":"https://api.github.com/users/fippo/repos","events_url":"https://api.github.com/users/fippo/events{/privacy}","received_events_url":"https://api.github.com/users/fippo/received_events","type":"User","site_admin":false},"body":"@juberti @pthatcherg I think you might be interested in this. A couple of rough edges but it passes most tests here (pending #131) and makes many of the samples work.\r\n\r\nMany thanks to @shijuns and @aboba for answering all of my questions.","created_at":"2015-09-16T22:50:15Z","updated_at":"2015-11-12T23:09:23Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e1db3dbb2d598a1d92a29afc4a8a639e00a5d238","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/webrtc/adapter/pulls/136/commits","review_comments_url":"https://api.github.com/repos/webrtc/adapter/pulls/136/comments","review_comment_url":"https://api.github.com/repos/webrtc/adapter/pulls/comments{/number}","comments_url":"https://api.github.com/repos/webrtc/adapter/issues/136/comments","statuses_url":"https://api.github.com/repos/webrtc/adapter/statuses/795da688e6d136d5ec515c6eb5921fd6f4c96258","head":{"label":"fippo:shim-ortc","ref":"shim-ortc","sha":"795da688e6d136d5ec515c6eb5921fd6f4c96258","user":{"login":"fippo","id":289731,"avatar_url":"https://avatars.githubusercontent.com/u/289731?v=3","gravatar_id":"","url":"https://api.github.com/users/fippo","html_url":"https://github.com/fippo","followers_url":"https://api.github.com/users/fippo/followers","following_url":"https://api.github.com/users/fippo/following{/other_user}","gists_url":"https://api.github.com/users/fippo/gists{/gist_id}","starred_url":"https://api.github.com/users/fippo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fippo/subscriptions","organizations_url":"https://api.github.com/users/fippo/orgs","repos_url":"https://api.github.com/users/fippo/repos","events_url":"https://api.github.com/users/fippo/events{/privacy}","received_events_url":"https://api.github.com/users/fippo/received_events","type":"User","site_admin":false},"repo":{"id":30330866,"name":"adapter","full_name":"fippo/adapter","owner":{"login":"fippo","id":289731,"avatar_url":"https://avatars.githubusercontent.com/u/289731?v=3","gravatar_id":"","url":"https://api.github.com/users/fippo","html_url":"https://github.com/fippo","followers_url":"https://api.github.com/users/fippo/followers","following_url":"https://api.github.com/users/fippo/following{/other_user}","gists_url":"https://api.github.com/users/fippo/gists{/gist_id}","starred_url":"https://api.github.com/users/fippo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fippo/subscriptions","organizations_url":"https://api.github.com/users/fippo/orgs","repos_url":"https://api.github.com/users/fippo/repos","events_url":"https://api.github.com/users/fippo/events{/privacy}","received_events_url":"https://api.github.com/users/fippo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/fippo/adapter","description":"Shim to insulate apps from spec changes and prefix differences.","fork":true,"url":"https://api.github.com/repos/fippo/adapter","forks_url":"https://api.github.com/repos/fippo/adapter/forks","keys_url":"https://api.github.com/repos/fippo/adapter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fippo/adapter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fippo/adapter/teams","hooks_url":"https://api.github.com/repos/fippo/adapter/hooks","issue_events_url":"https://api.github.com/repos/fippo/adapter/issues/events{/number}","events_url":"https://api.github.com/repos/fippo/adapter/events","assignees_url":"https://api.github.com/repos/fippo/adapter/assignees{/user}","branches_url":"https://api.github.com/repos/fippo/adapter/branches{/branch}","tags_url":"https://api.github.com/repos/fippo/adapter/tags","blobs_url":"https://api.github.com/repos/fippo/adapter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fippo/adapter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fippo/adapter/git/refs{/sha}","trees_url":"https://api.github.com/repos/fippo/adapter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fippo/adapter/statuses/{sha}","languages_url":"https://api.github.com/repos/fippo/adapter/languages","stargazers_url":"https://api.github.com/repos/fippo/adapter/stargazers","contributors_url":"https://api.github.com/repos/fippo/adapter/contributors","subscribers_url":"https://api.github.com/repos/fippo/adapter/subscribers","subscription_url":"https://api.github.com/repos/fippo/adapter/subscription","commits_url":"https://api.github.com/repos/fippo/adapter/commits{/sha}","git_commits_url":"https://api.github.com/repos/fippo/adapter/git/commits{/sha}","comments_url":"https://api.github.com/repos/fippo/adapter/comments{/number}","issue_comment_url":"https://api.github.com/repos/fippo/adapter/issues/comments{/number}","contents_url":"https://api.github.com/repos/fippo/adapter/contents/{+path}","compare_url":"https://api.github.com/repos/fippo/adapter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fippo/adapter/merges","archive_url":"https://api.github.com/repos/fippo/adapter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fippo/adapter/downloads","issues_url":"https://api.github.com/repos/fippo/adapter/issues{/number}","pulls_url":"https://api.github.com/repos/fippo/adapter/pulls{/number}","milestones_url":"https://api.github.com/repos/fippo/adapter/milestones{/number}","notifications_url":"https://api.github.com/repos/fippo/adapter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fippo/adapter/labels{/name}","releases_url":"https://api.github.com/repos/fippo/adapter/releases{/id}","created_at":"2015-02-05T01:20:35Z","updated_at":"2015-10-23T04:34:15Z","pushed_at":"2015-11-05T16:25:53Z","git_url":"git://github.com/fippo/adapter.git","ssh_url":"git@github.com:fippo/adapter.git","clone_url":"https://github.com/fippo/adapter.git","svn_url":"https://github.com/fippo/adapter","homepage":null,"size":3428,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"open_issues_count":1,"forks":3,"open_issues":1,"watchers":1,"default_branch":"master"}},"base":{"label":"webrtc:master","ref":"master","sha":"0d75c47aa4c4fd05a879f59801b8eb3f10b1f13b","user":{"login":"webrtc","id":10526312,"avatar_url":"https://avatars.githubusercontent.com/u/10526312?v=3","gravatar_id":"","url":"https://api.github.com/users/webrtc","html_url":"https://github.com/webrtc","followers_url":"https://api.github.com/users/webrtc/followers","following_url":"https://api.github.com/users/webrtc/following{/other_user}","gists_url":"https://api.github.com/users/webrtc/gists{/gist_id}","starred_url":"https://api.github.com/users/webrtc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webrtc/subscriptions","organizations_url":"https://api.github.com/users/webrtc/orgs","repos_url":"https://api.github.com/users/webrtc/repos","events_url":"https://api.github.com/users/webrtc/events{/privacy}","received_events_url":"https://api.github.com/users/webrtc/received_events","type":"Organization","site_admin":false},"repo":{"id":30027163,"name":"adapter","full_name":"webrtc/adapter","owner":{"login":"webrtc","id":10526312,"avatar_url":"https://avatars.githubusercontent.com/u/10526312?v=3","gravatar_id":"","url":"https://api.github.com/users/webrtc","html_url":"https://github.com/webrtc","followers_url":"https://api.github.com/users/webrtc/followers","following_url":"https://api.github.com/users/webrtc/following{/other_user}","gists_url":"https://api.github.com/users/webrtc/gists{/gist_id}","starred_url":"https://api.github.com/users/webrtc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webrtc/subscriptions","organizations_url":"https://api.github.com/users/webrtc/orgs","repos_url":"https://api.github.com/users/webrtc/repos","events_url":"https://api.github.com/users/webrtc/events{/privacy}","received_events_url":"https://api.github.com/users/webrtc/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/webrtc/adapter","description":"Shim to insulate apps from spec changes and prefix differences.","fork":false,"url":"https://api.github.com/repos/webrtc/adapter","forks_url":"https://api.github.com/repos/webrtc/adapter/forks","keys_url":"https://api.github.com/repos/webrtc/adapter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/webrtc/adapter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/webrtc/adapter/teams","hooks_url":"https://api.github.com/repos/webrtc/adapter/hooks","issue_events_url":"https://api.github.com/repos/webrtc/adapter/issues/events{/number}","events_url":"https://api.github.com/repos/webrtc/adapter/events","assignees_url":"https://api.github.com/repos/webrtc/adapter/assignees{/user}","branches_url":"https://api.github.com/repos/webrtc/adapter/branches{/branch}","tags_url":"https://api.github.com/repos/webrtc/adapter/tags","blobs_url":"https://api.github.com/repos/webrtc/adapter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/webrtc/adapter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/webrtc/adapter/git/refs{/sha}","trees_url":"https://api.github.com/repos/webrtc/adapter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/webrtc/adapter/statuses/{sha}","languages_url":"https://api.github.com/repos/webrtc/adapter/languages","stargazers_url":"https://api.github.com/repos/webrtc/adapter/stargazers","contributors_url":"https://api.github.com/repos/webrtc/adapter/contributors","subscribers_url":"https://api.github.com/repos/webrtc/adapter/subscribers","subscription_url":"https://api.github.com/repos/webrtc/adapter/subscription","commits_url":"https://api.github.com/repos/webrtc/adapter/commits{/sha}","git_commits_url":"https://api.github.com/repos/webrtc/adapter/git/commits{/sha}","comments_url":"https://api.github.com/repos/webrtc/adapter/comments{/number}","issue_comment_url":"https://api.github.com/repos/webrtc/adapter/issues/comments{/number}","contents_url":"https://api.github.com/repos/webrtc/adapter/contents/{+path}","compare_url":"https://api.github.com/repos/webrtc/adapter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/webrtc/adapter/merges","archive_url":"https://api.github.com/repos/webrtc/adapter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/webrtc/adapter/downloads","issues_url":"https://api.github.com/repos/webrtc/adapter/issues{/number}","pulls_url":"https://api.github.com/repos/webrtc/adapter/pulls{/number}","milestones_url":"https://api.github.com/repos/webrtc/adapter/milestones{/number}","notifications_url":"https://api.github.com/repos/webrtc/adapter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/webrtc/adapter/labels{/name}","releases_url":"https://api.github.com/repos/webrtc/adapter/releases{/id}","created_at":"2015-01-29T15:41:40Z","updated_at":"2015-11-12T20:01:29Z","pushed_at":"2015-11-12T19:59:19Z","git_url":"git://github.com/webrtc/adapter.git","ssh_url":"git@github.com:webrtc/adapter.git","clone_url":"https://github.com/webrtc/adapter.git","svn_url":"https://github.com/webrtc/adapter","homepage":null,"size":4888,"stargazers_count":171,"watchers_count":171,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":52,"mirror_url":null,"open_issues_count":21,"forks":52,"open_issues":21,"watchers":171,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/webrtc/adapter/pulls/136"},"html":{"href":"https://github.com/webrtc/adapter/pull/136"},"issue":{"href":"https://api.github.com/repos/webrtc/adapter/issues/136"},"comments":{"href":"https://api.github.com/repos/webrtc/adapter/issues/136/comments"},"review_comments":{"href":"https://api.github.com/repos/webrtc/adapter/pulls/136/comments"},"review_comment":{"href":"https://api.github.com/repos/webrtc/adapter/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/webrtc/adapter/pulls/136/commits"},"statuses":{"href":"https://api.github.com/repos/webrtc/adapter/statuses/795da688e6d136d5ec515c6eb5921fd6f4c96258"}}}}