diff --git a/bigbluebutton-client/resources/prod/lib/adapter.js b/bigbluebutton-client/resources/prod/lib/adapter.js index ce68e660b5..ac7d7a5ace 100755 --- a/bigbluebutton-client/resources/prod/lib/adapter.js +++ b/bigbluebutton-client/resources/prod/lib/adapter.js @@ -1,4 +1,4 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0; i--) { - this._iceGatherers.push(new window.RTCIceGatherer({ + this._iceGatherers = new window.RTCIceGatherer({ iceServers: config.iceServers, gatherPolicy: config.iceTransportPolicy - })); + }); } } else { config.iceCandidatePoolSize = 0; @@ -345,48 +302,14 @@ module.exports = function(window, edgeVersion) { this._sdpSessionVersion = 0; this._dtlsRole = undefined; // role for a=setup to use in answers. - - this._isClosed = false; - }; - - Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { - configurable: true, - get: function() { - return this._localDescription; - } - }); - Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { - configurable: true, - get: function() { - return this._remoteDescription; - } - }); - - // set up event handlers on prototype - RTCPeerConnection.prototype.onicecandidate = null; - RTCPeerConnection.prototype.onaddstream = null; - RTCPeerConnection.prototype.ontrack = null; - RTCPeerConnection.prototype.onremovestream = null; - RTCPeerConnection.prototype.onsignalingstatechange = null; - RTCPeerConnection.prototype.oniceconnectionstatechange = null; - RTCPeerConnection.prototype.onconnectionstatechange = null; - RTCPeerConnection.prototype.onicegatheringstatechange = null; - RTCPeerConnection.prototype.onnegotiationneeded = null; - RTCPeerConnection.prototype.ondatachannel = null; - - RTCPeerConnection.prototype._dispatchEvent = function(name, event) { - if (this._isClosed) { - return; - } - this.dispatchEvent(event); - if (typeof this['on' + name] === 'function') { - this['on' + name](event); - } }; RTCPeerConnection.prototype._emitGatheringStateChange = function() { var event = new Event('icegatheringstatechange'); - this._dispatchEvent('icegatheringstatechange', event); + this.dispatchEvent(event); + if (typeof this.onicegatheringstatechange === 'function') { + this.onicegatheringstatechange(event); + } }; RTCPeerConnection.prototype.getConfiguration = function() { @@ -402,8 +325,8 @@ module.exports = function(window, edgeVersion) { }; // internal helper to create a transceiver object. - // (which is not yet the same as the WebRTC 1.0 transceiver) - RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) { + // (whih is not yet the same as the WebRTC 1.0 transceiver) + RTCPeerConnection.prototype._createTransceiver = function(kind) { var hasBundleTransport = this.transceivers.length > 0; var transceiver = { track: null, @@ -419,7 +342,6 @@ module.exports = function(window, edgeVersion) { sendEncodingParameters: null, recvEncodingParameters: null, stream: null, - associatedRemoteMediaStreams: [], wantReceive: true }; if (this.usingBundle && hasBundleTransport) { @@ -430,26 +352,11 @@ module.exports = function(window, edgeVersion) { transceiver.iceTransport = transports.iceTransport; transceiver.dtlsTransport = transports.dtlsTransport; } - if (!doNotAdd) { - this.transceivers.push(transceiver); - } + this.transceivers.push(transceiver); return transceiver; }; RTCPeerConnection.prototype.addTrack = function(track, stream) { - if (this._isClosed) { - throw makeError('InvalidStateError', - 'Attempted to call addTrack on a closed peerconnection.'); - } - - var alreadyExists = this.transceivers.find(function(s) { - return s.track === track; - }); - - if (alreadyExists) { - throw makeError('InvalidAccessError', 'Track already exists.'); - } - var transceiver; for (var i = 0; i < this.transceivers.length; i++) { if (!this.transceivers[i].track && @@ -475,10 +382,10 @@ module.exports = function(window, edgeVersion) { }; RTCPeerConnection.prototype.addStream = function(stream) { - var pc = this; + var self = this; if (edgeVersion >= 15025) { stream.getTracks().forEach(function(track) { - pc.addTrack(track, stream); + self.addTrack(track, stream); }); } else { // Clone is necessary for local demos mostly, attaching directly @@ -492,59 +399,17 @@ module.exports = function(window, edgeVersion) { }); }); clonedStream.getTracks().forEach(function(track) { - pc.addTrack(track, clonedStream); + self.addTrack(track, clonedStream); }); } }; - RTCPeerConnection.prototype.removeTrack = function(sender) { - if (this._isClosed) { - throw makeError('InvalidStateError', - 'Attempted to call removeTrack on a closed peerconnection.'); - } - - if (!(sender instanceof window.RTCRtpSender)) { - throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + - 'does not implement interface RTCRtpSender.'); - } - - var transceiver = this.transceivers.find(function(t) { - return t.rtpSender === sender; - }); - - if (!transceiver) { - throw makeError('InvalidAccessError', - 'Sender was not created by this connection.'); - } - var stream = transceiver.stream; - - transceiver.rtpSender.stop(); - transceiver.rtpSender = null; - transceiver.track = null; - transceiver.stream = null; - - // remove the stream from the set of local streams - var localStreams = this.transceivers.map(function(t) { - return t.stream; - }); - if (localStreams.indexOf(stream) === -1 && - this.localStreams.indexOf(stream) > -1) { - this.localStreams.splice(this.localStreams.indexOf(stream), 1); - } - - this._maybeFireNegotiationNeeded(); - }; - RTCPeerConnection.prototype.removeStream = function(stream) { - var pc = this; - stream.getTracks().forEach(function(track) { - var sender = pc.getSenders().find(function(s) { - return s.track === track; - }); - if (sender) { - pc.removeTrack(sender); - } - }); + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + this._maybeFireNegotiationNeeded(); + } }; RTCPeerConnection.prototype.getSenders = function() { @@ -568,7 +433,7 @@ module.exports = function(window, edgeVersion) { RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, usingBundle) { - var pc = this; + var self = this; if (usingBundle && sdpMLineIndex > 0) { return this.transceivers[0].iceGatherer; } else if (this._iceGatherers.length) { @@ -582,14 +447,14 @@ module.exports = function(window, edgeVersion) { {value: 'new', writable: true} ); - this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; + this.transceivers[sdpMLineIndex].candidates = []; this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { var end = !event.candidate || Object.keys(event.candidate).length === 0; // polyfill since RTCIceGatherer.state is not implemented in // Edge 10547 yet. iceGatherer.state = end ? 'completed' : 'gathering'; - if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { - pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); + if (self.transceivers[sdpMLineIndex].candidates !== null) { + self.transceivers[sdpMLineIndex].candidates.push(event.candidate); } }; iceGatherer.addEventListener('localcandidate', @@ -599,18 +464,17 @@ module.exports = function(window, edgeVersion) { // start gathering from an RTCIceGatherer. RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { - var pc = this; + var self = this; var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; if (iceGatherer.onlocalcandidate) { return; } - var bufferedCandidateEvents = - this.transceivers[sdpMLineIndex].bufferedCandidateEvents; - this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; + var candidates = this.transceivers[sdpMLineIndex].candidates; + this.transceivers[sdpMLineIndex].candidates = null; iceGatherer.removeEventListener('localcandidate', this.transceivers[sdpMLineIndex].bufferCandidates); iceGatherer.onlocalcandidate = function(evt) { - if (pc.usingBundle && sdpMLineIndex > 0) { + if (self.usingBundle && sdpMLineIndex > 0) { // if we know that we use bundle we can drop candidates with // Ñ•dpMLineIndex > 0. If we don't do this then our state gets // confused since we dispose the extra ice gatherer. @@ -634,61 +498,52 @@ module.exports = function(window, edgeVersion) { } // RTCIceCandidate doesn't have a component, needs to be added cand.component = 1; - // also the usernameFragment. TODO: update SDP to take both variants. - cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; - - var serializedCandidate = SDPUtils.writeCandidate(cand); - event.candidate = Object.assign(event.candidate, - SDPUtils.parseCandidate(serializedCandidate)); - - event.candidate.candidate = serializedCandidate; - event.candidate.toJSON = function() { - return { - candidate: event.candidate.candidate, - sdpMid: event.candidate.sdpMid, - sdpMLineIndex: event.candidate.sdpMLineIndex, - usernameFragment: event.candidate.usernameFragment - }; - }; + event.candidate.candidate = SDPUtils.writeCandidate(cand); } // update local description. - var sections = SDPUtils.getMediaSections(pc._localDescription.sdp); + var sections = SDPUtils.splitSections(self.localDescription.sdp); if (!end) { - sections[event.candidate.sdpMLineIndex] += + sections[event.candidate.sdpMLineIndex + 1] += 'a=' + event.candidate.candidate + '\r\n'; } else { - sections[event.candidate.sdpMLineIndex] += + sections[event.candidate.sdpMLineIndex + 1] += 'a=end-of-candidates\r\n'; } - pc._localDescription.sdp = - SDPUtils.getDescription(pc._localDescription.sdp) + - sections.join(''); - var complete = pc.transceivers.every(function(transceiver) { + self.localDescription.sdp = sections.join(''); + var complete = self.transceivers.every(function(transceiver) { return transceiver.iceGatherer && transceiver.iceGatherer.state === 'completed'; }); - if (pc.iceGatheringState !== 'gathering') { - pc.iceGatheringState = 'gathering'; - pc._emitGatheringStateChange(); + if (self.iceGatheringState !== 'gathering') { + self.iceGatheringState = 'gathering'; + self._emitGatheringStateChange(); } // Emit candidate. Also emit null candidate when all gatherers are // complete. if (!end) { - pc._dispatchEvent('icecandidate', event); + self.dispatchEvent(event); + if (typeof self.onicecandidate === 'function') { + self.onicecandidate(event); + } } if (complete) { - pc._dispatchEvent('icecandidate', new Event('icecandidate')); - pc.iceGatheringState = 'complete'; - pc._emitGatheringStateChange(); + self.dispatchEvent(new Event('icecandidate')); + if (typeof self.onicecandidate === 'function') { + self.onicecandidate(new Event('icecandidate')); + } + self.iceGatheringState = 'complete'; + self._emitGatheringStateChange(); } }; // emit already gathered candidates. window.setTimeout(function() { - bufferedCandidateEvents.forEach(function(e) { + candidates.forEach(function(candidate) { + var e = new Event('RTCIceGatherEvent'); + e.candidate = candidate; iceGatherer.onlocalcandidate(e); }); }, 0); @@ -696,22 +551,21 @@ module.exports = function(window, edgeVersion) { // Create ICE transport and DTLS transport. RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { - var pc = this; + var self = this; var iceTransport = new window.RTCIceTransport(null); iceTransport.onicestatechange = function() { - pc._updateIceConnectionState(); - pc._updateConnectionState(); + self._updateConnectionState(); }; var dtlsTransport = new window.RTCDtlsTransport(iceTransport); dtlsTransport.ondtlsstatechange = function() { - pc._updateConnectionState(); + self._updateConnectionState(); }; dtlsTransport.onerror = function() { // onerror does not set state to failed by itself. Object.defineProperty(dtlsTransport, 'state', {value: 'failed', writable: true}); - pc._updateConnectionState(); + self._updateConnectionState(); }; return { @@ -767,17 +621,11 @@ module.exports = function(window, edgeVersion) { delete p.rtx; }); } - if (transceiver.recvEncodingParameters.length) { - params.encodings = transceiver.recvEncodingParameters; - } else { - params.encodings = [{}]; - } + params.encodings = transceiver.recvEncodingParameters; params.rtcp = { + cname: transceiver.rtcpParameters.cname, compound: transceiver.rtcpParameters.compound }; - if (transceiver.rtcpParameters.cname) { - params.rtcp.cname = transceiver.rtcpParameters.cname; - } if (transceiver.sendEncodingParameters.length) { params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; } @@ -786,19 +634,20 @@ module.exports = function(window, edgeVersion) { }; RTCPeerConnection.prototype.setLocalDescription = function(description) { - var pc = this; - - // Note: pranswer is not supported. - if (['offer', 'answer'].indexOf(description.type) === -1) { - return Promise.reject(makeError('TypeError', - 'Unsupported type "' + description.type + '"')); - } + var self = this; + var args = arguments; if (!isActionAllowedInSignalingState('setLocalDescription', - description.type, pc.signalingState) || pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not set local ' + description.type + - ' in state ' + pc.signalingState)); + description.type, this.signalingState)) { + return new Promise(function(resolve, reject) { + var e = new Error('Can not set local ' + description.type + + ' in state ' + self.signalingState); + e.name = 'InvalidStateError'; + if (args.length > 2 && typeof args[2] === 'function') { + args[2].apply(null, [e]); + } + reject(e); + }); } var sections; @@ -810,19 +659,19 @@ module.exports = function(window, edgeVersion) { sessionpart = sections.shift(); sections.forEach(function(mediaSection, sdpMLineIndex) { var caps = SDPUtils.parseRtpParameters(mediaSection); - pc.transceivers[sdpMLineIndex].localCapabilities = caps; + self.transceivers[sdpMLineIndex].localCapabilities = caps; }); - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { - pc._gather(transceiver.mid, sdpMLineIndex); + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + self._gather(transceiver.mid, sdpMLineIndex); }); } else if (description.type === 'answer') { - sections = SDPUtils.splitSections(pc._remoteDescription.sdp); + 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 = pc.transceivers[sdpMLineIndex]; + var transceiver = self.transceivers[sdpMLineIndex]; var iceGatherer = transceiver.iceGatherer; var iceTransport = transceiver.iceTransport; var dtlsTransport = transceiver.dtlsTransport; @@ -831,9 +680,9 @@ module.exports = function(window, edgeVersion) { // treat bundle-only as not-rejected. var rejected = SDPUtils.isRejected(mediaSection) && - SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + !SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 1; - if (!rejected && !transceiver.rejected) { + if (!rejected && !transceiver.isDatachannel) { var remoteIceParameters = SDPUtils.getIceParameters( mediaSection, sessionpart); var remoteDtlsParameters = SDPUtils.getDtlsParameters( @@ -842,8 +691,8 @@ module.exports = function(window, edgeVersion) { remoteDtlsParameters.role = 'server'; } - if (!pc.usingBundle || sdpMLineIndex === 0) { - pc._gather(transceiver.mid, sdpMLineIndex); + if (!self.usingBundle || sdpMLineIndex === 0) { + self._gather(transceiver.mid, sdpMLineIndex); if (iceTransport.state === 'new') { iceTransport.start(iceGatherer, remoteIceParameters, isIceLite ? 'controlling' : 'controlled'); @@ -859,44 +708,61 @@ module.exports = function(window, edgeVersion) { // Start the RTCRtpSender. The RTCRtpReceiver for this // transceiver has already been started in setRemoteDescription. - pc._transceive(transceiver, + self._transceive(transceiver, params.codecs.length > 0, false); } }); } - pc._localDescription = { + this.localDescription = { type: description.type, sdp: description.sdp }; - if (description.type === 'offer') { - pc._updateSignalingState('have-local-offer'); - } else { - pc._updateSignalingState('stable'); + 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 + + '"'); } - return Promise.resolve(); + // If a success callback was provided, emit ICE candidates after it + // has been executed. Otherwise, emit callback after the Promise is + // resolved. + var cb = arguments.length > 1 && typeof arguments[1] === 'function' && + arguments[1]; + return new Promise(function(resolve) { + if (cb) { + cb.apply(null); + } + resolve(); + }); }; RTCPeerConnection.prototype.setRemoteDescription = function(description) { - var pc = this; - - // Note: pranswer is not supported. - if (['offer', 'answer'].indexOf(description.type) === -1) { - return Promise.reject(makeError('TypeError', - 'Unsupported type "' + description.type + '"')); - } + var self = this; + var args = arguments; if (!isActionAllowedInSignalingState('setRemoteDescription', - description.type, pc.signalingState) || pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not set remote ' + description.type + - ' in state ' + pc.signalingState)); + description.type, this.signalingState)) { + return new Promise(function(resolve, reject) { + var e = new Error('Can not set remote ' + description.type + + ' in state ' + self.signalingState); + e.name = 'InvalidStateError'; + if (args.length > 2 && typeof args[2] === 'function') { + args[2].apply(null, [e]); + } + reject(e); + }); } var streams = {}; - pc.remoteStreams.forEach(function(stream) { + this.remoteStreams.forEach(function(stream) { streams[stream.id] = stream; }); var receiverList = []; @@ -906,14 +772,14 @@ module.exports = function(window, edgeVersion) { 'a=ice-lite').length > 0; var usingBundle = SDPUtils.matchPrefix(sessionpart, 'a=group:BUNDLE ').length > 0; - pc.usingBundle = usingBundle; + this.usingBundle = usingBundle; var iceOptions = SDPUtils.matchPrefix(sessionpart, 'a=ice-options:')[0]; if (iceOptions) { - pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ') + this.canTrickleIceCandidates = iceOptions.substr(14).split(' ') .indexOf('trickle') >= 0; } else { - pc.canTrickleIceCandidates = false; + this.canTrickleIceCandidates = false; } sections.forEach(function(mediaSection, sdpMLineIndex) { @@ -921,7 +787,7 @@ module.exports = function(window, edgeVersion) { var kind = SDPUtils.getKind(mediaSection); // treat bundle-only as not-rejected. var rejected = SDPUtils.isRejected(mediaSection) && - SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + !SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 1; var protocol = lines[0].substr(2).split(' ')[2]; var direction = SDPUtils.getDirection(mediaSection, sessionpart); @@ -930,25 +796,14 @@ module.exports = function(window, edgeVersion) { var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); // Reject datachannels which are not implemented yet. - if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' || - protocol === 'UDP/DTLS/SCTP'))) { - // TODO: this is dangerous in the case where a non-rejected m-line - // becomes rejected. - pc.transceivers[sdpMLineIndex] = { + if (kind === 'application' && protocol === 'DTLS/SCTP') { + self.transceivers[sdpMLineIndex] = { mid: mid, - kind: kind, - protocol: protocol, - rejected: true + isDatachannel: true }; return; } - if (!rejected && pc.transceivers[sdpMLineIndex] && - pc.transceivers[sdpMLineIndex].rejected) { - // recycle a rejected transceiver. - pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); - } - var transceiver; var iceGatherer; var iceTransport; @@ -988,30 +843,30 @@ module.exports = function(window, edgeVersion) { // Check if we can use BUNDLE and dispose transports. if ((description.type === 'offer' || description.type === 'answer') && !rejected && usingBundle && sdpMLineIndex > 0 && - pc.transceivers[sdpMLineIndex]) { - pc._disposeIceAndDtlsTransports(sdpMLineIndex); - pc.transceivers[sdpMLineIndex].iceGatherer = - pc.transceivers[0].iceGatherer; - pc.transceivers[sdpMLineIndex].iceTransport = - pc.transceivers[0].iceTransport; - pc.transceivers[sdpMLineIndex].dtlsTransport = - pc.transceivers[0].dtlsTransport; - if (pc.transceivers[sdpMLineIndex].rtpSender) { - pc.transceivers[sdpMLineIndex].rtpSender.setTransport( - pc.transceivers[0].dtlsTransport); + self.transceivers[sdpMLineIndex]) { + 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 (pc.transceivers[sdpMLineIndex].rtpReceiver) { - pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( - pc.transceivers[0].dtlsTransport); + if (self.transceivers[sdpMLineIndex].rtpReceiver) { + self.transceivers[sdpMLineIndex].rtpReceiver.setTransport( + self.transceivers[0].dtlsTransport); } } if (description.type === 'offer' && !rejected) { - transceiver = pc.transceivers[sdpMLineIndex] || - pc._createTransceiver(kind); + transceiver = self.transceivers[sdpMLineIndex] || + self._createTransceiver(kind); transceiver.mid = mid; if (!transceiver.iceGatherer) { - transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + transceiver.iceGatherer = self._createIceGatherer(sdpMLineIndex, usingBundle); } @@ -1040,7 +895,6 @@ module.exports = function(window, edgeVersion) { ssrc: (2 * sdpMLineIndex + 2) * 1001 }]; - // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams var isNewTrack = false; if (direction === 'sendrecv' || direction === 'sendonly') { isNewTrack = !transceiver.rtpReceiver; @@ -1051,9 +905,7 @@ module.exports = function(window, edgeVersion) { var stream; track = rtpReceiver.track; // FIXME: does not work with Plan B. - if (remoteMsid && remoteMsid.stream === '-') { - // no-op. a stream id of '-' means: no associated stream. - } else if (remoteMsid) { + if (remoteMsid) { if (!streams[remoteMsid.stream]) { streams[remoteMsid.stream] = new window.MediaStream(); Object.defineProperty(streams[remoteMsid.stream], 'id', { @@ -1074,22 +926,9 @@ module.exports = function(window, edgeVersion) { } stream = streams.default; } - if (stream) { - addTrackToStreamAndFireEvent(track, stream); - transceiver.associatedRemoteMediaStreams.push(stream); - } + stream.addTrack(track); receiverList.push([track, rtpReceiver, stream]); } - } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { - transceiver.associatedRemoteMediaStreams.forEach(function(s) { - var nativeTrack = s.getTracks().find(function(t) { - return t.id === transceiver.rtpReceiver.track.id; - }); - if (nativeTrack) { - removeTrackFromStreamAndFireEvent(nativeTrack, s); - } - }); - transceiver.associatedRemoteMediaStreams = []; } transceiver.localCapabilities = localCapabilities; @@ -1101,11 +940,11 @@ module.exports = function(window, edgeVersion) { // Start the RTCRtpReceiver now. The RTPSender is started in // setLocalDescription. - pc._transceive(pc.transceivers[sdpMLineIndex], + self._transceive(self.transceivers[sdpMLineIndex], false, isNewTrack); } else if (description.type === 'answer' && !rejected) { - transceiver = pc.transceivers[sdpMLineIndex]; + transceiver = self.transceivers[sdpMLineIndex]; iceGatherer = transceiver.iceGatherer; iceTransport = transceiver.iceTransport; dtlsTransport = transceiver.dtlsTransport; @@ -1113,11 +952,11 @@ module.exports = function(window, edgeVersion) { sendEncodingParameters = transceiver.sendEncodingParameters; localCapabilities = transceiver.localCapabilities; - pc.transceivers[sdpMLineIndex].recvEncodingParameters = + self.transceivers[sdpMLineIndex].recvEncodingParameters = recvEncodingParameters; - pc.transceivers[sdpMLineIndex].remoteCapabilities = + self.transceivers[sdpMLineIndex].remoteCapabilities = remoteCapabilities; - pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; + self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; if (cands.length && iceTransport.state === 'new') { if ((isIceLite || isComplete) && @@ -1140,24 +979,10 @@ module.exports = function(window, edgeVersion) { } } - // If the offer contained RTX but the answer did not, - // remove RTX from sendEncodingParameters. - var commonCapabilities = getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); - - var hasRtx = commonCapabilities.codecs.filter(function(c) { - return c.name.toLowerCase() === 'rtx'; - }).length; - if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { - delete transceiver.sendEncodingParameters[0].rtx; - } - - pc._transceive(transceiver, + self._transceive(transceiver, direction === 'sendrecv' || direction === 'recvonly', direction === 'sendrecv' || direction === 'sendonly'); - // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams if (rtpReceiver && (direction === 'sendrecv' || direction === 'sendonly')) { track = rtpReceiver.track; @@ -1165,13 +990,13 @@ module.exports = function(window, edgeVersion) { if (!streams[remoteMsid.stream]) { streams[remoteMsid.stream] = new window.MediaStream(); } - addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); + streams[remoteMsid.stream].addTrack(track); receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); } else { if (!streams.default) { streams.default = new window.MediaStream(); } - addTrackToStreamAndFireEvent(track, streams.default); + streams.default.addTrack(track); receiverList.push([track, rtpReceiver, streams.default]); } } else { @@ -1181,28 +1006,37 @@ module.exports = function(window, edgeVersion) { } }); - if (pc._dtlsRole === undefined) { - pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; + if (this._dtlsRole === undefined) { + this._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; } - pc._remoteDescription = { + this.remoteDescription = { type: description.type, sdp: description.sdp }; - if (description.type === 'offer') { - pc._updateSignalingState('have-remote-offer'); - } else { - pc._updateSignalingState('stable'); + 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) { - if (pc.remoteStreams.indexOf(stream) === -1) { - pc.remoteStreams.push(stream); + if (self.remoteStreams.indexOf(stream) === -1) { + self.remoteStreams.push(stream); var event = new Event('addstream'); event.stream = stream; window.setTimeout(function() { - pc._dispatchEvent('addstream', event); + self.dispatchEvent(event); + if (typeof self.onaddstream === 'function') { + self.onaddstream(event); + } }); } @@ -1212,24 +1046,28 @@ module.exports = function(window, edgeVersion) { if (stream.id !== item[2].id) { return; } - fireAddTrack(pc, track, receiver, [stream]); + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.transceiver = {receiver: receiver}; + trackEvent.streams = [stream]; + window.setTimeout(function() { + self.dispatchEvent(trackEvent); + if (typeof self.ontrack === 'function') { + self.ontrack(trackEvent); + } + }); }); } }); - receiverList.forEach(function(item) { - if (item[2]) { - return; - } - fireAddTrack(pc, item[0], item[1], []); - }); // check whether addIceCandidate({}) was called within four seconds after // setRemoteDescription. window.setTimeout(function() { - if (!(pc && pc.transceivers)) { + if (!(self && self.transceivers)) { return; } - pc.transceivers.forEach(function(transceiver) { + self.transceivers.forEach(function(transceiver) { if (transceiver.iceTransport && transceiver.iceTransport.state === 'new' && transceiver.iceTransport.getRemoteCandidates().length > 0) { @@ -1240,7 +1078,12 @@ module.exports = function(window, edgeVersion) { }); }, 4000); - return Promise.resolve(); + return new Promise(function(resolve) { + if (args.length > 1 && typeof args[1] === 'function') { + args[1].apply(null); + } + resolve(); + }); }; RTCPeerConnection.prototype.close = function() { @@ -1264,7 +1107,6 @@ module.exports = function(window, edgeVersion) { } }); // FIXME: clean up tracks, local streams, remote streams, etc - this._isClosed = true; this._updateSignalingState('closed'); }; @@ -1272,63 +1114,32 @@ module.exports = function(window, edgeVersion) { RTCPeerConnection.prototype._updateSignalingState = function(newState) { this.signalingState = newState; var event = new Event('signalingstatechange'); - this._dispatchEvent('signalingstatechange', event); + this.dispatchEvent(event); + if (typeof this.onsignalingstatechange === 'function') { + this.onsignalingstatechange(event); + } }; // Determine whether to fire the negotiationneeded event. RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { - var pc = this; + var self = this; if (this.signalingState !== 'stable' || this.needNegotiation === true) { return; } this.needNegotiation = true; window.setTimeout(function() { - if (pc.needNegotiation) { - pc.needNegotiation = false; - var event = new Event('negotiationneeded'); - pc._dispatchEvent('negotiationneeded', event); + if (self.needNegotiation === false) { + return; + } + self.needNegotiation = false; + var event = new Event('negotiationneeded'); + self.dispatchEvent(event); + if (typeof self.onnegotiationneeded === 'function') { + self.onnegotiationneeded(event); } }, 0); }; - // Update the ice connection state. - RTCPeerConnection.prototype._updateIceConnectionState = function() { - var newState; - var states = { - 'new': 0, - closed: 0, - checking: 0, - connected: 0, - completed: 0, - disconnected: 0, - failed: 0 - }; - this.transceivers.forEach(function(transceiver) { - states[transceiver.iceTransport.state]++; - }); - - newState = 'new'; - if (states.failed > 0) { - newState = 'failed'; - } else if (states.checking > 0) { - newState = 'checking'; - } else if (states.disconnected > 0) { - newState = 'disconnected'; - } else if (states.new > 0) { - newState = 'new'; - } else if (states.connected > 0) { - newState = 'connected'; - } else if (states.completed > 0) { - newState = 'completed'; - } - - if (newState !== this.iceConnectionState) { - this.iceConnectionState = newState; - var event = new Event('iceconnectionstatechange'); - this._dispatchEvent('iceconnectionstatechange', event); - } - }; - // Update the connection state. RTCPeerConnection.prototype._updateConnectionState = function() { var newState; @@ -1336,6 +1147,7 @@ module.exports = function(window, edgeVersion) { 'new': 0, closed: 0, connecting: 0, + checking: 0, connected: 0, completed: 0, disconnected: 0, @@ -1351,40 +1163,45 @@ module.exports = function(window, edgeVersion) { newState = 'new'; if (states.failed > 0) { newState = 'failed'; - } else if (states.connecting > 0) { + } 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) { + } else if (states.connected > 0 || states.completed > 0) { newState = 'connected'; } - if (newState !== this.connectionState) { - this.connectionState = newState; - var event = new Event('connectionstatechange'); - this._dispatchEvent('connectionstatechange', event); + if (newState !== this.iceConnectionState) { + this.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this.dispatchEvent(event); + if (typeof this.oniceconnectionstatechange === 'function') { + this.oniceconnectionstatechange(event); + } } }; RTCPeerConnection.prototype.createOffer = function() { - var pc = this; + var self = this; + var args = arguments; - if (pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not call createOffer after close')); + var offerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + offerOptions = arguments[0]; + } else if (arguments.length === 3) { + offerOptions = arguments[2]; } - var numAudioTracks = pc.transceivers.filter(function(t) { + var numAudioTracks = this.transceivers.filter(function(t) { return t.kind === 'audio'; }).length; - var numVideoTracks = pc.transceivers.filter(function(t) { + var numVideoTracks = this.transceivers.filter(function(t) { return t.kind === 'video'; }).length; // Determine number of audio and video tracks we need to send/recv. - var offerOptions = arguments[0]; if (offerOptions) { // Reject Chrome legacy constraints. if (offerOptions.mandatory || offerOptions.optional) { @@ -1411,7 +1228,7 @@ module.exports = function(window, edgeVersion) { } } - pc.transceivers.forEach(function(transceiver) { + this.transceivers.forEach(function(transceiver) { if (transceiver.kind === 'audio') { numAudioTracks--; if (numAudioTracks < 0) { @@ -1428,28 +1245,28 @@ module.exports = function(window, edgeVersion) { // Create M-lines for recvonly streams. while (numAudioTracks > 0 || numVideoTracks > 0) { if (numAudioTracks > 0) { - pc._createTransceiver('audio'); + this._createTransceiver('audio'); numAudioTracks--; } if (numVideoTracks > 0) { - pc._createTransceiver('video'); + this._createTransceiver('video'); numVideoTracks--; } } - var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, - pc._sdpSessionVersion++); - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { // For each track, create an ice gatherer, ice transport, // dtls transport, potentially rtpsender and rtpreceiver. var track = transceiver.track; var kind = transceiver.kind; - var mid = transceiver.mid || SDPUtils.generateIdentifier(); + var mid = SDPUtils.generateIdentifier(); transceiver.mid = mid; if (!transceiver.iceGatherer) { - transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, - pc.usingBundle); + transceiver.iceGatherer = self._createIceGatherer(sdpMLineIndex, + self.usingBundle); } var localCapabilities = window.RTCRtpSender.getCapabilities(kind); @@ -1468,27 +1285,6 @@ module.exports = function(window, edgeVersion) { codec.parameters['level-asymmetry-allowed'] === undefined) { codec.parameters['level-asymmetry-allowed'] = '1'; } - - // for subsequent offers, we might have to re-use the payload - // type of the last offer. - if (transceiver.remoteCapabilities && - transceiver.remoteCapabilities.codecs) { - transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) { - if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && - codec.clockRate === remoteCodec.clockRate) { - codec.preferredPayloadType = remoteCodec.payloadType; - } - }); - } - }); - localCapabilities.headerExtensions.forEach(function(hdrExt) { - var remoteExtensions = transceiver.remoteCapabilities && - transceiver.remoteCapabilities.headerExtensions || []; - remoteExtensions.forEach(function(rHdrExt) { - if (hdrExt.uri === rHdrExt.uri) { - hdrExt.id = rHdrExt.id; - } - }); }); // generate an ssrc now, to be used later in rtpSender.send @@ -1515,20 +1311,20 @@ module.exports = function(window, edgeVersion) { }); // always offer BUNDLE and dispose on return if not supported. - if (pc._config.bundlePolicy !== 'max-compat') { - sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { + if (this._config.bundlePolicy !== 'max-compat') { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { return t.mid; }).join(' ') + '\r\n'; } sdp += 'a=ice-options:trickle\r\n'; - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { sdp += writeMediaSection(transceiver, transceiver.localCapabilities, - 'offer', transceiver.stream, pc._dtlsRole); + 'offer', transceiver.stream, self._dtlsRole); sdp += 'a=rtcp-rsize\r\n'; - if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && - (sdpMLineIndex === 0 || !pc.usingBundle)) { + if (transceiver.iceGatherer && self.iceGatheringState !== 'new' && + (sdpMLineIndex === 0 || !self.usingBundle)) { transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { cand.component = 1; sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; @@ -1544,55 +1340,36 @@ module.exports = function(window, edgeVersion) { type: 'offer', sdp: sdp }); - return Promise.resolve(desc); + return new Promise(function(resolve) { + if (args.length > 0 && typeof args[0] === 'function') { + args[0].apply(null, [desc]); + resolve(); + return; + } + resolve(desc); + }); }; RTCPeerConnection.prototype.createAnswer = function() { - var pc = this; + var self = this; + var args = arguments; - if (pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not call createAnswer after close')); - } - - if (!(pc.signalingState === 'have-remote-offer' || - pc.signalingState === 'have-local-pranswer')) { - return Promise.reject(makeError('InvalidStateError', - 'Can not call createAnswer in signalingState ' + pc.signalingState)); - } - - var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, - pc._sdpSessionVersion++); - if (pc.usingBundle) { - sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { return t.mid; }).join(' ') + '\r\n'; } - sdp += 'a=ice-options:trickle\r\n'; - - var mediaSectionsInOffer = SDPUtils.getMediaSections( - pc._remoteDescription.sdp).length; - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + var mediaSectionsInOffer = SDPUtils.splitSections( + this.remoteDescription.sdp).length - 1; + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { if (sdpMLineIndex + 1 > mediaSectionsInOffer) { return; } - if (transceiver.rejected) { - if (transceiver.kind === 'application') { - if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt - sdp += 'm=application 0 DTLS/SCTP 5000\r\n'; - } else { - sdp += 'm=application 0 ' + transceiver.protocol + - ' webrtc-datachannel\r\n'; - } - } else if (transceiver.kind === 'audio') { - sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + - 'a=rtpmap:0 PCMU/8000\r\n'; - } else if (transceiver.kind === 'video') { - sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + - 'a=rtpmap:120 VP8/90000\r\n'; - } - sdp += 'c=IN IP4 0.0.0.0\r\n' + - 'a=inactive\r\n' + + 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; } @@ -1629,7 +1406,7 @@ module.exports = function(window, edgeVersion) { } sdp += writeMediaSection(transceiver, commonCapabilities, - 'answer', transceiver.stream, pc._dtlsRole); + 'answer', transceiver.stream, self._dtlsRole); if (transceiver.rtcpParameters && transceiver.rtcpParameters.reducedSize) { sdp += 'a=rtcp-rsize\r\n'; @@ -1640,111 +1417,107 @@ module.exports = function(window, edgeVersion) { type: 'answer', sdp: sdp }); - return Promise.resolve(desc); + return new Promise(function(resolve) { + if (args.length > 0 && typeof args[0] === 'function') { + args[0].apply(null, [desc]); + resolve(); + return; + } + resolve(desc); + }); }; RTCPeerConnection.prototype.addIceCandidate = function(candidate) { - var pc = this; + var err; var sections; - if (candidate && !(candidate.sdpMLineIndex !== undefined || - candidate.sdpMid)) { - return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); - } - - // TODO: needs to go into ops queue. - return new Promise(function(resolve, reject) { - if (!pc._remoteDescription) { - return reject(makeError('InvalidStateError', - 'Can not add ICE candidate without a remote description')); - } else if (!candidate || candidate.candidate === '') { - for (var j = 0; j < pc.transceivers.length; j++) { - if (pc.transceivers[j].rejected) { - continue; - } - pc.transceivers[j].iceTransport.addRemoteCandidate({}); - sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); - sections[j] += 'a=end-of-candidates\r\n'; - pc._remoteDescription.sdp = - SDPUtils.getDescription(pc._remoteDescription.sdp) + - sections.join(''); - if (pc.usingBundle) { + if (!candidate || candidate.candidate === '') { + for (var j = 0; j < this.transceivers.length; j++) { + if (this.transceivers[j].isDatachannel) { + continue; + } + this.transceivers[j].iceTransport.addRemoteCandidate({}); + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[j + 1] += 'a=end-of-candidates\r\n'; + this.remoteDescription.sdp = sections.join(''); + if (this.usingBundle) { + break; + } + } + } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) { + throw new TypeError('sdpMLineIndex or sdpMid required'); + } else if (!this.remoteDescription) { + err = new Error('Can not add ICE candidate without ' + + 'a remote description'); + err.name = 'InvalidStateError'; + } else { + var sdpMLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + sdpMLineIndex = i; break; } } - } else { - var sdpMLineIndex = candidate.sdpMLineIndex; - if (candidate.sdpMid) { - for (var i = 0; i < pc.transceivers.length; i++) { - if (pc.transceivers[i].mid === candidate.sdpMid) { - sdpMLineIndex = i; - break; - } + } + var transceiver = this.transceivers[sdpMLineIndex]; + if (transceiver) { + if (transceiver.isDatachannel) { + return Promise.resolve(); + } + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return Promise.resolve(); + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component && cand.component !== 1) { + return Promise.resolve(); + } + // when using bundle, avoid adding candidates to the wrong + // ice transport. And avoid adding candidates added in the SDP. + if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && + transceiver.iceTransport !== this.transceivers[0].iceTransport)) { + if (!maybeAddCandidate(transceiver.iceTransport, cand)) { + err = new Error('Can not add ICE candidate'); + err.name = 'OperationError'; } } - var transceiver = pc.transceivers[sdpMLineIndex]; - if (transceiver) { - if (transceiver.rejected) { - return resolve(); - } - var cand = Object.keys(candidate.candidate).length > 0 ? - SDPUtils.parseCandidate(candidate.candidate) : {}; - // Ignore Chrome's invalid candidates since Edge does not like them. - if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { - return resolve(); - } - // Ignore RTCP candidates, we assume RTCP-MUX. - if (cand.component && cand.component !== 1) { - return resolve(); - } - // when using bundle, avoid adding candidates to the wrong - // ice transport. And avoid adding candidates added in the SDP. - if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && - transceiver.iceTransport !== pc.transceivers[0].iceTransport)) { - if (!maybeAddCandidate(transceiver.iceTransport, cand)) { - return reject(makeError('OperationError', - 'Can not add ICE candidate')); - } - } + if (!err) { // update the remoteDescription. var candidateString = candidate.candidate.trim(); if (candidateString.indexOf('a=') === 0) { candidateString = candidateString.substr(2); } - sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); - sections[sdpMLineIndex] += 'a=' + + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[sdpMLineIndex + 1] += 'a=' + (cand.type ? candidateString : 'end-of-candidates') + '\r\n'; - pc._remoteDescription.sdp = - SDPUtils.getDescription(pc._remoteDescription.sdp) + - sections.join(''); - } else { - return reject(makeError('OperationError', - 'Can not add ICE candidate')); + this.remoteDescription.sdp = sections.join(''); } + } else { + err = new Error('Can not add ICE candidate'); + err.name = 'OperationError'; + } + } + var args = arguments; + return new Promise(function(resolve, reject) { + if (err) { + if (args.length > 2 && typeof args[2] === 'function') { + args[2].apply(null, [err]); + } + reject(err); + } else { + if (args.length > 1 && typeof args[1] === 'function') { + args[1].apply(null); + } + resolve(); } - resolve(); }); }; - RTCPeerConnection.prototype.getStats = function(selector) { - if (selector && selector instanceof window.MediaStreamTrack) { - var senderOrReceiver = null; - this.transceivers.forEach(function(transceiver) { - if (transceiver.rtpSender && - transceiver.rtpSender.track === selector) { - senderOrReceiver = transceiver.rtpSender; - } else if (transceiver.rtpReceiver && - transceiver.rtpReceiver.track === selector) { - senderOrReceiver = transceiver.rtpReceiver; - } - }); - if (!senderOrReceiver) { - throw makeError('InvalidAccessError', 'Invalid selector.'); - } - return senderOrReceiver.getStats(); - } - + RTCPeerConnection.prototype.getStats = function() { var promises = []; this.transceivers.forEach(function(transceiver) { ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', @@ -1754,101 +1527,34 @@ module.exports = function(window, edgeVersion) { } }); }); - return Promise.all(promises).then(function(allStats) { + 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(); - allStats.forEach(function(stats) { - stats.forEach(function(stat) { - results.set(stat.id, stat); + 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) { + cb.apply(null, results); + } + resolve(results); }); - return results; }); }; - - // fix low-level stat names and return Map instead of object. - var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', - 'RTCIceTransport', 'RTCDtlsTransport']; - ortcObjects.forEach(function(ortcObjectName) { - var obj = window[ortcObjectName]; - if (obj && obj.prototype && obj.prototype.getStats) { - var nativeGetstats = obj.prototype.getStats; - obj.prototype.getStats = function() { - return nativeGetstats.apply(this) - .then(function(nativeStats) { - var mapStats = new Map(); - Object.keys(nativeStats).forEach(function(id) { - nativeStats[id].type = fixStatsType(nativeStats[id]); - mapStats.set(id, nativeStats[id]); - }); - return mapStats; - }); - }; - } - }); - - // legacy callback shims. Should be moved to adapter.js some days. - var methods = ['createOffer', 'createAnswer']; - methods.forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - var args = arguments; - if (typeof args[0] === 'function' || - typeof args[1] === 'function') { // legacy - return nativeMethod.apply(this, [arguments[2]]) - .then(function(description) { - if (typeof args[0] === 'function') { - args[0].apply(null, [description]); - } - }, function(error) { - if (typeof args[1] === 'function') { - args[1].apply(null, [error]); - } - }); - } - return nativeMethod.apply(this, arguments); - }; - }); - - methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; - methods.forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - var args = arguments; - if (typeof args[1] === 'function' || - typeof args[2] === 'function') { // legacy - return nativeMethod.apply(this, arguments) - .then(function() { - if (typeof args[1] === 'function') { - args[1].apply(null); - } - }, function(error) { - if (typeof args[2] === 'function') { - args[2].apply(null, [error]); - } - }); - } - return nativeMethod.apply(this, arguments); - }; - }); - - // getStats is special. It doesn't have a spec legacy method yet we support - // getStats(something, cb) without error callbacks. - ['getStats'].forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - var args = arguments; - if (typeof args[1] === 'function') { - return nativeMethod.apply(this, arguments) - .then(function() { - if (typeof args[1] === 'function') { - args[1].apply(null); - } - }); - } - return nativeMethod.apply(this, arguments); - }; - }); - return RTCPeerConnection; }; @@ -1882,19 +1588,6 @@ SDPUtils.splitSections = function(blob) { }); }; -// returns the session description. -SDPUtils.getDescription = function(blob) { - var sections = SDPUtils.splitSections(blob); - return sections && sections[0]; -}; - -// returns the individual media sections. -SDPUtils.getMediaSections = function(blob) { - var sections = SDPUtils.splitSections(blob); - sections.shift(); - return sections; -}; - // Returns lines that start with a certain prefix. SDPUtils.matchPrefix = function(blob, prefix) { return SDPUtils.splitLines(blob).filter(function(line) { @@ -1920,7 +1613,6 @@ SDPUtils.parseCandidate = function(line) { protocol: parts[2].toLowerCase(), priority: parseInt(parts[3], 10), ip: parts[4], - address: parts[4], // address is an alias for ip. port: parseInt(parts[5], 10), // skip parts[6] == 'typ' type: parts[7] @@ -1956,7 +1648,7 @@ SDPUtils.writeCandidate = function(candidate) { sdp.push(candidate.component); sdp.push(candidate.protocol.toUpperCase()); sdp.push(candidate.priority); - sdp.push(candidate.address || candidate.ip); + sdp.push(candidate.ip); sdp.push(candidate.port); var type = candidate.type; @@ -1965,17 +1657,17 @@ SDPUtils.writeCandidate = function(candidate) { if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) { sdp.push('raddr'); - sdp.push(candidate.relatedAddress); + sdp.push(candidate.relatedAddress); // was: relAddr sdp.push('rport'); - sdp.push(candidate.relatedPort); + sdp.push(candidate.relatedPort); // was: relPort } if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { sdp.push('tcptype'); sdp.push(candidate.tcpType); } - if (candidate.usernameFragment || candidate.ufrag) { + if (candidate.ufrag) { sdp.push('ufrag'); - sdp.push(candidate.usernameFragment || candidate.ufrag); + sdp.push(candidate.ufrag); } return 'candidate:' + sdp.join(' '); }; @@ -1984,7 +1676,7 @@ SDPUtils.writeCandidate = function(candidate) { // a=ice-options:foo bar SDPUtils.parseIceOptions = function(line) { return line.substr(14).split(' '); -}; +} // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: // a=rtpmap:111 opus/48000/2 @@ -1998,9 +1690,8 @@ SDPUtils.parseRtpMap = function(line) { parsed.name = parts[0]; parsed.clockRate = parseInt(parts[1], 10); // was: clockrate - parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; - // legacy alias, got renamed back to channels in ORTC. - parsed.numChannels = parsed.channels; + // was: channels + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; return parsed; }; @@ -2011,9 +1702,8 @@ SDPUtils.writeRtpMap = function(codec) { if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } - var channels = codec.channels || codec.numChannels || 1; return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + - (channels !== 1 ? '/' + channels : '') + '\r\n'; + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; }; // Parses an a=extmap line (headerextension from RFC 5285). Sample input: @@ -2062,11 +1752,7 @@ SDPUtils.writeFmtp = function(codec) { if (codec.parameters && Object.keys(codec.parameters).length) { var params = []; Object.keys(codec.parameters).forEach(function(param) { - if (codec.parameters[param]) { - params.push(param + '=' + codec.parameters[param]); - } else { - params.push(param); - } + params.push(param + '=' + codec.parameters[param]); }); line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; } @@ -2117,16 +1803,6 @@ SDPUtils.parseSsrcMedia = function(line) { return parts; }; -SDPUtils.parseSsrcGroup = function(line) { - var parts = line.substr(13).split(' '); - return { - semantics: parts.shift(), - ssrcs: parts.map(function(ssrc) { - return parseInt(ssrc, 10); - }) - }; -}; - // Extracts the MID (RFC 5888) from a media section. // returns the MID or undefined if no mid line was found. SDPUtils.getMid = function(mediaSection) { @@ -2134,7 +1810,7 @@ SDPUtils.getMid = function(mediaSection) { if (mid) { return mid.substr(6); } -}; +} SDPUtils.parseFingerprint = function(line) { var parts = line.substr(14).split(' '); @@ -2268,11 +1944,9 @@ SDPUtils.writeRtpDescription = function(kind, caps) { } sdp += 'a=rtcp-mux\r\n'; - if (caps.headerExtensions) { - caps.headerExtensions.forEach(function(extension) { - sdp += SDPUtils.writeExtmap(extension); - }); - } + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); // FIXME: write fecMechanisms. return sdp; }; @@ -2298,7 +1972,8 @@ SDPUtils.parseRtpEncodingParameters = function(mediaSection) { var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') .map(function(line) { - var parts = line.substr(17).split(' '); + var parts = line.split(' '); + parts.shift(); return parts.map(function(part) { return parseInt(part, 10); }); @@ -2311,16 +1986,16 @@ SDPUtils.parseRtpEncodingParameters = function(mediaSection) { if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { var encParam = { ssrc: primarySsrc, - codecPayloadType: parseInt(codec.parameters.apt, 10) + codecPayloadType: parseInt(codec.parameters.apt, 10), + rtx: { + ssrc: secondarySsrc + } }; - if (primarySsrc && secondarySsrc) { - encParam.rtx = {ssrc: secondarySsrc}; - } encodingParameters.push(encParam); if (hasRed) { encParam = JSON.parse(JSON.stringify(encParam)); encParam.fec = { - ssrc: primarySsrc, + ssrc: secondarySsrc, mechanism: hasUlpfec ? 'red+ulpfec' : 'red' }; encodingParameters.push(encParam); @@ -2356,7 +2031,8 @@ SDPUtils.parseRtpEncodingParameters = function(mediaSection) { SDPUtils.parseRtcpParameters = function(mediaSection) { var rtcpParameters = {}; - // Gets the first SSRC. Note tha with RTX there might be multiple + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple // SSRCs. var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') .map(function(line) { @@ -2397,8 +2073,8 @@ SDPUtils.parseMsid = function(mediaSection) { .map(function(line) { return SDPUtils.parseSsrcMedia(line); }) - .filter(function(msidParts) { - return msidParts.attribute === 'msid'; + .filter(function(parts) { + return parts.attribute === 'msid'; }); if (planB.length > 0) { parts = planB[0].value.split(' '); @@ -2418,8 +2094,7 @@ SDPUtils.generateSessionId = function() { // sessId argument is optional - if not supplied it will // be generated randomly // sessVersion is optional and defaults to 2 -// sessUser is optional and defaults to 'thisisadapterortc' -SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) { var sessionId; var version = sessVer !== undefined ? sessVer : 2; if (sessId) { @@ -2427,11 +2102,9 @@ SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { } else { sessionId = SDPUtils.generateSessionId(); } - var user = sessUser || 'thisisadapterortc'; // FIXME: sess-id should be an NTP timestamp. return 'v=0\r\n' + - 'o=' + user + ' ' + sessionId + ' ' + version + - ' IN IP4 127.0.0.1\r\n' + + 'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n'; }; @@ -2523,43 +2196,15 @@ SDPUtils.isRejected = function(mediaSection) { SDPUtils.parseMLine = function(mediaSection) { var lines = SDPUtils.splitLines(mediaSection); - var parts = lines[0].substr(2).split(' '); + var mline = lines[0].split(' '); return { - kind: parts[0], - port: parseInt(parts[1], 10), - protocol: parts[2], - fmt: parts.slice(3).join(' ') + kind: mline[0].substr(2), + port: parseInt(mline[1], 10), + protocol: mline[2], + fmt: mline.slice(3).join(' ') }; }; -SDPUtils.parseOLine = function(mediaSection) { - var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; - var parts = line.substr(2).split(' '); - return { - username: parts[0], - sessionId: parts[1], - sessionVersion: parseInt(parts[2], 10), - netType: parts[3], - addressType: parts[4], - address: parts[5] - }; -}; - -// a very naive interpretation of a valid SDP. -SDPUtils.isValidSDP = function(blob) { - if (typeof blob !== 'string' || blob.length === 0) { - return false; - } - var lines = SDPUtils.splitLines(blob); - for (var i = 0; i < lines.length; i++) { - if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { - return false; - } - // TODO: check the modifier a bit more. - } - return true; -}; - // Expose public methods. if (typeof module === 'object') { module.exports = SDPUtils; @@ -2616,6 +2261,14 @@ module.exports = function(dependencies, opts) { var logging = utils.log; var browserDetails = utils.detectBrowser(window); + // Export to the adapter global object visible in the browser. + var adapter = { + browserDetails: browserDetails, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings + }; + // Uncomment the line below if you want logging to occur, including logging // for the switch statement below. Can also be turned on in the browser via // adapter.disableLog(false), but then logging from the switch statement below @@ -2629,15 +2282,6 @@ module.exports = function(dependencies, opts) { var safariShim = require('./safari/safari_shim') || null; var commonShim = require('./common_shim') || null; - // Export to the adapter global object visible in the browser. - var adapter = { - browserDetails: browserDetails, - commonShim: commonShim, - extractVersion: utils.extractVersion, - disableLog: utils.disableLog, - disableWarnings: utils.disableWarnings - }; - // Shim browser if found. switch (browserDetails.browser) { case 'chrome': @@ -2658,12 +2302,8 @@ module.exports = function(dependencies, opts) { chromeShim.shimOnTrack(window); chromeShim.shimAddTrackRemoveTrack(window); chromeShim.shimGetSendersWithDtmf(window); - chromeShim.shimSenderReceiverGetStats(window); - chromeShim.fixNegotiationNeeded(window); commonShim.shimRTCIceCandidate(window); - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); break; case 'firefox': if (!firefoxShim || !firefoxShim.shimPeerConnection || @@ -2680,14 +2320,8 @@ module.exports = function(dependencies, opts) { firefoxShim.shimSourceObject(window); firefoxShim.shimPeerConnection(window); firefoxShim.shimOnTrack(window); - firefoxShim.shimRemoveStream(window); - firefoxShim.shimSenderGetStats(window); - firefoxShim.shimReceiverGetStats(window); - firefoxShim.shimRTCDataChannel(window); commonShim.shimRTCIceCandidate(window); - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); break; case 'edge': if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { @@ -2704,9 +2338,6 @@ module.exports = function(dependencies, opts) { edgeShim.shimReplaceTrack(window); // the edge shim implements the full RTCIceCandidate object. - - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); break; case 'safari': if (!safariShim || !options.shimSafari) { @@ -2719,16 +2350,14 @@ module.exports = function(dependencies, opts) { commonShim.shimCreateObjectURL(window); safariShim.shimRTCIceServerUrls(window); - safariShim.shimCreateOfferLegacy(window); safariShim.shimCallbacksAPI(window); safariShim.shimLocalStreamsAPI(window); safariShim.shimRemoteStreamsAPI(window); safariShim.shimTrackEventTransceiver(window); safariShim.shimGetUserMedia(window); + safariShim.shimCreateOfferLegacy(window); commonShim.shimRTCIceCandidate(window); - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); break; default: logging('Unsupported browser!'); @@ -2738,7 +2367,7 @@ module.exports = function(dependencies, opts) { return adapter; }; -},{"./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){ +},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],5:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. @@ -2752,49 +2381,7 @@ module.exports = function(dependencies, opts) { var utils = require('../utils.js'); var logging = utils.log; -/* iterates the stats graph recursively. */ -function walkStats(stats, base, resultSet) { - if (!base || resultSet.has(base.id)) { - return; - } - resultSet.set(base.id, base); - Object.keys(base).forEach(function(name) { - if (name.endsWith('Id')) { - walkStats(stats, stats.get(base[name]), resultSet); - } else if (name.endsWith('Ids')) { - base[name].forEach(function(id) { - walkStats(stats, stats.get(id), resultSet); - }); - } - }); -} - -/* filter getStats for a sender/receiver track. */ -function filterStats(result, track, outbound) { - var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; - var filteredResult = new Map(); - if (track === null) { - return filteredResult; - } - var trackStats = []; - result.forEach(function(value) { - if (value.type === 'track' && - value.trackIdentifier === track.id) { - trackStats.push(value); - } - }); - trackStats.forEach(function(trackStat) { - result.forEach(function(stats) { - if (stats.type === streamStatsType && stats.trackId === trackStat.id) { - walkStats(result, stats, filteredResult); - } - }); - }); - return filteredResult; -} - -module.exports = { - shimGetUserMedia: require('./getusermedia'), +var chromeShim = { shimMediaStream: function(window) { window.MediaStream = window.MediaStream || window.webkitMediaStream; }, @@ -2811,9 +2398,7 @@ module.exports = { this.removeEventListener('track', this._ontrack); } this.addEventListener('track', this._ontrack = f); - }, - enumerable: true, - configurable: true + } }); var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; @@ -2861,17 +2446,6 @@ module.exports = { } return origSetRemoteDescription.apply(pc, arguments); }; - } else { - // even if RTCRtpTransceiver is in window, it is only used and - // emitted in unified-plan. Unfortunately this means we need - // to unconditionally wrap the event. - utils.wrapPeerConnectionEvent(window, 'track', function(e) { - if (!e.transceiver) { - Object.defineProperty(e, 'transceiver', - {value: {receiver: e.receiver}}); - } - return e; - }); } }, @@ -2979,122 +2553,6 @@ module.exports = { } }, - shimSenderReceiverGetStats: function(window) { - if (!(typeof window === 'object' && window.RTCPeerConnection && - window.RTCRtpSender && window.RTCRtpReceiver)) { - return; - } - - // shim sender stats. - if (!('getStats' in window.RTCRtpSender.prototype)) { - var origGetSenders = window.RTCPeerConnection.prototype.getSenders; - if (origGetSenders) { - window.RTCPeerConnection.prototype.getSenders = function() { - var pc = this; - var senders = origGetSenders.apply(pc, []); - senders.forEach(function(sender) { - sender._pc = pc; - }); - return senders; - }; - } - - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - if (origAddTrack) { - window.RTCPeerConnection.prototype.addTrack = function() { - var sender = origAddTrack.apply(this, arguments); - sender._pc = this; - return sender; - }; - } - window.RTCRtpSender.prototype.getStats = function() { - var sender = this; - return this._pc.getStats().then(function(result) { - /* Note: this will include stats of all senders that - * send a track with the same id as sender.track as - * it is not possible to identify the RTCRtpSender. - */ - return filterStats(result, sender.track, true); - }); - }; - } - - // shim receiver stats. - if (!('getStats' in window.RTCRtpReceiver.prototype)) { - var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; - if (origGetReceivers) { - window.RTCPeerConnection.prototype.getReceivers = function() { - var pc = this; - var receivers = origGetReceivers.apply(pc, []); - receivers.forEach(function(receiver) { - receiver._pc = pc; - }); - return receivers; - }; - } - utils.wrapPeerConnectionEvent(window, 'track', function(e) { - e.receiver._pc = e.srcElement; - return e; - }); - window.RTCRtpReceiver.prototype.getStats = function() { - var receiver = this; - return this._pc.getStats().then(function(result) { - return filterStats(result, receiver.track, false); - }); - }; - } - - if (!('getStats' in window.RTCRtpSender.prototype && - 'getStats' in window.RTCRtpReceiver.prototype)) { - return; - } - - // shim RTCPeerConnection.getStats(track). - var origGetStats = window.RTCPeerConnection.prototype.getStats; - window.RTCPeerConnection.prototype.getStats = function() { - var pc = this; - if (arguments.length > 0 && - arguments[0] instanceof window.MediaStreamTrack) { - var track = arguments[0]; - var sender; - var receiver; - var err; - pc.getSenders().forEach(function(s) { - if (s.track === track) { - if (sender) { - err = true; - } else { - sender = s; - } - } - }); - pc.getReceivers().forEach(function(r) { - if (r.track === track) { - if (receiver) { - err = true; - } else { - receiver = r; - } - } - return r.track === track; - }); - if (err || (sender && receiver)) { - return Promise.reject(new DOMException( - 'There are more than one sender or receiver for the track.', - 'InvalidAccessError')); - } else if (sender) { - return sender.getStats(); - } else if (receiver) { - return receiver.getStats(); - } - return Promise.reject(new DOMException( - 'There is no sender or receiver for the track.', - 'InvalidAccessError')); - } - return origGetStats.apply(pc, arguments); - }; - }, - shimSourceObject: function(window) { var URL = window && window.URL; @@ -3139,88 +2597,12 @@ module.exports = { } }, - shimAddTrackRemoveTrackWithNative: function(window) { - // shim addTrack/removeTrack with native variants in order to make - // the interactions with legacy getLocalStreams behave as in other browsers. - // Keeps a mapping stream.id => [stream, rtpsenders...] - window.RTCPeerConnection.prototype.getLocalStreams = function() { - var pc = this; - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - return Object.keys(this._shimmedLocalStreams).map(function(streamId) { - return pc._shimmedLocalStreams[streamId][0]; - }); - }; - - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - window.RTCPeerConnection.prototype.addTrack = function(track, stream) { - if (!stream) { - return origAddTrack.apply(this, arguments); - } - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - - var sender = origAddTrack.apply(this, arguments); - if (!this._shimmedLocalStreams[stream.id]) { - this._shimmedLocalStreams[stream.id] = [stream, sender]; - } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { - this._shimmedLocalStreams[stream.id].push(sender); - } - return sender; - }; - - var origAddStream = window.RTCPeerConnection.prototype.addStream; - window.RTCPeerConnection.prototype.addStream = function(stream) { - var pc = this; - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - - stream.getTracks().forEach(function(track) { - var alreadyExists = pc.getSenders().find(function(s) { - return s.track === track; - }); - if (alreadyExists) { - throw new DOMException('Track already exists.', - 'InvalidAccessError'); - } - }); - var existingSenders = pc.getSenders(); - origAddStream.apply(this, arguments); - var newSenders = pc.getSenders().filter(function(newSender) { - return existingSenders.indexOf(newSender) === -1; - }); - this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); - }; - - var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; - window.RTCPeerConnection.prototype.removeStream = function(stream) { - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - delete this._shimmedLocalStreams[stream.id]; - return origRemoveStream.apply(this, arguments); - }; - - var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; - window.RTCPeerConnection.prototype.removeTrack = function(sender) { - var pc = this; - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - if (sender) { - Object.keys(this._shimmedLocalStreams).forEach(function(streamId) { - var idx = pc._shimmedLocalStreams[streamId].indexOf(sender); - if (idx !== -1) { - pc._shimmedLocalStreams[streamId].splice(idx, 1); - } - if (pc._shimmedLocalStreams[streamId].length === 1) { - delete pc._shimmedLocalStreams[streamId]; - } - }); - } - return origRemoveTrack.apply(this, arguments); - }; - }, - shimAddTrackRemoveTrack: function(window) { var browserDetails = utils.detectBrowser(window); // shim addTrack and removeTrack. if (window.RTCPeerConnection.prototype.addTrack && - browserDetails.version >= 65) { - return this.shimAddTrackRemoveTrackWithNative(window); + browserDetails.version >= 63) { + return; } // also shim pc.getLocalStreams when addTrack is shimmed @@ -3228,11 +2610,11 @@ module.exports = { var origGetLocalStreams = window.RTCPeerConnection.prototype .getLocalStreams; window.RTCPeerConnection.prototype.getLocalStreams = function() { - var pc = this; + var self = this; var nativeStreams = origGetLocalStreams.apply(this); - pc._reverseStreams = pc._reverseStreams || {}; + self._reverseStreams = self._reverseStreams || {}; return nativeStreams.map(function(stream) { - return pc._reverseStreams[stream.id]; + return self._reverseStreams[stream.id]; }); }; @@ -3458,7 +2840,7 @@ module.exports = { var browserDetails = utils.detectBrowser(window); // The RTCPeerConnection object. - if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + if (!window.RTCPeerConnection) { window.RTCPeerConnection = function(pcConfig, pcConstraints) { // Translate iceTransportPolicy to iceTransports, // see https://code.google.com/p/webrtc/issues/detail?id=4869 @@ -3480,12 +2862,41 @@ module.exports = { } }); } + } else { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); } var origGetStats = window.RTCPeerConnection.prototype.getStats; window.RTCPeerConnection.prototype.getStats = function(selector, successCallback, errorCallback) { - var pc = this; + var self = this; var args = arguments; // If selector is a function then we are in the old style stats so just @@ -3540,7 +2951,7 @@ module.exports = { // promise-support return new Promise(function(resolve, reject) { - origGetStats.apply(pc, [ + origGetStats.apply(self, [ function(response) { resolve(makeMapStats(fixChromeStats_(response))); }, reject]); @@ -3554,9 +2965,9 @@ module.exports = { var nativeMethod = window.RTCPeerConnection.prototype[method]; window.RTCPeerConnection.prototype[method] = function() { var args = arguments; - var pc = this; + var self = this; var promise = new Promise(function(resolve, reject) { - nativeMethod.apply(pc, [args[0], resolve, reject]); + nativeMethod.apply(self, [args[0], resolve, reject]); }); if (args.length < 2) { return promise; @@ -3579,12 +2990,12 @@ module.exports = { ['createOffer', 'createAnswer'].forEach(function(method) { var nativeMethod = window.RTCPeerConnection.prototype[method]; window.RTCPeerConnection.prototype[method] = function() { - var pc = this; + var self = this; if (arguments.length < 1 || (arguments.length === 1 && typeof arguments[0] === 'object')) { var opts = arguments.length === 1 ? arguments[0] : undefined; return new Promise(function(resolve, reject) { - nativeMethod.apply(pc, [resolve, reject, opts]); + nativeMethod.apply(self, [resolve, reject, opts]); }); } return nativeMethod.apply(this, arguments); @@ -3616,46 +3027,22 @@ module.exports = { } return nativeAddIceCandidate.apply(this, arguments); }; - }, - - fixNegotiationNeeded: function(window) { - utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function(e) { - var pc = e.target; - if (pc.signalingState !== 'stable') { - return; - } - return e; - }); - }, - - shimGetDisplayMedia: function(window, getSourceId) { - if ('getDisplayMedia' in window.navigator) { - return; - } - // getSourceId is a function that returns a promise resolving with - // the sourceId of the screen/window/tab to be shared. - if (typeof getSourceId !== 'function') { - console.error('shimGetDisplayMedia: getSourceId argument is not ' + - 'a function'); - return; - } - navigator.getDisplayMedia = function(constraints) { - return getSourceId(constraints) - .then(function(sourceId) { - constraints.video = { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: sourceId, - maxFrameRate: constraints.video.frameRate || 3 - } - }; - return navigator.mediaDevices.getUserMedia(constraints); - }); - }; } }; -},{"../utils.js":14,"./getusermedia":6}],6:[function(require,module,exports){ + +// Expose public methods. +module.exports = { + shimMediaStream: chromeShim.shimMediaStream, + shimOnTrack: chromeShim.shimOnTrack, + shimAddTrackRemoveTrack: chromeShim.shimAddTrackRemoveTrack, + shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf, + shimSourceObject: chromeShim.shimSourceObject, + shimPeerConnection: chromeShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') +}; + +},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -3725,9 +3112,6 @@ module.exports = function(window) { }; var shimConstraints_ = function(constraints, func) { - if (browserDetails.version >= 61) { - return func(constraints); - } constraints = JSON.parse(JSON.stringify(constraints)); if (constraints && typeof constraints.audio === 'object') { var remap = function(obj, a, b) { @@ -3791,25 +3175,18 @@ module.exports = function(window) { }; var shimError_ = function(e) { - if (browserDetails.version >= 64) { - return e; - } return { name: { PermissionDeniedError: 'NotAllowedError', - PermissionDismissedError: 'NotAllowedError', - InvalidStateError: 'NotAllowedError', + InvalidStateError: 'NotReadableError', DevicesNotFoundError: 'NotFoundError', ConstraintNotSatisfiedError: 'OverconstrainedError', TrackStartError: 'NotReadableError', - MediaDeviceFailedDueToShutdown: 'NotAllowedError', - MediaDeviceKillSwitchOn: 'NotAllowedError', - TabCaptureError: 'AbortError', - ScreenCaptureError: 'AbortError', - DeviceCaptureError: 'AbortError' + MediaDeviceFailedDueToShutdown: 'NotReadableError', + MediaDeviceKillSwitchOn: 'NotReadableError' }[e.name] || e.name, message: e.message, - constraint: e.constraint || e.constraintName, + constraint: e.constraintName, toString: function() { return this.name + (this.message && ': ') + this.message; } @@ -3904,7 +3281,7 @@ module.exports = function(window) { } }; -},{"../utils.js":14}],7:[function(require,module,exports){ +},{"../utils.js":13}],7:[function(require,module,exports){ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * @@ -3918,12 +3295,63 @@ module.exports = function(window) { var SDPUtils = require('sdp'); var utils = require('./utils'); +// Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object. +function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + proto.addEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + var wrappedCallback = function(e) { + cb(wrapper(e)); + }; + this._eventMap = this._eventMap || {}; + this._eventMap[cb] = wrappedCallback; + return nativeAddEventListener.apply(this, [nativeEventName, + wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + proto.removeEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap + || !this._eventMap[cb]) { + return nativeRemoveEventListener.apply(this, arguments); + } + var unwrappedCb = this._eventMap[cb]; + delete this._eventMap[cb]; + return nativeRemoveEventListener.apply(this, [nativeEventName, + unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function() { + return this['_on' + eventNameToWrap]; + }, + set: function(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, + this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + if (cb) { + this.addEventListener(eventNameToWrap, + this['_on' + eventNameToWrap] = cb); + } + } + }); +} + module.exports = { shimRTCIceCandidate: function(window) { // foundation is arbitrarily chosen as an indicator for full support for // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface - if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in - window.RTCIceCandidate.prototype)) { + if (window.RTCIceCandidate && 'foundation' in + window.RTCIceCandidate.prototype) { return; } @@ -3936,31 +3364,27 @@ module.exports = { args.candidate = args.candidate.substr(2); } - if (args.candidate && args.candidate.length) { - // Augment the native candidate with the parsed fields. - var nativeCandidate = new NativeRTCIceCandidate(args); - var parsedCandidate = SDPUtils.parseCandidate(args.candidate); - var augmentedCandidate = Object.assign(nativeCandidate, - parsedCandidate); + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + var parsedCandidate = SDPUtils.parseCandidate(args.candidate); + var augmentedCandidate = Object.assign(nativeCandidate, + parsedCandidate); - // Add a serializer that does not serialize the extra attributes. - augmentedCandidate.toJSON = function() { - return { - candidate: augmentedCandidate.candidate, - sdpMid: augmentedCandidate.sdpMid, - sdpMLineIndex: augmentedCandidate.sdpMLineIndex, - usernameFragment: augmentedCandidate.usernameFragment, - }; + // Add a serializer that does not serialize the extra attributes. + augmentedCandidate.toJSON = function() { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment, }; - return augmentedCandidate; - } - return new NativeRTCIceCandidate(args); + }; + return augmentedCandidate; }; - window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; // Hook up the augmented candidate in onicecandidate and // addEventListener('icecandidate', ...) - utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) { + wrapPeerConnectionEvent(window, 'icecandidate', function(e) { if (e.candidate) { Object.defineProperty(e, 'candidate', { value: new window.RTCIceCandidate(e.candidate), @@ -4022,181 +3446,10 @@ module.exports = { } return nativeSetAttribute.apply(this, arguments); }; - }, - - shimMaxMessageSize: function(window) { - if (window.RTCSctpTransport || !window.RTCPeerConnection) { - return; - } - var browserDetails = utils.detectBrowser(window); - - if (!('sctp' in window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { - get: function() { - return typeof this._sctp === 'undefined' ? null : this._sctp; - } - }); - } - - var sctpInDescription = function(description) { - var sections = SDPUtils.splitSections(description.sdp); - sections.shift(); - return sections.some(function(mediaSection) { - var mLine = SDPUtils.parseMLine(mediaSection); - return mLine && mLine.kind === 'application' - && mLine.protocol.indexOf('SCTP') !== -1; - }); - }; - - var getRemoteFirefoxVersion = function(description) { - // TODO: Is there a better solution for detecting Firefox? - var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); - if (match === null || match.length < 2) { - return -1; - } - var version = parseInt(match[1], 10); - // Test for NaN (yes, this is ugly) - return version !== version ? -1 : version; - }; - - var getCanSendMaxMessageSize = function(remoteIsFirefox) { - // Every implementation we know can send at least 64 KiB. - // Note: Although Chrome is technically able to send up to 256 KiB, the - // data does not reach the other peer reliably. - // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 - var canSendMaxMessageSize = 65536; - if (browserDetails.browser === 'firefox') { - if (browserDetails.version < 57) { - if (remoteIsFirefox === -1) { - // FF < 57 will send in 16 KiB chunks using the deprecated PPID - // fragmentation. - canSendMaxMessageSize = 16384; - } else { - // However, other FF (and RAWRTC) can reassemble PPID-fragmented - // messages. Thus, supporting ~2 GiB when sending. - canSendMaxMessageSize = 2147483637; - } - } else if (browserDetails.version < 60) { - // Currently, all FF >= 57 will reset the remote maximum message size - // to the default value when a data channel is created at a later - // stage. :( - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 - canSendMaxMessageSize = - browserDetails.version === 57 ? 65535 : 65536; - } else { - // FF >= 60 supports sending ~2 GiB - canSendMaxMessageSize = 2147483637; - } - } - return canSendMaxMessageSize; - }; - - var getMaxMessageSize = function(description, remoteIsFirefox) { - // Note: 65536 bytes is the default value from the SDP spec. Also, - // every implementation we know supports receiving 65536 bytes. - var maxMessageSize = 65536; - - // FF 57 has a slightly incorrect default remote max message size, so - // we need to adjust it here to avoid a failure when sending. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 - if (browserDetails.browser === 'firefox' - && browserDetails.version === 57) { - maxMessageSize = 65535; - } - - var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:'); - if (match.length > 0) { - maxMessageSize = parseInt(match[0].substr(19), 10); - } else if (browserDetails.browser === 'firefox' && - remoteIsFirefox !== -1) { - // If the maximum message size is not present in the remote SDP and - // both local and remote are Firefox, the remote peer can receive - // ~2 GiB. - maxMessageSize = 2147483637; - } - return maxMessageSize; - }; - - var origSetRemoteDescription = - window.RTCPeerConnection.prototype.setRemoteDescription; - window.RTCPeerConnection.prototype.setRemoteDescription = function() { - var pc = this; - pc._sctp = null; - - if (sctpInDescription(arguments[0])) { - // Check if the remote is FF. - var isFirefox = getRemoteFirefoxVersion(arguments[0]); - - // Get the maximum message size the local peer is capable of sending - var canSendMMS = getCanSendMaxMessageSize(isFirefox); - - // Get the maximum message size of the remote peer. - var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); - - // Determine final maximum message size - var maxMessageSize; - if (canSendMMS === 0 && remoteMMS === 0) { - maxMessageSize = Number.POSITIVE_INFINITY; - } else if (canSendMMS === 0 || remoteMMS === 0) { - maxMessageSize = Math.max(canSendMMS, remoteMMS); - } else { - maxMessageSize = Math.min(canSendMMS, remoteMMS); - } - - // Create a dummy RTCSctpTransport object and the 'maxMessageSize' - // attribute. - var sctp = {}; - Object.defineProperty(sctp, 'maxMessageSize', { - get: function() { - return maxMessageSize; - } - }); - pc._sctp = sctp; - } - - return origSetRemoteDescription.apply(pc, arguments); - }; - }, - - shimSendThrowTypeError: function(window) { - if (!(window.RTCPeerConnection && - 'createDataChannel' in window.RTCPeerConnection.prototype)) { - return; - } - - // Note: Although Firefox >= 57 has a native implementation, the maximum - // message size can be reset for all data channels at a later stage. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 - - function wrapDcSend(dc, pc) { - var origDataChannelSend = dc.send; - dc.send = function() { - var data = arguments[0]; - var length = data.length || data.size || data.byteLength; - if (dc.readyState === 'open' && - pc.sctp && length > pc.sctp.maxMessageSize) { - throw new TypeError('Message too large (can send a maximum of ' + - pc.sctp.maxMessageSize + ' bytes)'); - } - return origDataChannelSend.apply(dc, arguments); - }; - } - var origCreateDataChannel = - window.RTCPeerConnection.prototype.createDataChannel; - window.RTCPeerConnection.prototype.createDataChannel = function() { - var pc = this; - var dataChannel = origCreateDataChannel.apply(pc, arguments); - wrapDcSend(dataChannel, pc); - return dataChannel; - }; - utils.wrapPeerConnectionEvent(window, 'datachannel', function(e) { - wrapDcSend(e.channel, e.target); - return e; - }); } }; -},{"./utils":14,"sdp":2}],8:[function(require,module,exports){ +},{"./utils":13,"sdp":2}],8:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -4208,7 +3461,6 @@ module.exports = { 'use strict'; var utils = require('../utils'); -var filterIceServers = require('./filtericeservers'); var shimRTCPeerConnection = require('rtcpeerconnection-shim'); module.exports = { @@ -4217,11 +3469,16 @@ module.exports = { var browserDetails = utils.detectBrowser(window); if (window.RTCIceGatherer) { + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. if (!window.RTCIceCandidate) { window.RTCIceCandidate = function(args) { return args; }; } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. if (!window.RTCSessionDescription) { window.RTCSessionDescription = function(args) { return args; @@ -4260,21 +3517,9 @@ module.exports = { } }); } - // Edge currently only implements the RTCDtmfSender, not the - // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2* - if (window.RTCDtmfSender && !window.RTCDTMFSender) { - window.RTCDTMFSender = window.RTCDtmfSender; - } - var RTCPeerConnectionShim = shimRTCPeerConnection(window, - browserDetails.version); - window.RTCPeerConnection = function(config) { - if (config && config.iceServers) { - config.iceServers = filterIceServers(config.iceServers); - } - return new RTCPeerConnectionShim(config); - }; - window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; + window.RTCPeerConnection = + shimRTCPeerConnection(window, browserDetails.version); }, shimReplaceTrack: function(window) { // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 @@ -4286,58 +3531,7 @@ module.exports = { } }; -},{"../utils":14,"./filtericeservers":9,"./getusermedia":10,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){ -/* - * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ -'use strict'; - -var utils = require('../utils'); -// Edge does not like -// 1) stun: filtered after 14393 unless ?transport=udp is present -// 2) turn: that does not have all of turn:host:port?transport=udp -// 3) turn: with ipv6 addresses -// 4) turn: occurring muliple times -module.exports = function(iceServers, edgeVersion) { - var hasTurn = false; - iceServers = JSON.parse(JSON.stringify(iceServers)); - return iceServers.filter(function(server) { - if (server && (server.urls || server.url)) { - var urls = server.urls || server.url; - if (server.url && !server.urls) { - utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); - } - var isString = typeof urls === 'string'; - if (isString) { - urls = [urls]; - } - urls = urls.filter(function(url) { - var validTurn = url.indexOf('turn:') === 0 && - url.indexOf('transport=udp') !== -1 && - url.indexOf('turn:[') === -1 && - !hasTurn; - - if (validTurn) { - hasTurn = true; - return true; - } - return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && - url.indexOf('?transport=udp') === -1; - }); - - delete server.url; - server.urls = isString ? urls[0] : urls; - return !!urls.length; - } - }); -}; - -},{"../utils":14}],10:[function(require,module,exports){ +},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -4373,7 +3567,7 @@ module.exports = function(window) { }; }; -},{}],11:[function(require,module,exports){ +},{}],10:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -4386,8 +3580,7 @@ module.exports = function(window) { var utils = require('../utils'); -module.exports = { - shimGetUserMedia: require('./getusermedia'), +var firefoxShim = { shimOnTrack: function(window) { if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { @@ -4411,9 +3604,7 @@ module.exports = { this.dispatchEvent(event); }.bind(this)); }.bind(this)); - }, - enumerable: true, - configurable: true + } }); } if (typeof window === 'object' && window.RTCTrackEvent && @@ -4575,118 +3766,18 @@ module.exports = { }) .then(onSucc, onErr); }; - }, - - shimSenderGetStats: function(window) { - if (!(typeof window === 'object' && window.RTCPeerConnection && - window.RTCRtpSender)) { - return; - } - if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { - return; - } - var origGetSenders = window.RTCPeerConnection.prototype.getSenders; - if (origGetSenders) { - window.RTCPeerConnection.prototype.getSenders = function() { - var pc = this; - var senders = origGetSenders.apply(pc, []); - senders.forEach(function(sender) { - sender._pc = pc; - }); - return senders; - }; - } - - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - if (origAddTrack) { - window.RTCPeerConnection.prototype.addTrack = function() { - var sender = origAddTrack.apply(this, arguments); - sender._pc = this; - return sender; - }; - } - window.RTCRtpSender.prototype.getStats = function() { - return this.track ? this._pc.getStats(this.track) : - Promise.resolve(new Map()); - }; - }, - - shimReceiverGetStats: function(window) { - if (!(typeof window === 'object' && window.RTCPeerConnection && - window.RTCRtpSender)) { - return; - } - if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { - return; - } - var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; - if (origGetReceivers) { - window.RTCPeerConnection.prototype.getReceivers = function() { - var pc = this; - var receivers = origGetReceivers.apply(pc, []); - receivers.forEach(function(receiver) { - receiver._pc = pc; - }); - return receivers; - }; - } - utils.wrapPeerConnectionEvent(window, 'track', function(e) { - e.receiver._pc = e.srcElement; - return e; - }); - window.RTCRtpReceiver.prototype.getStats = function() { - return this._pc.getStats(this.track); - }; - }, - - shimRemoveStream: function(window) { - if (!window.RTCPeerConnection || - 'removeStream' in window.RTCPeerConnection.prototype) { - return; - } - window.RTCPeerConnection.prototype.removeStream = function(stream) { - var pc = this; - utils.deprecated('removeStream', 'removeTrack'); - this.getSenders().forEach(function(sender) { - if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) { - pc.removeTrack(sender); - } - }); - }; - }, - - shimRTCDataChannel: function(window) { - // rename DataChannel to RTCDataChannel (native fix in FF60): - // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 - if (window.DataChannel && !window.RTCDataChannel) { - window.RTCDataChannel = window.DataChannel; - } - }, - - shimGetDisplayMedia: function(window, preferredMediaSource) { - if ('getDisplayMedia' in window.navigator) { - return; - } - navigator.getDisplayMedia = function(constraints) { - if (!(constraints && constraints.video)) { - var err = new DOMException('getDisplayMedia without video ' + - 'constraints is undefined'); - err.name = 'NotFoundError'; - // from https://heycam.github.io/webidl/#idl-DOMException-error-names - err.code = 8; - return Promise.reject(err); - } - if (constraints.video === true) { - constraints.video = {mediaSource: preferredMediaSource}; - } else { - constraints.video.mediaSource = preferredMediaSource; - } - return navigator.mediaDevices.getUserMedia(constraints); - }; } }; -},{"../utils":14,"./getusermedia":12}],12:[function(require,module,exports){ +// Expose public methods. +module.exports = { + shimOnTrack: firefoxShim.shimOnTrack, + shimSourceObject: firefoxShim.shimSourceObject, + shimPeerConnection: firefoxShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') +}; + +},{"../utils":13,"./getusermedia":11}],11:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -4897,7 +3988,7 @@ module.exports = function(window) { }; }; -},{"../utils":14}],13:[function(require,module,exports){ +},{"../utils":13}],12:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -4908,7 +3999,13 @@ module.exports = function(window) { 'use strict'; var utils = require('../utils'); -module.exports = { +var safariShim = { + // TODO: DrAlex, should be here, double check against LayoutTests + + // TODO: once the back-end for the mac port is done, add. + // TODO: check for webkitGTK+ + // shimPeerConnection: function() { }, + shimLocalStreamsAPI: function(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; @@ -4950,9 +4047,9 @@ module.exports = { if (this._localStreams.indexOf(stream) === -1) { this._localStreams.push(stream); } - var pc = this; + var self = this; stream.getTracks().forEach(function(track) { - _addTrack.call(pc, track, stream); + _addTrack.call(self, track, stream); }); }; @@ -4964,7 +4061,7 @@ module.exports = { this._localStreams.push(stream); } } - return _addTrack.call(this, track, stream); + _addTrack.call(this, track, stream); }; } if (!('removeStream' in window.RTCPeerConnection.prototype)) { @@ -4977,11 +4074,11 @@ module.exports = { return; } this._localStreams.splice(index, 1); - var pc = this; + var self = this; var tracks = stream.getTracks(); this.getSenders().forEach(function(sender) { if (tracks.indexOf(sender.track) !== -1) { - pc.removeTrack(sender); + self.removeTrack(sender); } }); }; @@ -5004,32 +4101,24 @@ module.exports = { set: function(f) { if (this._onaddstream) { this.removeEventListener('addstream', this._onaddstream); + this.removeEventListener('track', this._onaddstreampoly); } this.addEventListener('addstream', this._onaddstream = f); + this.addEventListener('track', this._onaddstreampoly = function(e) { + var stream = e.streams[0]; + if (!this._remoteStreams) { + this._remoteStreams = []; + } + if (this._remoteStreams.indexOf(stream) >= 0) { + return; + } + this._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = e.streams[0]; + this.dispatchEvent(event); + }.bind(this)); } }); - var origSetRemoteDescription = - window.RTCPeerConnection.prototype.setRemoteDescription; - window.RTCPeerConnection.prototype.setRemoteDescription = function() { - var pc = this; - if (!this._onaddstreampoly) { - this.addEventListener('track', this._onaddstreampoly = function(e) { - e.streams.forEach(function(stream) { - if (!pc._remoteStreams) { - pc._remoteStreams = []; - } - if (pc._remoteStreams.indexOf(stream) >= 0) { - return; - } - pc._remoteStreams.push(stream); - var event = new Event('addstream'); - event.stream = stream; - pc.dispatchEvent(event); - }); - }); - } - return origSetRemoteDescription.apply(pc, arguments); - }; } }, shimCallbacksAPI: function(window) { @@ -5161,38 +4250,21 @@ module.exports = { window.RTCPeerConnection.prototype.createOffer = function(offerOptions) { var pc = this; if (offerOptions) { - if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { - // support bit values - offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; - } var audioTransceiver = pc.getTransceivers().find(function(transceiver) { return transceiver.sender.track && transceiver.sender.track.kind === 'audio'; }); if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { if (audioTransceiver.direction === 'sendrecv') { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection('sendonly'); - } else { - audioTransceiver.direction = 'sendonly'; - } + audioTransceiver.setDirection('sendonly'); } else if (audioTransceiver.direction === 'recvonly') { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection('inactive'); - } else { - audioTransceiver.direction = 'inactive'; - } + audioTransceiver.setDirection('inactive'); } } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { pc.addTransceiver('audio'); } - - if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { - // support bit values - offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; - } var videoTransceiver = pc.getTransceivers().find(function(transceiver) { return transceiver.sender.track && transceiver.sender.track.kind === 'video'; @@ -5213,7 +4285,20 @@ module.exports = { } }; -},{"../utils":14}],14:[function(require,module,exports){ +// Expose public methods. +module.exports = { + shimCallbacksAPI: safariShim.shimCallbacksAPI, + shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI, + shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI, + shimGetUserMedia: safariShim.shimGetUserMedia, + shimRTCIceServerUrls: safariShim.shimRTCIceServerUrls, + shimTrackEventTransceiver: safariShim.shimTrackEventTransceiver, + shimCreateOfferLegacy: safariShim.shimCreateOfferLegacy + // TODO + // shimPeerConnection: safariShim.shimPeerConnection +}; + +},{"../utils":13}],13:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -5227,80 +4312,8 @@ module.exports = { var logDisabled_ = true; var deprecationWarnings_ = true; -/** - * Extract browser version out of the provided user agent string. - * - * @param {!string} uastring userAgent string. - * @param {!string} expr Regular expression used as match criteria. - * @param {!number} pos position in the version string to be returned. - * @return {!number} browser version. - */ -function extractVersion(uastring, expr, pos) { - var match = uastring.match(expr); - return match && match.length >= pos && parseInt(match[pos], 10); -} - -// Wraps the peerconnection event eventNameToWrap in a function -// which returns the modified event object (or false to prevent -// the event). -function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { - if (!window.RTCPeerConnection) { - return; - } - var proto = window.RTCPeerConnection.prototype; - var nativeAddEventListener = proto.addEventListener; - proto.addEventListener = function(nativeEventName, cb) { - if (nativeEventName !== eventNameToWrap) { - return nativeAddEventListener.apply(this, arguments); - } - var wrappedCallback = function(e) { - var modifiedEvent = wrapper(e); - if (modifiedEvent) { - cb(modifiedEvent); - } - }; - this._eventMap = this._eventMap || {}; - this._eventMap[cb] = wrappedCallback; - return nativeAddEventListener.apply(this, [nativeEventName, - wrappedCallback]); - }; - - var nativeRemoveEventListener = proto.removeEventListener; - proto.removeEventListener = function(nativeEventName, cb) { - if (nativeEventName !== eventNameToWrap || !this._eventMap - || !this._eventMap[cb]) { - return nativeRemoveEventListener.apply(this, arguments); - } - var unwrappedCb = this._eventMap[cb]; - delete this._eventMap[cb]; - return nativeRemoveEventListener.apply(this, [nativeEventName, - unwrappedCb]); - }; - - Object.defineProperty(proto, 'on' + eventNameToWrap, { - get: function() { - return this['_on' + eventNameToWrap]; - }, - set: function(cb) { - if (this['_on' + eventNameToWrap]) { - this.removeEventListener(eventNameToWrap, - this['_on' + eventNameToWrap]); - delete this['_on' + eventNameToWrap]; - } - if (cb) { - this.addEventListener(eventNameToWrap, - this['_on' + eventNameToWrap] = cb); - } - }, - enumerable: true, - configurable: true - }); -} - // Utility methods. -module.exports = { - extractVersion: extractVersion, - wrapPeerConnectionEvent: wrapPeerConnectionEvent, +var utils = { disableLog: function(bool) { if (typeof bool !== 'boolean') { return new Error('Argument type: ' + typeof bool + @@ -5346,6 +4359,19 @@ module.exports = { ' instead.'); }, + /** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }, + /** * Browser detector. * @@ -5366,25 +4392,38 @@ module.exports = { return result; } - if (navigator.mozGetUserMedia) { // Firefox. + // Firefox. + if (navigator.mozGetUserMedia) { result.browser = 'firefox'; - result.version = extractVersion(navigator.userAgent, + result.version = this.extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); } else if (navigator.webkitGetUserMedia) { - // Chrome, Chromium, Webview, Opera. - // Version matches Chrome/WebRTC version. - result.browser = 'chrome'; - result.version = extractVersion(navigator.userAgent, + // Chrome, Chromium, Webview, Opera, all use the chrome shim for now + if (window.webkitRTCPeerConnection) { + result.browser = 'chrome'; + result.version = this.extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); + } else { // Safari (in an unpublished version) or unknown webkit-based. + if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { + result.browser = 'safari'; + result.version = this.extractVersion(navigator.userAgent, + /AppleWebKit\/(\d+)\./, 1); + } else { // unknown webkit-based browser. + result.browser = 'Unsupported webkit-based browser ' + + 'with GUM support but no WebRTC support.'; + return result; + } + } } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. result.browser = 'edge'; - result.version = extractVersion(navigator.userAgent, + result.version = this.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); - } else if (window.RTCPeerConnection && - navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. + } else if (navigator.mediaDevices && + navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { + // Safari, with webkitGetUserMedia removed. result.browser = 'safari'; - result.version = extractVersion(navigator.userAgent, + result.version = this.extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); } else { // Default fallthrough: not supported. result.browser = 'Not a supported browser.'; @@ -5392,8 +4431,20 @@ module.exports = { } return result; - } + }, + +}; + +// Export. +module.exports = { + log: utils.log, + deprecated: utils.deprecated, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings, + extractVersion: utils.extractVersion, + shimCreateObjectURL: utils.shimCreateObjectURL, + detectBrowser: utils.detectBrowser.bind(utils) }; },{}]},{},[3])(3) -}); +}); \ No newline at end of file