webrtc-adapter
Advanced tools
Comparing version 3.3.1 to 3.3.2
@@ -24,3 +24,3 @@ 'use strict'; | ||
}, | ||
// Use this if you do not want MS edge shim to be included. | ||
// Use this if you do not want Microsoft Edge shim to be included. | ||
adapterNoEdge: { | ||
@@ -32,4 +32,3 @@ src: ['./src/js/adapter_core.js'], | ||
ignore: [ | ||
'./src/js/edge/edge_shim.js', | ||
'./src/js/edge/edge_sdp.js' | ||
'./src/js/edge/edge_shim.js' | ||
], | ||
@@ -42,3 +41,3 @@ browserifyOptions: { | ||
}, | ||
// Use this if you do not want MS edge shim to be included and do not | ||
// Use this if you do not want Microsoft Edge shim to be included and do not | ||
// want adapter to expose anything to the global scope. | ||
@@ -50,4 +49,3 @@ adapterNoEdgeAndNoGlobalObject: { | ||
ignore: [ | ||
'./src/js/edge/edge_shim.js', | ||
'./src/js/edge/edge_sdp.js' | ||
'./src/js/edge/edge_shim.js' | ||
] | ||
@@ -54,0 +52,0 @@ } |
@@ -532,10 +532,10 @@ (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){ | ||
delete constraints.video.facingMode; | ||
var match; | ||
var matches; | ||
if (face.exact === 'environment' || face.ideal === 'environment') { | ||
match = 'back'; | ||
matches = ['back', 'rear']; | ||
} else if (face.exact === 'user' || face.ideal === 'user') { | ||
match = 'front'; | ||
matches = ['front']; | ||
} | ||
if (match) { | ||
// Look for match in label. | ||
if (matches) { | ||
// Look for matches in label, or use last cam for back (typical). | ||
return navigator.mediaDevices.enumerateDevices() | ||
@@ -547,4 +547,9 @@ .then(function(devices) { | ||
var dev = devices.find(function(d) { | ||
return d.label.toLowerCase().indexOf(match) !== -1; | ||
return matches.some(function(match) { | ||
return d.label.toLowerCase().indexOf(match) !== -1; | ||
}); | ||
}); | ||
if (!dev && devices.length && matches.indexOf('back') !== -1) { | ||
dev = devices[devices.length - 1]; // more likely the back cam | ||
} | ||
if (dev) { | ||
@@ -551,0 +556,0 @@ constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : |
@@ -532,10 +532,10 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function 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){ | ||
delete constraints.video.facingMode; | ||
var match; | ||
var matches; | ||
if (face.exact === 'environment' || face.ideal === 'environment') { | ||
match = 'back'; | ||
matches = ['back', 'rear']; | ||
} else if (face.exact === 'user' || face.ideal === 'user') { | ||
match = 'front'; | ||
matches = ['front']; | ||
} | ||
if (match) { | ||
// Look for match in label. | ||
if (matches) { | ||
// Look for matches in label, or use last cam for back (typical). | ||
return navigator.mediaDevices.enumerateDevices() | ||
@@ -547,4 +547,9 @@ .then(function(devices) { | ||
var dev = devices.find(function(d) { | ||
return d.label.toLowerCase().indexOf(match) !== -1; | ||
return matches.some(function(match) { | ||
return d.label.toLowerCase().indexOf(match) !== -1; | ||
}); | ||
}); | ||
if (!dev && devices.length && matches.indexOf('back') !== -1) { | ||
dev = devices[devices.length - 1]; // more likely the back cam | ||
} | ||
if (dev) { | ||
@@ -551,0 +556,0 @@ constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : |
{ | ||
"name": "webrtc-adapter", | ||
"version": "3.3.1", | ||
"version": "3.3.2", | ||
"description": "A shim to insulate apps from WebRTC spec changes and browser prefix differences", | ||
@@ -5,0 +5,0 @@ "license": "BSD-3-Clause", |
@@ -83,10 +83,10 @@ /* | ||
delete constraints.video.facingMode; | ||
var match; | ||
var matches; | ||
if (face.exact === 'environment' || face.ideal === 'environment') { | ||
match = 'back'; | ||
matches = ['back', 'rear']; | ||
} else if (face.exact === 'user' || face.ideal === 'user') { | ||
match = 'front'; | ||
matches = ['front']; | ||
} | ||
if (match) { | ||
// Look for match in label. | ||
if (matches) { | ||
// Look for matches in label, or use last cam for back (typical). | ||
return navigator.mediaDevices.enumerateDevices() | ||
@@ -98,4 +98,9 @@ .then(function(devices) { | ||
var dev = devices.find(function(d) { | ||
return d.label.toLowerCase().indexOf(match) !== -1; | ||
return matches.some(function(match) { | ||
return d.label.toLowerCase().indexOf(match) !== -1; | ||
}); | ||
}); | ||
if (!dev && devices.length && matches.indexOf('back') !== -1) { | ||
dev = devices[devices.length - 1]; // more likely the back cam | ||
} | ||
if (dev) { | ||
@@ -102,0 +107,0 @@ constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : |
@@ -11,67 +11,7 @@ /* | ||
var SDPUtils = require('sdp'); | ||
var browserDetails = require('../utils').browserDetails; | ||
var shimRTCPeerConnection = require('./rtcpeerconnection_shim'); | ||
// sort tracks such that they follow an a-v-a-v... | ||
// pattern. | ||
function sortTracks(tracks) { | ||
var audioTracks = tracks.filter(function(track) { | ||
return track.kind === 'audio'; | ||
}); | ||
var videoTracks = tracks.filter(function(track) { | ||
return track.kind === 'video'; | ||
}); | ||
tracks = []; | ||
while (audioTracks.length || videoTracks.length) { | ||
if (audioTracks.length) { | ||
tracks.push(audioTracks.shift()); | ||
} | ||
if (videoTracks.length) { | ||
tracks.push(videoTracks.shift()); | ||
} | ||
} | ||
return tracks; | ||
} | ||
// Edge does not like | ||
// 1) stun: | ||
// 2) turn: that does not have all of turn:host:port?transport=udp | ||
// 3) turn: with ipv6 addresses | ||
// 4) turn: occurring muliple times | ||
function filterIceServers(iceServers) { | ||
var hasTurn = false; | ||
iceServers = JSON.parse(JSON.stringify(iceServers)); | ||
return iceServers.filter(function(server) { | ||
if (server && (server.urls || server.url)) { | ||
var urls = server.urls || server.url; | ||
if (server.url && !server.urls) { | ||
console.warn('RTCIceServer.url is deprecated! Use urls instead.'); | ||
} | ||
var isString = typeof urls === 'string'; | ||
if (isString) { | ||
urls = [urls]; | ||
} | ||
urls = urls.filter(function(url) { | ||
var validTurn = url.indexOf('turn:') === 0 && | ||
url.indexOf('transport=udp') !== -1 && | ||
url.indexOf('turn:[') === -1 && | ||
!hasTurn; | ||
if (validTurn) { | ||
hasTurn = true; | ||
return true; | ||
} | ||
return url.indexOf('stun:') === 0 && | ||
browserDetails.version >= 14393; | ||
}); | ||
delete server.url; | ||
server.urls = isString ? urls[0] : urls; | ||
return !!urls.length; | ||
} | ||
return false; | ||
}); | ||
} | ||
var edgeShim = { | ||
module.exports = { | ||
shimGetUserMedia: require('./getusermedia'), | ||
shimPeerConnection: function() { | ||
@@ -110,1209 +50,3 @@ if (window.RTCIceGatherer) { | ||
} | ||
window.RTCPeerConnection = function(config) { | ||
var self = this; | ||
var _eventTarget = document.createDocumentFragment(); | ||
['addEventListener', 'removeEventListener', 'dispatchEvent'] | ||
.forEach(function(method) { | ||
self[method] = _eventTarget[method].bind(_eventTarget); | ||
}); | ||
this.onicecandidate = null; | ||
this.onaddstream = null; | ||
this.ontrack = null; | ||
this.onremovestream = null; | ||
this.onsignalingstatechange = null; | ||
this.oniceconnectionstatechange = null; | ||
this.onicegatheringstatechange = null; | ||
this.onnegotiationneeded = null; | ||
this.ondatachannel = null; | ||
this.canTrickleIceCandidates = null; | ||
this.localStreams = []; | ||
this.remoteStreams = []; | ||
this.getLocalStreams = function() { | ||
return self.localStreams; | ||
}; | ||
this.getRemoteStreams = function() { | ||
return self.remoteStreams; | ||
}; | ||
this.localDescription = new RTCSessionDescription({ | ||
type: '', | ||
sdp: '' | ||
}); | ||
this.remoteDescription = new RTCSessionDescription({ | ||
type: '', | ||
sdp: '' | ||
}); | ||
this.signalingState = 'stable'; | ||
this.iceConnectionState = 'new'; | ||
this.iceGatheringState = 'new'; | ||
this.iceOptions = { | ||
gatherPolicy: 'all', | ||
iceServers: [] | ||
}; | ||
if (config && config.iceTransportPolicy) { | ||
switch (config.iceTransportPolicy) { | ||
case 'all': | ||
case 'relay': | ||
this.iceOptions.gatherPolicy = config.iceTransportPolicy; | ||
break; | ||
default: | ||
// don't set iceTransportPolicy. | ||
break; | ||
} | ||
} | ||
this.usingBundle = config && config.bundlePolicy === 'max-bundle'; | ||
if (config && config.iceServers) { | ||
this.iceOptions.iceServers = filterIceServers(config.iceServers); | ||
} | ||
this._config = config || {}; | ||
// per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... | ||
// everything that is needed to describe a SDP m-line. | ||
this.transceivers = []; | ||
// since the iceGatherer is currently created in createOffer but we | ||
// must not emit candidates until after setLocalDescription we buffer | ||
// them in this array. | ||
this._localIceCandidatesBuffer = []; | ||
}; | ||
window.RTCPeerConnection.prototype._emitGatheringStateChange = function() { | ||
var event = new Event('icegatheringstatechange'); | ||
this.dispatchEvent(event); | ||
if (this.onicegatheringstatechange !== null) { | ||
this.onicegatheringstatechange(event); | ||
} | ||
}; | ||
window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { | ||
var self = this; | ||
var sections = SDPUtils.splitSections(self.localDescription.sdp); | ||
// FIXME: need to apply ice candidates in a way which is async but | ||
// in-order | ||
this._localIceCandidatesBuffer.forEach(function(event) { | ||
var end = !event.candidate || Object.keys(event.candidate).length === 0; | ||
if (end) { | ||
for (var j = 1; j < sections.length; j++) { | ||
if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) { | ||
sections[j] += 'a=end-of-candidates\r\n'; | ||
} | ||
} | ||
} else { | ||
sections[event.candidate.sdpMLineIndex + 1] += | ||
'a=' + event.candidate.candidate + '\r\n'; | ||
} | ||
self.localDescription.sdp = sections.join(''); | ||
self.dispatchEvent(event); | ||
if (self.onicecandidate !== null) { | ||
self.onicecandidate(event); | ||
} | ||
if (!event.candidate && self.iceGatheringState !== 'complete') { | ||
var complete = self.transceivers.every(function(transceiver) { | ||
return transceiver.iceGatherer && | ||
transceiver.iceGatherer.state === 'completed'; | ||
}); | ||
if (complete && self.iceGatheringStateChange !== 'complete') { | ||
self.iceGatheringState = 'complete'; | ||
self._emitGatheringStateChange(); | ||
} | ||
} | ||
}); | ||
this._localIceCandidatesBuffer = []; | ||
}; | ||
window.RTCPeerConnection.prototype.getConfiguration = function() { | ||
return this._config; | ||
}; | ||
window.RTCPeerConnection.prototype.addStream = function(stream) { | ||
if (browserDetails.version >= 15025) { | ||
this.localStreams.push(stream); | ||
} else { | ||
// Clone is necessary for local demos mostly, attaching directly | ||
// to two different senders does not work (build 10547). | ||
// Fixed in 15025 (or earlier) | ||
var clonedStream = stream.clone(); | ||
stream.getTracks().forEach(function(track, idx) { | ||
var clonedTrack = clonedStream.getTracks()[idx]; | ||
track.addEventListener('enabled', function(event) { | ||
clonedTrack.enabled = event.enabled; | ||
}); | ||
}); | ||
this.localStreams.push(clonedStream); | ||
} | ||
this._maybeFireNegotiationNeeded(); | ||
}; | ||
window.RTCPeerConnection.prototype.removeStream = function(stream) { | ||
var idx = this.localStreams.indexOf(stream); | ||
if (idx > -1) { | ||
this.localStreams.splice(idx, 1); | ||
this._maybeFireNegotiationNeeded(); | ||
} | ||
}; | ||
window.RTCPeerConnection.prototype.getSenders = function() { | ||
return this.transceivers.filter(function(transceiver) { | ||
return !!transceiver.rtpSender; | ||
}) | ||
.map(function(transceiver) { | ||
return transceiver.rtpSender; | ||
}); | ||
}; | ||
window.RTCPeerConnection.prototype.getReceivers = function() { | ||
return this.transceivers.filter(function(transceiver) { | ||
return !!transceiver.rtpReceiver; | ||
}) | ||
.map(function(transceiver) { | ||
return transceiver.rtpReceiver; | ||
}); | ||
}; | ||
// Determines the intersection of local and remote capabilities. | ||
window.RTCPeerConnection.prototype._getCommonCapabilities = | ||
function(localCapabilities, remoteCapabilities) { | ||
var commonCapabilities = { | ||
codecs: [], | ||
headerExtensions: [], | ||
fecMechanisms: [] | ||
}; | ||
var findCodecByPayloadType = function(pt, codecs) { | ||
pt = parseInt(pt, 10); | ||
for (var i = 0; i < codecs.length; i++) { | ||
if (codecs[i].payloadType === pt || | ||
codecs[i].preferredPayloadType === pt) { | ||
return codecs[i]; | ||
} | ||
} | ||
}; | ||
var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { | ||
var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); | ||
var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); | ||
return lCodec && rCodec && | ||
lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); | ||
}; | ||
localCapabilities.codecs.forEach(function(lCodec) { | ||
for (var i = 0; i < remoteCapabilities.codecs.length; i++) { | ||
var rCodec = remoteCapabilities.codecs[i]; | ||
if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && | ||
lCodec.clockRate === rCodec.clockRate) { | ||
if (lCodec.name.toLowerCase() === 'rtx' && | ||
lCodec.parameters && rCodec.parameters.apt) { | ||
// for RTX we need to find the local rtx that has a apt | ||
// which points to the same local codec as the remote one. | ||
if (!rtxCapabilityMatches(lCodec, rCodec, | ||
localCapabilities.codecs, remoteCapabilities.codecs)) { | ||
continue; | ||
} | ||
} | ||
rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy | ||
// number of channels is the highest common number of channels | ||
rCodec.numChannels = Math.min(lCodec.numChannels, | ||
rCodec.numChannels); | ||
// push rCodec so we reply with offerer payload type | ||
commonCapabilities.codecs.push(rCodec); | ||
// determine common feedback mechanisms | ||
rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { | ||
for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { | ||
if (lCodec.rtcpFeedback[j].type === fb.type && | ||
lCodec.rtcpFeedback[j].parameter === fb.parameter) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}); | ||
// FIXME: also need to determine .parameters | ||
// see https://github.com/openpeer/ortc/issues/569 | ||
break; | ||
} | ||
} | ||
}); | ||
localCapabilities.headerExtensions | ||
.forEach(function(lHeaderExtension) { | ||
for (var i = 0; i < remoteCapabilities.headerExtensions.length; | ||
i++) { | ||
var rHeaderExtension = remoteCapabilities.headerExtensions[i]; | ||
if (lHeaderExtension.uri === rHeaderExtension.uri) { | ||
commonCapabilities.headerExtensions.push(rHeaderExtension); | ||
break; | ||
} | ||
} | ||
}); | ||
// FIXME: fecMechanisms | ||
return commonCapabilities; | ||
}; | ||
// Create ICE gatherer, ICE transport and DTLS transport. | ||
window.RTCPeerConnection.prototype._createIceAndDtlsTransports = | ||
function(mid, sdpMLineIndex) { | ||
var self = this; | ||
var iceGatherer = new RTCIceGatherer(self.iceOptions); | ||
var iceTransport = new RTCIceTransport(iceGatherer); | ||
iceGatherer.onlocalcandidate = function(evt) { | ||
var event = new Event('icecandidate'); | ||
event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; | ||
var cand = evt.candidate; | ||
var end = !cand || Object.keys(cand).length === 0; | ||
// Edge emits an empty object for RTCIceCandidateComplete‥ | ||
if (end) { | ||
// polyfill since RTCIceGatherer.state is not implemented in | ||
// Edge 10547 yet. | ||
if (iceGatherer.state === undefined) { | ||
iceGatherer.state = 'completed'; | ||
} | ||
} else { | ||
// RTCIceCandidate doesn't have a component, needs to be added | ||
cand.component = iceTransport.component === 'RTCP' ? 2 : 1; | ||
event.candidate.candidate = SDPUtils.writeCandidate(cand); | ||
} | ||
// update local description. | ||
var sections = SDPUtils.splitSections(self.localDescription.sdp); | ||
if (!end) { | ||
sections[event.candidate.sdpMLineIndex + 1] += | ||
'a=' + event.candidate.candidate + '\r\n'; | ||
} else { | ||
sections[event.candidate.sdpMLineIndex + 1] += | ||
'a=end-of-candidates\r\n'; | ||
} | ||
self.localDescription.sdp = sections.join(''); | ||
var transceivers = self._pendingOffer ? self._pendingOffer : | ||
self.transceivers; | ||
var complete = transceivers.every(function(transceiver) { | ||
return transceiver.iceGatherer && | ||
transceiver.iceGatherer.state === 'completed'; | ||
}); | ||
// Emit candidate if localDescription is set. | ||
// Also emits null candidate when all gatherers are complete. | ||
switch (self.iceGatheringState) { | ||
case 'new': | ||
if (!end) { | ||
self._localIceCandidatesBuffer.push(event); | ||
} | ||
if (end && complete) { | ||
self._localIceCandidatesBuffer.push( | ||
new Event('icecandidate')); | ||
} | ||
break; | ||
case 'gathering': | ||
self._emitBufferedCandidates(); | ||
if (!end) { | ||
self.dispatchEvent(event); | ||
if (self.onicecandidate !== null) { | ||
self.onicecandidate(event); | ||
} | ||
} | ||
if (complete) { | ||
self.dispatchEvent(new Event('icecandidate')); | ||
if (self.onicecandidate !== null) { | ||
self.onicecandidate(new Event('icecandidate')); | ||
} | ||
self.iceGatheringState = 'complete'; | ||
self._emitGatheringStateChange(); | ||
} | ||
break; | ||
case 'complete': | ||
// should not happen... currently! | ||
break; | ||
default: // no-op. | ||
break; | ||
} | ||
}; | ||
iceTransport.onicestatechange = function() { | ||
self._updateConnectionState(); | ||
}; | ||
var dtlsTransport = new RTCDtlsTransport(iceTransport); | ||
dtlsTransport.ondtlsstatechange = function() { | ||
self._updateConnectionState(); | ||
}; | ||
dtlsTransport.onerror = function() { | ||
// onerror does not set state to failed by itself. | ||
dtlsTransport.state = 'failed'; | ||
self._updateConnectionState(); | ||
}; | ||
return { | ||
iceGatherer: iceGatherer, | ||
iceTransport: iceTransport, | ||
dtlsTransport: dtlsTransport | ||
}; | ||
}; | ||
// Destroy ICE gatherer, ICE transport and DTLS transport. | ||
// Without triggering the callbacks. | ||
window.RTCPeerConnection.prototype._disposeIceAndDtlsTransports = | ||
function(sdpMLineIndex) { | ||
var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; | ||
if (iceGatherer) { | ||
delete iceGatherer.onlocalcandidate; | ||
delete this.transceivers[sdpMLineIndex].iceGatherer; | ||
} | ||
var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; | ||
if (iceTransport) { | ||
delete iceTransport.onicestatechange; | ||
delete this.transceivers[sdpMLineIndex].iceTransport; | ||
} | ||
var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; | ||
if (dtlsTransport) { | ||
delete dtlsTransport.ondtlssttatechange; | ||
delete dtlsTransport.onerror; | ||
delete this.transceivers[sdpMLineIndex].dtlsTransport; | ||
} | ||
}; | ||
// Start the RTP Sender and Receiver for a transceiver. | ||
window.RTCPeerConnection.prototype._transceive = function(transceiver, | ||
send, recv) { | ||
var params = this._getCommonCapabilities(transceiver.localCapabilities, | ||
transceiver.remoteCapabilities); | ||
if (send && transceiver.rtpSender) { | ||
params.encodings = transceiver.sendEncodingParameters; | ||
params.rtcp = { | ||
cname: SDPUtils.localCName, | ||
compound: transceiver.rtcpParameters.compound | ||
}; | ||
if (transceiver.recvEncodingParameters.length) { | ||
params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; | ||
} | ||
transceiver.rtpSender.send(params); | ||
} | ||
if (recv && transceiver.rtpReceiver) { | ||
// remove RTX field in Edge 14942 | ||
if (transceiver.kind === 'video' | ||
&& transceiver.recvEncodingParameters | ||
&& browserDetails.version < 15019) { | ||
transceiver.recvEncodingParameters.forEach(function(p) { | ||
delete p.rtx; | ||
}); | ||
} | ||
params.encodings = transceiver.recvEncodingParameters; | ||
params.rtcp = { | ||
cname: transceiver.rtcpParameters.cname, | ||
compound: transceiver.rtcpParameters.compound | ||
}; | ||
if (transceiver.sendEncodingParameters.length) { | ||
params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; | ||
} | ||
transceiver.rtpReceiver.receive(params); | ||
} | ||
}; | ||
window.RTCPeerConnection.prototype.setLocalDescription = | ||
function(description) { | ||
var self = this; | ||
var sections; | ||
var sessionpart; | ||
if (description.type === 'offer') { | ||
// FIXME: What was the purpose of this empty if statement? | ||
// if (!this._pendingOffer) { | ||
// } else { | ||
if (this._pendingOffer) { | ||
// VERY limited support for SDP munging. Limited to: | ||
// * changing the order of codecs | ||
sections = SDPUtils.splitSections(description.sdp); | ||
sessionpart = sections.shift(); | ||
sections.forEach(function(mediaSection, sdpMLineIndex) { | ||
var caps = SDPUtils.parseRtpParameters(mediaSection); | ||
self._pendingOffer[sdpMLineIndex].localCapabilities = caps; | ||
}); | ||
this.transceivers = this._pendingOffer; | ||
delete this._pendingOffer; | ||
} | ||
} else if (description.type === 'answer') { | ||
sections = SDPUtils.splitSections(self.remoteDescription.sdp); | ||
sessionpart = sections.shift(); | ||
var isIceLite = SDPUtils.matchPrefix(sessionpart, | ||
'a=ice-lite').length > 0; | ||
sections.forEach(function(mediaSection, sdpMLineIndex) { | ||
var transceiver = self.transceivers[sdpMLineIndex]; | ||
var iceGatherer = transceiver.iceGatherer; | ||
var iceTransport = transceiver.iceTransport; | ||
var dtlsTransport = transceiver.dtlsTransport; | ||
var localCapabilities = transceiver.localCapabilities; | ||
var remoteCapabilities = transceiver.remoteCapabilities; | ||
var rejected = mediaSection.split('\n', 1)[0] | ||
.split(' ', 2)[1] === '0'; | ||
if (!rejected && !transceiver.isDatachannel) { | ||
var remoteIceParameters = SDPUtils.getIceParameters( | ||
mediaSection, sessionpart); | ||
var remoteDtlsParameters = SDPUtils.getDtlsParameters( | ||
mediaSection, sessionpart); | ||
if (isIceLite) { | ||
remoteDtlsParameters.role = 'server'; | ||
} | ||
if (!self.usingBundle || sdpMLineIndex === 0) { | ||
iceTransport.start(iceGatherer, remoteIceParameters, | ||
isIceLite ? 'controlling' : 'controlled'); | ||
dtlsTransport.start(remoteDtlsParameters); | ||
} | ||
// Calculate intersection of capabilities. | ||
var params = self._getCommonCapabilities(localCapabilities, | ||
remoteCapabilities); | ||
// Start the RTCRtpSender. The RTCRtpReceiver for this | ||
// transceiver has already been started in setRemoteDescription. | ||
self._transceive(transceiver, | ||
params.codecs.length > 0, | ||
false); | ||
} | ||
}); | ||
} | ||
this.localDescription = { | ||
type: description.type, | ||
sdp: description.sdp | ||
}; | ||
switch (description.type) { | ||
case 'offer': | ||
this._updateSignalingState('have-local-offer'); | ||
break; | ||
case 'answer': | ||
this._updateSignalingState('stable'); | ||
break; | ||
default: | ||
throw new TypeError('unsupported type "' + description.type + | ||
'"'); | ||
} | ||
// If a success callback was provided, emit ICE candidates after it | ||
// has been executed. Otherwise, emit callback after the Promise is | ||
// resolved. | ||
var hasCallback = arguments.length > 1 && | ||
typeof arguments[1] === 'function'; | ||
if (hasCallback) { | ||
var cb = arguments[1]; | ||
window.setTimeout(function() { | ||
cb(); | ||
if (self.iceGatheringState === 'new') { | ||
self.iceGatheringState = 'gathering'; | ||
self._emitGatheringStateChange(); | ||
} | ||
self._emitBufferedCandidates(); | ||
}, 0); | ||
} | ||
var p = Promise.resolve(); | ||
p.then(function() { | ||
if (!hasCallback) { | ||
if (self.iceGatheringState === 'new') { | ||
self.iceGatheringState = 'gathering'; | ||
self._emitGatheringStateChange(); | ||
} | ||
// Usually candidates will be emitted earlier. | ||
window.setTimeout(self._emitBufferedCandidates.bind(self), 500); | ||
} | ||
}); | ||
return p; | ||
}; | ||
window.RTCPeerConnection.prototype.setRemoteDescription = | ||
function(description) { | ||
var self = this; | ||
var streams = {}; | ||
var receiverList = []; | ||
var sections = SDPUtils.splitSections(description.sdp); | ||
var sessionpart = sections.shift(); | ||
var isIceLite = SDPUtils.matchPrefix(sessionpart, | ||
'a=ice-lite').length > 0; | ||
var usingBundle = SDPUtils.matchPrefix(sessionpart, | ||
'a=group:BUNDLE ').length > 0; | ||
var iceOptions = SDPUtils.matchPrefix(sessionpart, | ||
'a=ice-options:')[0]; | ||
if (iceOptions) { | ||
this.canTrickleIceCandidates = iceOptions.substr(14).split(' ') | ||
.indexOf('trickle') >= 0; | ||
} else { | ||
this.canTrickleIceCandidates = false; | ||
} | ||
sections.forEach(function(mediaSection, sdpMLineIndex) { | ||
var lines = SDPUtils.splitLines(mediaSection); | ||
var mline = lines[0].substr(2).split(' '); | ||
var kind = mline[0]; | ||
var rejected = mline[1] === '0'; | ||
var direction = SDPUtils.getDirection(mediaSection, sessionpart); | ||
var remoteMsid = SDPUtils.parseMsid(mediaSection); | ||
var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:'); | ||
if (mid.length) { | ||
mid = mid[0].substr(6); | ||
} else { | ||
mid = SDPUtils.generateIdentifier(); | ||
} | ||
// Reject datachannels which are not implemented yet. | ||
if (kind === 'application' && mline[2] === 'DTLS/SCTP') { | ||
self.transceivers[sdpMLineIndex] = { | ||
mid: mid, | ||
isDatachannel: true | ||
}; | ||
return; | ||
} | ||
var transceiver; | ||
var iceGatherer; | ||
var iceTransport; | ||
var dtlsTransport; | ||
var rtpSender; | ||
var rtpReceiver; | ||
var sendEncodingParameters; | ||
var recvEncodingParameters; | ||
var localCapabilities; | ||
var track; | ||
// FIXME: ensure the mediaSection has rtcp-mux set. | ||
var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); | ||
var remoteIceParameters; | ||
var remoteDtlsParameters; | ||
if (!rejected) { | ||
remoteIceParameters = SDPUtils.getIceParameters(mediaSection, | ||
sessionpart); | ||
remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, | ||
sessionpart); | ||
remoteDtlsParameters.role = 'client'; | ||
} | ||
recvEncodingParameters = | ||
SDPUtils.parseRtpEncodingParameters(mediaSection); | ||
var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); | ||
var isComplete = SDPUtils.matchPrefix(mediaSection, | ||
'a=end-of-candidates', sessionpart).length > 0; | ||
var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') | ||
.map(function(cand) { | ||
return SDPUtils.parseCandidate(cand); | ||
}) | ||
.filter(function(cand) { | ||
return cand.component === '1'; | ||
}); | ||
if (description.type === 'offer' && !rejected) { | ||
var transports = usingBundle && sdpMLineIndex > 0 ? { | ||
iceGatherer: self.transceivers[0].iceGatherer, | ||
iceTransport: self.transceivers[0].iceTransport, | ||
dtlsTransport: self.transceivers[0].dtlsTransport | ||
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex); | ||
if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { | ||
transports.iceTransport.setRemoteCandidates(cands); | ||
} | ||
localCapabilities = RTCRtpReceiver.getCapabilities(kind); | ||
// filter RTX until additional stuff needed for RTX is implemented | ||
// in adapter.js | ||
if (browserDetails.version < 15019) { | ||
localCapabilities.codecs = localCapabilities.codecs.filter( | ||
function(codec) { | ||
return codec.name !== 'rtx'; | ||
}); | ||
} | ||
sendEncodingParameters = [{ | ||
ssrc: (2 * sdpMLineIndex + 2) * 1001 | ||
}]; | ||
if (direction === 'sendrecv' || direction === 'sendonly') { | ||
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, | ||
kind); | ||
track = rtpReceiver.track; | ||
// FIXME: does not work with Plan B. | ||
if (remoteMsid) { | ||
if (!streams[remoteMsid.stream]) { | ||
streams[remoteMsid.stream] = new MediaStream(); | ||
Object.defineProperty(streams[remoteMsid.stream], 'id', { | ||
get: function() { | ||
return remoteMsid.stream; | ||
} | ||
}); | ||
} | ||
Object.defineProperty(track, 'id', { | ||
get: function() { | ||
return remoteMsid.track; | ||
} | ||
}); | ||
streams[remoteMsid.stream].addTrack(track); | ||
receiverList.push([track, rtpReceiver, | ||
streams[remoteMsid.stream]]); | ||
} else { | ||
if (!streams.default) { | ||
streams.default = new MediaStream(); | ||
} | ||
streams.default.addTrack(track); | ||
receiverList.push([track, rtpReceiver, streams.default]); | ||
} | ||
} | ||
// FIXME: look at direction. | ||
if (self.localStreams.length > 0 && | ||
self.localStreams[0].getTracks().length >= sdpMLineIndex) { | ||
var localTrack; | ||
if (kind === 'audio') { | ||
localTrack = self.localStreams[0].getAudioTracks()[0]; | ||
} else if (kind === 'video') { | ||
localTrack = self.localStreams[0].getVideoTracks()[0]; | ||
} | ||
if (localTrack) { | ||
// add RTX | ||
if (browserDetails.version >= 15019 && kind === 'video') { | ||
sendEncodingParameters[0].rtx = { | ||
ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1 | ||
}; | ||
} | ||
rtpSender = new RTCRtpSender(localTrack, | ||
transports.dtlsTransport); | ||
} | ||
} | ||
self.transceivers[sdpMLineIndex] = { | ||
iceGatherer: transports.iceGatherer, | ||
iceTransport: transports.iceTransport, | ||
dtlsTransport: transports.dtlsTransport, | ||
localCapabilities: localCapabilities, | ||
remoteCapabilities: remoteCapabilities, | ||
rtpSender: rtpSender, | ||
rtpReceiver: rtpReceiver, | ||
kind: kind, | ||
mid: mid, | ||
rtcpParameters: rtcpParameters, | ||
sendEncodingParameters: sendEncodingParameters, | ||
recvEncodingParameters: recvEncodingParameters | ||
}; | ||
// Start the RTCRtpReceiver now. The RTPSender is started in | ||
// setLocalDescription. | ||
self._transceive(self.transceivers[sdpMLineIndex], | ||
false, | ||
direction === 'sendrecv' || direction === 'sendonly'); | ||
} else if (description.type === 'answer' && !rejected) { | ||
if (usingBundle && sdpMLineIndex > 0) { | ||
self._disposeIceAndDtlsTransports(sdpMLineIndex); | ||
self.transceivers[sdpMLineIndex].iceGatherer = | ||
self.transceivers[0].iceGatherer; | ||
self.transceivers[sdpMLineIndex].iceTransport = | ||
self.transceivers[0].iceTransport; | ||
self.transceivers[sdpMLineIndex].dtlsTransport = | ||
self.transceivers[0].dtlsTransport; | ||
if (self.transceivers[sdpMLineIndex].rtpSender) { | ||
self.transceivers[sdpMLineIndex].rtpSender.setTransport( | ||
self.transceivers[0].dtlsTransport); | ||
} | ||
if (self.transceivers[sdpMLineIndex].rtpReceiver) { | ||
self.transceivers[sdpMLineIndex].rtpReceiver.setTransport( | ||
self.transceivers[0].dtlsTransport); | ||
} | ||
} | ||
transceiver = self.transceivers[sdpMLineIndex]; | ||
iceGatherer = transceiver.iceGatherer; | ||
iceTransport = transceiver.iceTransport; | ||
dtlsTransport = transceiver.dtlsTransport; | ||
rtpSender = transceiver.rtpSender; | ||
rtpReceiver = transceiver.rtpReceiver; | ||
sendEncodingParameters = transceiver.sendEncodingParameters; | ||
localCapabilities = transceiver.localCapabilities; | ||
self.transceivers[sdpMLineIndex].recvEncodingParameters = | ||
recvEncodingParameters; | ||
self.transceivers[sdpMLineIndex].remoteCapabilities = | ||
remoteCapabilities; | ||
self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; | ||
if ((isIceLite || isComplete) && cands.length) { | ||
iceTransport.setRemoteCandidates(cands); | ||
} | ||
if (!usingBundle || sdpMLineIndex === 0) { | ||
iceTransport.start(iceGatherer, remoteIceParameters, | ||
'controlling'); | ||
dtlsTransport.start(remoteDtlsParameters); | ||
} | ||
self._transceive(transceiver, | ||
direction === 'sendrecv' || direction === 'recvonly', | ||
direction === 'sendrecv' || direction === 'sendonly'); | ||
if (rtpReceiver && | ||
(direction === 'sendrecv' || direction === 'sendonly')) { | ||
track = rtpReceiver.track; | ||
receiverList.push([track, rtpReceiver]); | ||
if (remoteMsid) { | ||
if (!streams[remoteMsid.stream]) { | ||
streams[remoteMsid.stream] = new MediaStream(); | ||
} | ||
streams[remoteMsid.stream].addTrack(track); | ||
} else { | ||
if (!streams.default) { | ||
streams.default = new MediaStream(); | ||
} | ||
streams.default.addTrack(track); | ||
} | ||
} else { | ||
// FIXME: actually the receiver should be created later. | ||
delete transceiver.rtpReceiver; | ||
} | ||
} | ||
}); | ||
this.usingBundle = usingBundle; | ||
this.remoteDescription = { | ||
type: description.type, | ||
sdp: description.sdp | ||
}; | ||
switch (description.type) { | ||
case 'offer': | ||
this._updateSignalingState('have-remote-offer'); | ||
break; | ||
case 'answer': | ||
this._updateSignalingState('stable'); | ||
break; | ||
default: | ||
throw new TypeError('unsupported type "' + description.type + | ||
'"'); | ||
} | ||
Object.keys(streams).forEach(function(sid) { | ||
var stream = streams[sid]; | ||
if (stream.getTracks().length) { | ||
self.remoteStreams.push(stream); | ||
var event = new Event('addstream'); | ||
event.stream = stream; | ||
self.dispatchEvent(event); | ||
if (self.onaddstream !== null) { | ||
window.setTimeout(function() { | ||
self.onaddstream(event); | ||
}, 0); | ||
} | ||
receiverList.forEach(function(item) { | ||
var track = item[0]; | ||
var receiver = item[1]; | ||
if (stream.id !== item[2].id) { | ||
return; | ||
} | ||
var trackEvent = new Event('track'); | ||
trackEvent.track = track; | ||
trackEvent.receiver = receiver; | ||
trackEvent.streams = [stream]; | ||
self.dispatchEvent(trackEvent); | ||
if (self.ontrack !== null) { | ||
window.setTimeout(function() { | ||
self.ontrack(trackEvent); | ||
}, 0); | ||
} | ||
}); | ||
} | ||
}); | ||
if (arguments.length > 1 && typeof arguments[1] === 'function') { | ||
window.setTimeout(arguments[1], 0); | ||
} | ||
return Promise.resolve(); | ||
}; | ||
window.RTCPeerConnection.prototype.close = function() { | ||
this.transceivers.forEach(function(transceiver) { | ||
/* not yet | ||
if (transceiver.iceGatherer) { | ||
transceiver.iceGatherer.close(); | ||
} | ||
*/ | ||
if (transceiver.iceTransport) { | ||
transceiver.iceTransport.stop(); | ||
} | ||
if (transceiver.dtlsTransport) { | ||
transceiver.dtlsTransport.stop(); | ||
} | ||
if (transceiver.rtpSender) { | ||
transceiver.rtpSender.stop(); | ||
} | ||
if (transceiver.rtpReceiver) { | ||
transceiver.rtpReceiver.stop(); | ||
} | ||
}); | ||
// FIXME: clean up tracks, local streams, remote streams, etc | ||
this._updateSignalingState('closed'); | ||
}; | ||
// Update the signaling state. | ||
window.RTCPeerConnection.prototype._updateSignalingState = | ||
function(newState) { | ||
this.signalingState = newState; | ||
var event = new Event('signalingstatechange'); | ||
this.dispatchEvent(event); | ||
if (this.onsignalingstatechange !== null) { | ||
this.onsignalingstatechange(event); | ||
} | ||
}; | ||
// Determine whether to fire the negotiationneeded event. | ||
window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = | ||
function() { | ||
// Fire away (for now). | ||
var event = new Event('negotiationneeded'); | ||
this.dispatchEvent(event); | ||
if (this.onnegotiationneeded !== null) { | ||
this.onnegotiationneeded(event); | ||
} | ||
}; | ||
// Update the connection state. | ||
window.RTCPeerConnection.prototype._updateConnectionState = function() { | ||
var self = this; | ||
var newState; | ||
var states = { | ||
'new': 0, | ||
closed: 0, | ||
connecting: 0, | ||
checking: 0, | ||
connected: 0, | ||
completed: 0, | ||
failed: 0 | ||
}; | ||
this.transceivers.forEach(function(transceiver) { | ||
states[transceiver.iceTransport.state]++; | ||
states[transceiver.dtlsTransport.state]++; | ||
}); | ||
// ICETransport.completed and connected are the same for this purpose. | ||
states.connected += states.completed; | ||
newState = 'new'; | ||
if (states.failed > 0) { | ||
newState = 'failed'; | ||
} else if (states.connecting > 0 || states.checking > 0) { | ||
newState = 'connecting'; | ||
} else if (states.disconnected > 0) { | ||
newState = 'disconnected'; | ||
} else if (states.new > 0) { | ||
newState = 'new'; | ||
} else if (states.connected > 0 || states.completed > 0) { | ||
newState = 'connected'; | ||
} | ||
if (newState !== self.iceConnectionState) { | ||
self.iceConnectionState = newState; | ||
var event = new Event('iceconnectionstatechange'); | ||
this.dispatchEvent(event); | ||
if (this.oniceconnectionstatechange !== null) { | ||
this.oniceconnectionstatechange(event); | ||
} | ||
} | ||
}; | ||
window.RTCPeerConnection.prototype.createOffer = function() { | ||
var self = this; | ||
if (this._pendingOffer) { | ||
throw new Error('createOffer called while there is a pending offer.'); | ||
} | ||
var offerOptions; | ||
if (arguments.length === 1 && typeof arguments[0] !== 'function') { | ||
offerOptions = arguments[0]; | ||
} else if (arguments.length === 3) { | ||
offerOptions = arguments[2]; | ||
} | ||
var tracks = []; | ||
var numAudioTracks = 0; | ||
var numVideoTracks = 0; | ||
// Default to sendrecv. | ||
if (this.localStreams.length) { | ||
numAudioTracks = this.localStreams.reduce(function(numTracks, stream) { | ||
return numTracks + stream.getAudioTracks().length; | ||
}, 0); | ||
numVideoTracks = this.localStreams.reduce(function(numTracks, stream) { | ||
return numTracks + stream.getVideoTracks().length; | ||
}, 0); | ||
} | ||
// Determine number of audio and video tracks we need to send/recv. | ||
if (offerOptions) { | ||
// Reject Chrome legacy constraints. | ||
if (offerOptions.mandatory || offerOptions.optional) { | ||
throw new TypeError( | ||
'Legacy mandatory/optional constraints not supported.'); | ||
} | ||
if (offerOptions.offerToReceiveAudio !== undefined) { | ||
numAudioTracks = offerOptions.offerToReceiveAudio; | ||
} | ||
if (offerOptions.offerToReceiveVideo !== undefined) { | ||
numVideoTracks = offerOptions.offerToReceiveVideo; | ||
} | ||
} | ||
// Push local streams. | ||
this.localStreams.forEach(function(localStream) { | ||
localStream.getTracks().forEach(function(track) { | ||
tracks.push({ | ||
kind: track.kind, | ||
track: track, | ||
stream: localStream, | ||
wantReceive: track.kind === 'audio' ? | ||
numAudioTracks > 0 : numVideoTracks > 0 | ||
}); | ||
if (track.kind === 'audio') { | ||
numAudioTracks--; | ||
} else if (track.kind === 'video') { | ||
numVideoTracks--; | ||
} | ||
}); | ||
}); | ||
// Create M-lines for recvonly streams. | ||
while (numAudioTracks > 0 || numVideoTracks > 0) { | ||
if (numAudioTracks > 0) { | ||
tracks.push({ | ||
kind: 'audio', | ||
wantReceive: true | ||
}); | ||
numAudioTracks--; | ||
} | ||
if (numVideoTracks > 0) { | ||
tracks.push({ | ||
kind: 'video', | ||
wantReceive: true | ||
}); | ||
numVideoTracks--; | ||
} | ||
} | ||
// reorder tracks | ||
tracks = sortTracks(tracks); | ||
var sdp = SDPUtils.writeSessionBoilerplate(); | ||
var transceivers = []; | ||
tracks.forEach(function(mline, sdpMLineIndex) { | ||
// For each track, create an ice gatherer, ice transport, | ||
// dtls transport, potentially rtpsender and rtpreceiver. | ||
var track = mline.track; | ||
var kind = mline.kind; | ||
var mid = SDPUtils.generateIdentifier(); | ||
var transports = self.usingBundle && sdpMLineIndex > 0 ? { | ||
iceGatherer: transceivers[0].iceGatherer, | ||
iceTransport: transceivers[0].iceTransport, | ||
dtlsTransport: transceivers[0].dtlsTransport | ||
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex); | ||
var localCapabilities = RTCRtpSender.getCapabilities(kind); | ||
// filter RTX until additional stuff needed for RTX is implemented | ||
// in adapter.js | ||
if (browserDetails.version < 15019) { | ||
localCapabilities.codecs = localCapabilities.codecs.filter( | ||
function(codec) { | ||
return codec.name !== 'rtx'; | ||
}); | ||
} | ||
localCapabilities.codecs.forEach(function(codec) { | ||
// work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 | ||
// by adding level-asymmetry-allowed=1 | ||
if (codec.name === 'H264' && | ||
codec.parameters['level-asymmetry-allowed'] === undefined) { | ||
codec.parameters['level-asymmetry-allowed'] = '1'; | ||
} | ||
}); | ||
var rtpSender; | ||
var rtpReceiver; | ||
// generate an ssrc now, to be used later in rtpSender.send | ||
var sendEncodingParameters = [{ | ||
ssrc: (2 * sdpMLineIndex + 1) * 1001 | ||
}]; | ||
if (track) { | ||
// add RTX | ||
if (browserDetails.version >= 15019 && kind === 'video') { | ||
sendEncodingParameters[0].rtx = { | ||
ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1 | ||
}; | ||
} | ||
rtpSender = new RTCRtpSender(track, transports.dtlsTransport); | ||
} | ||
if (mline.wantReceive) { | ||
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); | ||
} | ||
transceivers[sdpMLineIndex] = { | ||
iceGatherer: transports.iceGatherer, | ||
iceTransport: transports.iceTransport, | ||
dtlsTransport: transports.dtlsTransport, | ||
localCapabilities: localCapabilities, | ||
remoteCapabilities: null, | ||
rtpSender: rtpSender, | ||
rtpReceiver: rtpReceiver, | ||
kind: kind, | ||
mid: mid, | ||
sendEncodingParameters: sendEncodingParameters, | ||
recvEncodingParameters: null | ||
}; | ||
}); | ||
// always offer BUNDLE and dispose on return if not supported. | ||
if (this._config.bundlePolicy !== 'max-compat') { | ||
sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) { | ||
return t.mid; | ||
}).join(' ') + '\r\n'; | ||
} | ||
sdp += 'a=ice-options:trickle\r\n'; | ||
tracks.forEach(function(mline, sdpMLineIndex) { | ||
var transceiver = transceivers[sdpMLineIndex]; | ||
sdp += SDPUtils.writeMediaSection(transceiver, | ||
transceiver.localCapabilities, 'offer', mline.stream); | ||
sdp += 'a=rtcp-rsize\r\n'; | ||
}); | ||
this._pendingOffer = transceivers; | ||
var desc = new RTCSessionDescription({ | ||
type: 'offer', | ||
sdp: sdp | ||
}); | ||
if (arguments.length && typeof arguments[0] === 'function') { | ||
window.setTimeout(arguments[0], 0, desc); | ||
} | ||
return Promise.resolve(desc); | ||
}; | ||
window.RTCPeerConnection.prototype.createAnswer = function() { | ||
var self = this; | ||
var sdp = SDPUtils.writeSessionBoilerplate(); | ||
if (this.usingBundle) { | ||
sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { | ||
return t.mid; | ||
}).join(' ') + '\r\n'; | ||
} | ||
this.transceivers.forEach(function(transceiver) { | ||
if (transceiver.isDatachannel) { | ||
sdp += 'm=application 0 DTLS/SCTP 5000\r\n' + | ||
'c=IN IP4 0.0.0.0\r\n' + | ||
'a=mid:' + transceiver.mid + '\r\n'; | ||
return; | ||
} | ||
// Calculate intersection of capabilities. | ||
var commonCapabilities = self._getCommonCapabilities( | ||
transceiver.localCapabilities, | ||
transceiver.remoteCapabilities); | ||
var hasRtx = commonCapabilities.codecs.filter(function(c) { | ||
return c.name.toLowerCase() === 'rtx'; | ||
}).length; | ||
if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { | ||
delete transceiver.sendEncodingParameters[0].rtx; | ||
} | ||
sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, | ||
'answer', self.localStreams[0]); | ||
if (transceiver.rtcpParameters && | ||
transceiver.rtcpParameters.reducedSize) { | ||
sdp += 'a=rtcp-rsize\r\n'; | ||
} | ||
}); | ||
var desc = new RTCSessionDescription({ | ||
type: 'answer', | ||
sdp: sdp | ||
}); | ||
if (arguments.length && typeof arguments[0] === 'function') { | ||
window.setTimeout(arguments[0], 0, desc); | ||
} | ||
return Promise.resolve(desc); | ||
}; | ||
window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { | ||
if (!candidate) { | ||
for (var j = 0; j < this.transceivers.length; j++) { | ||
this.transceivers[j].iceTransport.addRemoteCandidate({}); | ||
if (this.usingBundle) { | ||
return Promise.resolve(); | ||
} | ||
} | ||
} else { | ||
var mLineIndex = candidate.sdpMLineIndex; | ||
if (candidate.sdpMid) { | ||
for (var i = 0; i < this.transceivers.length; i++) { | ||
if (this.transceivers[i].mid === candidate.sdpMid) { | ||
mLineIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
var transceiver = this.transceivers[mLineIndex]; | ||
if (transceiver) { | ||
var cand = Object.keys(candidate.candidate).length > 0 ? | ||
SDPUtils.parseCandidate(candidate.candidate) : {}; | ||
// Ignore Chrome's invalid candidates since Edge does not like them. | ||
if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { | ||
return Promise.resolve(); | ||
} | ||
// Ignore RTCP candidates, we assume RTCP-MUX. | ||
if (cand.component !== '1') { | ||
return Promise.resolve(); | ||
} | ||
transceiver.iceTransport.addRemoteCandidate(cand); | ||
// update the remoteDescription. | ||
var sections = SDPUtils.splitSections(this.remoteDescription.sdp); | ||
sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim() | ||
: 'a=end-of-candidates') + '\r\n'; | ||
this.remoteDescription.sdp = sections.join(''); | ||
} | ||
} | ||
if (arguments.length > 1 && typeof arguments[1] === 'function') { | ||
window.setTimeout(arguments[1], 0); | ||
} | ||
return Promise.resolve(); | ||
}; | ||
window.RTCPeerConnection.prototype.getStats = function() { | ||
var promises = []; | ||
this.transceivers.forEach(function(transceiver) { | ||
['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', | ||
'dtlsTransport'].forEach(function(method) { | ||
if (transceiver[method]) { | ||
promises.push(transceiver[method].getStats()); | ||
} | ||
}); | ||
}); | ||
var cb = arguments.length > 1 && typeof arguments[1] === 'function' && | ||
arguments[1]; | ||
var fixStatsType = function(stat) { | ||
return { | ||
inboundrtp: 'inbound-rtp', | ||
outboundrtp: 'outbound-rtp', | ||
candidatepair: 'candidate-pair', | ||
localcandidate: 'local-candidate', | ||
remotecandidate: 'remote-candidate' | ||
}[stat.type] || stat.type; | ||
}; | ||
return new Promise(function(resolve) { | ||
// shim getStats with maplike support | ||
var results = new Map(); | ||
Promise.all(promises).then(function(res) { | ||
res.forEach(function(result) { | ||
Object.keys(result).forEach(function(id) { | ||
result[id].type = fixStatsType(result[id]); | ||
results.set(id, result[id]); | ||
}); | ||
}); | ||
if (cb) { | ||
window.setTimeout(cb, 0, results); | ||
} | ||
resolve(results); | ||
}); | ||
}); | ||
}; | ||
window.RTCPeerConnection = shimRTCPeerConnection(browserDetails.version); | ||
}, | ||
@@ -1326,8 +60,1 @@ shimReplaceTrack: function() { | ||
}; | ||
// Expose public methods. | ||
module.exports = { | ||
shimPeerConnection: edgeShim.shimPeerConnection, | ||
shimGetUserMedia: require('./getusermedia'), | ||
shimReplaceTrack: edgeShim.shimReplaceTrack | ||
}; |
@@ -164,2 +164,4 @@ /* | ||
utils.browserDetails.version = 14392; | ||
// need to re-evaluate after changing the browser version. | ||
shim.shimPeerConnection(); | ||
pc = new RTCPeerConnection({ | ||
@@ -166,0 +168,0 @@ iceServers: [{urls: 'stun:stun.l.google.com'}] |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
38
14344
541001