From 25137da9a841f612652ded00de4a39a7a8755ff1 Mon Sep 17 00:00:00 2001 From: syerrapragada Date: Wed, 31 Oct 2018 17:38:28 -0700 Subject: [PATCH 1/5] Fix media section sdp for rejected datachannels --- rtcpeerconnection.js | 113 +++++++++++++++++++------------------- test/rtcpeerconnection.js | 41 ++++++++++++++ writemediasection.js | 30 +++++++++- 3 files changed, 127 insertions(+), 57 deletions(-) diff --git a/rtcpeerconnection.js b/rtcpeerconnection.js index a29bb18..19865a0 100644 --- a/rtcpeerconnection.js +++ b/rtcpeerconnection.js @@ -13,7 +13,9 @@ var shimIceGatherer = require('./rtcicegatherer'); var shimIceTransport = require('./rtcicetransport'); var shimDtlsTransport = require('./rtcdtlstransport'); var getCommonCapabilities = require('./getcommoncapabilities'); -var writeMediaSection = require('./writemediasection'); +var writeMediaSection = require('./writemediasection').writeMediaSection; +var writeRejectedMediaSection = + require('./writemediasection').writeRejectedMediaSection; var util = require('./util'); module.exports = function(window, edgeVersion) { @@ -168,9 +170,9 @@ module.exports = function(window, edgeVersion) { return transceiver; }; - RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, + RTCPeerConnection.prototype._createIceGatherer = function(transceiver, usingBundle) { - if (usingBundle && sdpMLineIndex > 0) { + if (usingBundle && transceiver.sdpMLineIndex > 0) { return this._transceivers[0].iceGatherer; } else if (this._iceGatherers.length) { return this._iceGatherers.shift(); @@ -183,9 +185,11 @@ module.exports = function(window, edgeVersion) { }; // start gathering from an RTCIceGatherer. - RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { + RTCPeerConnection.prototype._gather = function(transceiver) { var pc = this; - var iceGatherer = this._transceivers[sdpMLineIndex].iceGatherer; + var mid = transceiver.mid; + var sdpMLineIndex = transceiver.sdpMLineIndex; + var iceGatherer = transceiver.iceGatherer; if (iceGatherer.onlocalcandidate) { return; } @@ -242,9 +246,8 @@ module.exports = function(window, edgeVersion) { pc._dispatchEvent('icecandidate', event); } - var complete = pc._transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'complete'; + var complete = pc._transceivers.every(function(t) { + return t.iceGatherer && t.iceGatherer.state === 'complete'; }); if (complete) { pc._updateIceGatheringState('complete'); @@ -294,15 +297,15 @@ module.exports = function(window, edgeVersion) { // Destroy ICE gatherer, ICE transport and DTLS transport. // Without triggering the callbacks. RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( - sdpMLineIndex) { - var iceGatherer = this._transceivers[sdpMLineIndex].iceGatherer; + transceiver) { + var iceGatherer = transceiver.iceGatherer; if (iceGatherer) { delete iceGatherer.onlocalcandidate; - delete this._transceivers[sdpMLineIndex].iceGatherer; + delete transceiver.iceGatherer; } - delete this._transceivers[sdpMLineIndex].iceTransport; + delete transceiver.iceTransport; - delete this._transceivers[sdpMLineIndex].dtlsTransport; + delete transceiver.dtlsTransport; }; // Start the RTP Sender and Receiver for a transceiver. @@ -403,7 +406,9 @@ module.exports = function(window, edgeVersion) { failed: 0 }; this._transceivers.forEach(function(transceiver) { - states[transceiver.iceTransport.state]++; + if (!transceiver.rejected) { + states[transceiver.iceTransport.state]++; + } }); newState = 'new'; @@ -441,8 +446,10 @@ module.exports = function(window, edgeVersion) { failed: 0 }; this._transceivers.forEach(function(transceiver) { - states[transceiver.iceTransport.state]++; - states[transceiver.dtlsTransport.state]++; + if (!transceiver.rejected) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + } }); // ICETransport.completed and connected are the same for this purpose. states.connected += states.completed; @@ -659,8 +666,8 @@ module.exports = function(window, edgeVersion) { transceiver.localCapabilities = caps; }); - pc._transceivers.forEach(function(transceiver, sdpMLineIndex) { - pc._gather(transceiver.mid, sdpMLineIndex); + pc._transceivers.forEach(function(transceiver) { + pc._gather(transceiver); }); } else if (description.type === 'answer') { sections = SDPUtils.splitSections(pc._remoteDescription.sdp); @@ -694,7 +701,7 @@ module.exports = function(window, edgeVersion) { } if (!pc._usingBundle || sdpMLineIndex === 0) { - pc._gather(transceiver.mid, sdpMLineIndex); + pc._gather(transceiver); if (iceTransport.state === 'new') { iceTransport.start(iceGatherer, remoteIceParameters, isIceLite ? 'controlling' : 'controlled'); @@ -887,7 +894,7 @@ 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 && transceiver) { - pc._disposeIceAndDtlsTransports(transceiver.sdpMLineIndex); + pc._disposeIceAndDtlsTransports(transceiver); // TODO: this needs to search for the transceiver with // sdpMLinexIndex 0, not the transceiver at [0] transceiver.iceGatherer = pc._transceivers[0].iceGatherer; @@ -907,7 +914,7 @@ module.exports = function(window, edgeVersion) { if (!transceiver.iceGatherer) { transceiver.iceGatherer = pc._createIceGatherer( - transceiver.sdpMLineIndex, usingBundle); + transceiver, usingBundle); } if (cands.length && transceiver.iceTransport.state === 'new') { @@ -1235,6 +1242,9 @@ module.exports = function(window, edgeVersion) { var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, pc._sdpSessionVersion++); pc._transceivers.forEach(function(transceiver, sdpMLineIndex) { + if (transceiver.rejected) { + return; + } // For each track, create an ice gatherer, ice transport, // dtls transport, potentially rtpsender and rtpreceiver. var track = transceiver.track; @@ -1242,11 +1252,14 @@ module.exports = function(window, edgeVersion) { var mid = transceiver.mid || SDPUtils.generateIdentifier(); transceiver.mid = mid; if (transceiver.sdpMLineIndex === undefined) { - transceiver.sdpMLineIndex = sdpMLineIndex; + transceiver.sdpMLineIndex = pc._transceivers.reduce(function(max, t) { + return t.sdpMLineIndex !== undefined && + t.sdpMLineIndex >= max ? t.sdpMLineIndex + 1 : max; + }, 0); } if (!transceiver.iceGatherer) { - transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + transceiver.iceGatherer = pc._createIceGatherer(transceiver, pc._usingBundle); } @@ -1322,20 +1335,25 @@ module.exports = function(window, edgeVersion) { var mediaSections = []; pc._transceivers.forEach(function(transceiver, sdpMLineIndex) { - var mediaSection = writeMediaSection(transceiver, - transceiver.localCapabilities, 'offer', transceiver.stream, - pc._dtlsRole); - mediaSection += 'a=rtcp-rsize\r\n'; - - if (transceiver.iceGatherer && pc._iceGatheringState !== 'new' && - (sdpMLineIndex === 0 || !pc._usingBundle)) { - transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { - cand.component = 1; - mediaSection += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; - }); + var mediaSection = ''; + if (transceiver.rejected) { + mediaSection = writeRejectedMediaSection(transceiver); + } else { + mediaSection = writeMediaSection(transceiver, + transceiver.localCapabilities, 'offer', transceiver.stream, + pc._dtlsRole); + mediaSection += 'a=rtcp-rsize\r\n'; - if (transceiver.iceGatherer.state === 'complete') { - mediaSection += 'a=end-of-candidates\r\n'; + if (transceiver.iceGatherer && pc._iceGatheringState !== 'new' && + (sdpMLineIndex === 0 || !pc._usingBundle)) { + transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { + cand.component = 1; + mediaSection += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; + }); + + if (transceiver.iceGatherer.state === 'complete') { + mediaSection += 'a=end-of-candidates\r\n'; + } } } mediaSections[transceiver.sdpMLineIndex] = mediaSection; @@ -1380,23 +1398,7 @@ module.exports = function(window, edgeVersion) { var sdpMLineIndex = transceiver.sdpMLineIndex; var mediaSection = ''; if (transceiver.rejected) { - if (transceiver.kind === 'application') { - if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt - mediaSection += 'm=application 0 DTLS/SCTP 5000\r\n'; - } else { - mediaSection += 'm=application 0 ' + transceiver.protocol + - ' webrtc-datachannel\r\n'; - } - } else if (transceiver.kind === 'audio') { - mediaSection += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + - 'a=rtpmap:0 PCMU/8000\r\n'; - } else if (transceiver.kind === 'video') { - mediaSection += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + - 'a=rtpmap:120 VP8/90000\r\n'; - } - mediaSection += 'c=IN IP4 0.0.0.0\r\n' + - 'a=inactive\r\n' + - 'a=mid:' + transceiver.mid + '\r\n'; + mediaSection = writeRejectedMediaSection(transceiver); mediaSections[sdpMLineIndex] = mediaSection; return; } @@ -1469,7 +1471,8 @@ module.exports = function(window, edgeVersion) { } pc._transceivers[j].iceTransport.addRemoteCandidate({}); sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); - sections[j] += 'a=end-of-candidates\r\n'; + sections[pc._transceivers[j].sdpMLineIndex] += + 'a=end-of-candidates\r\n'; pc._remoteDescription.sdp = SDPUtils.getDescription(pc._remoteDescription.sdp) + sections.join(''); @@ -1482,7 +1485,7 @@ module.exports = function(window, edgeVersion) { if (candidate.sdpMid) { for (var i = 0; i < pc._transceivers.length; i++) { if (pc._transceivers[i].mid === candidate.sdpMid) { - sdpMLineIndex = i; + sdpMLineIndex = pc._transceivers[i].sdpMLineIndex; break; } } diff --git a/test/rtcpeerconnection.js b/test/rtcpeerconnection.js index 1983fcc..fe34d2f 100644 --- a/test/rtcpeerconnection.js +++ b/test/rtcpeerconnection.js @@ -1893,6 +1893,47 @@ describe('Edge shim', () => { }); }); + describe('when called after rejecting a', () => { + const legacy = SDP_BOILERPLATE + + 'm=application 9 DTLS/SCTP 5000\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:' + ICEUFRAG + '\r\n' + + 'a=ice-pwd:' + ICEPWD + '\r\n' + + 'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:data\r\n' + + 'a=sctpmap:5000 webrtc-datachannel 1024\r\n'; + const newStyle = SDP_BOILERPLATE + + 'm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:' + ICEUFRAG + '\r\n' + + 'a=ice-pwd:' + ICEPWD + '\r\n' + + 'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:data\r\n' + + 'a=sctp-port:5000\r\n' + + 'a=max-message-size:1073741823\r\n'; + new Map([['legacy', legacy],['new-style', newStyle]]).forEach( + (sdp, style) => { + it(`"${style}" datachannel offer`, () => { + return navigator.mediaDevices.getUserMedia({audio: true}) + .then((stream) => { + pc.addTrack(stream.getTracks()[0], stream); + return pc.setRemoteDescription({type: 'offer', sdp: sdp}); + }) + .then(() => pc.createAnswer()) + .then((answer) => pc.setLocalDescription(answer)) + .then(() => pc.createOffer()) + .then((offer) => { + const sections = SDPUtils.getMediaSections(offer.sdp); + expect(sections).to.have.length(2); + expect(SDPUtils.isRejected(sections[0])).to.equal(true); + expect(SDPUtils.getKind(sections[1])).to.equal('audio'); + }); + }); + }); + }); + describe('after replaceTrack', () => { it('retains the original track id', () => { return navigator.mediaDevices.getUserMedia({audio: true}) diff --git a/writemediasection.js b/writemediasection.js index f443e8d..28aa782 100644 --- a/writemediasection.js +++ b/writemediasection.js @@ -9,7 +9,7 @@ var SDPUtils = require('sdp'); /* generates a m= SDP from a transceiver. */ -module.exports = function(transceiver, caps, type, stream, dtlsRole) { +function writeMediaSection(transceiver, caps, type, stream, dtlsRole) { var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); // Map ICE parameters (ufrag, pwd) to SDP. @@ -63,4 +63,30 @@ module.exports = function(transceiver, caps, type, stream, dtlsRole) { ' cname:' + SDPUtils.localCName + '\r\n'; } return sdp; -}; +} + +/* generates a m= SDP from a rejected transceiver. */ +function writeRejectedMediaSection(transceiver) { + var sdp = ''; + if (transceiver.kind === 'application') { + if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt + sdp += 'm=application 0 DTLS/SCTP 5000\r\n'; + } else { + sdp += 'm=application 0 ' + transceiver.protocol + + ' webrtc-datachannel\r\n'; + } + } else if (transceiver.kind === 'audio') { + sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n'; + } else if (transceiver.kind === 'video') { + sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + + 'a=rtpmap:120 VP8/90000\r\n'; + } + sdp += 'c=IN IP4 0.0.0.0\r\n' + + 'a=inactive\r\n' + + 'a=mid:' + transceiver.mid + '\r\n'; + return sdp; +} + +exports.writeMediaSection = writeMediaSection; +exports.writeRejectedMediaSection = writeRejectedMediaSection; From bd152cc5651dcee0035143b1d8343806c294f298 Mon Sep 17 00:00:00 2001 From: syerrapragada Date: Thu, 1 Nov 2018 15:11:05 -0700 Subject: [PATCH 2/5] More sdpMLineIndex fixes --- rtcpeerconnection.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rtcpeerconnection.js b/rtcpeerconnection.js index 19865a0..8d9621b 100644 --- a/rtcpeerconnection.js +++ b/rtcpeerconnection.js @@ -1241,10 +1241,7 @@ module.exports = function(window, edgeVersion) { var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, pc._sdpSessionVersion++); - pc._transceivers.forEach(function(transceiver, sdpMLineIndex) { - if (transceiver.rejected) { - return; - } + pc._transceivers.forEach(function(transceiver) { // For each track, create an ice gatherer, ice transport, // dtls transport, potentially rtpsender and rtpreceiver. var track = transceiver.track; @@ -1263,6 +1260,10 @@ module.exports = function(window, edgeVersion) { pc._usingBundle); } + if (transceiver.rejected) { + return; + } + var localCapabilities = window.RTCRtpSender.getCapabilities(kind); // filter RTX until additional stuff needed for RTX is implemented // in adapter.js @@ -1304,7 +1305,7 @@ module.exports = function(window, edgeVersion) { // generate an ssrc now, to be used later in rtpSender.send var sendEncodingParameters = transceiver.sendEncodingParameters || [{ - ssrc: (2 * sdpMLineIndex + 1) * 1001 + ssrc: (2 * transceiver.sdpMLineIndex + 1) * 1001 }]; if (track) { // add RTX @@ -1334,7 +1335,7 @@ module.exports = function(window, edgeVersion) { sdp += 'a=ice-options:trickle\r\n'; var mediaSections = []; - pc._transceivers.forEach(function(transceiver, sdpMLineIndex) { + pc._transceivers.forEach(function(transceiver) { var mediaSection = ''; if (transceiver.rejected) { mediaSection = writeRejectedMediaSection(transceiver); @@ -1345,7 +1346,7 @@ module.exports = function(window, edgeVersion) { mediaSection += 'a=rtcp-rsize\r\n'; if (transceiver.iceGatherer && pc._iceGatheringState !== 'new' && - (sdpMLineIndex === 0 || !pc._usingBundle)) { + (transceiver.sdpMLineIndex === 0 || !pc._usingBundle)) { transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { cand.component = 1; mediaSection += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; From 0d56dd49667f771544f4631438a8cfb6f7686f1b Mon Sep 17 00:00:00 2001 From: Sreeram Yerrapragada Date: Fri, 2 Nov 2018 11:08:49 -0700 Subject: [PATCH 3/5] Address self review comments --- rtcpeerconnection.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rtcpeerconnection.js b/rtcpeerconnection.js index 8d9621b..6a8ee2c 100644 --- a/rtcpeerconnection.js +++ b/rtcpeerconnection.js @@ -666,9 +666,7 @@ module.exports = function(window, edgeVersion) { transceiver.localCapabilities = caps; }); - pc._transceivers.forEach(function(transceiver) { - pc._gather(transceiver); - }); + pc._transceivers.forEach(pc._gather, pc); } else if (description.type === 'answer') { sections = SDPUtils.splitSections(pc._remoteDescription.sdp); sessionpart = sections.shift(); From 7369d69d5f0e1967a47c00dfdccb8d5ae85546e4 Mon Sep 17 00:00:00 2001 From: Sreeram Yerrapragada Date: Fri, 2 Nov 2018 12:37:23 -0700 Subject: [PATCH 4/5] Partial rollback support --- rtcpeerconnection.js | 160 ++++++++++++++++++++++---------------- test/rtcpeerconnection.js | 87 +++++++++++++++++++++ util.js | 3 + 3 files changed, 185 insertions(+), 65 deletions(-) diff --git a/rtcpeerconnection.js b/rtcpeerconnection.js index 6a8ee2c..30053f9 100644 --- a/rtcpeerconnection.js +++ b/rtcpeerconnection.js @@ -46,6 +46,7 @@ module.exports = function(window, edgeVersion) { this._canTrickleIceCandidates = null; this._localDescription = null; + this._previousLocalDescription = null; this._remoteDescription = null; this._signalingState = 'stable'; this._iceConnectionState = 'new'; @@ -55,6 +56,7 @@ module.exports = function(window, edgeVersion) { // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... // everything that is needed to describe a SDP m-line. this._transceivers = []; + this._negotiationNeededTracks = []; this._sdpSessionId = SDPUtils.generateSessionId(); this._sdpSessionVersion = 0; @@ -144,6 +146,7 @@ module.exports = function(window, edgeVersion) { iceGatherer: null, iceTransport: null, dtlsTransport: null, + previousLocalCapabilities: null, localCapabilities: null, remoteCapabilities: null, rtpSender: null, @@ -541,6 +544,9 @@ module.exports = function(window, edgeVersion) { transceiver.track = track; transceiver.stream = stream; transceiver.rtpSender = new window.RTCRtpSender(track); + + this._negotiationNeededTracks.push(track); + return transceiver.rtpSender; }; @@ -639,7 +645,7 @@ module.exports = function(window, edgeVersion) { var pc = this; // Note: pranswer is not supported. - if (['offer', 'answer'].indexOf(description.type) === -1) { + if (['offer', 'answer', 'rollback'].indexOf(description.type) === -1) { return Promise.reject(util.makeError('TypeError', 'Unsupported type "' + description.type + '"')); } @@ -653,79 +659,98 @@ module.exports = function(window, edgeVersion) { var sections; var sessionpart; - if (description.type === 'offer') { - // VERY limited support for SDP munging. Limited to: - // * changing the order of codecs - sections = SDPUtils.splitSections(description.sdp); - sessionpart = sections.shift(); - sections.forEach(function(mediaSection, sdpMLineIndex) { - var caps = SDPUtils.parseRtpParameters(mediaSection); - var transceiver = pc._transceivers.find(function(t) { - return t.mid === SDPUtils.getMid(mediaSection); - }); - transceiver.localCapabilities = caps; - }); - pc._transceivers.forEach(pc._gather, pc); - } else if (description.type === 'answer') { - sections = SDPUtils.splitSections(pc._remoteDescription.sdp); - sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - sections.forEach(function(mediaSection, sdpMLineIndex) { - var transceiver = pc._transceivers.find(function(t) { - return t.mid === SDPUtils.getMid(mediaSection); + if (description.type === 'rollback') { + pc._localDescription = pc._previousLocalDescription; + pc._previousLocalDescription = null; + pc._transceivers.forEach(function(transceiver) { + transceiver.localCapabilities = transceiver.previousLocalCapabilities; + transceiver.previousLocalCapabilities = null; + if (!pc._localDescription) { + transceiver.iceGatherer.onlocalcandidate = null; + } + if (pc._negotiationNeededTracks.includes(transceiver.track) + && !transceiver.remoteCapabilities) { + transceiver.mid = null; + delete transceiver.sdpMLineIndex; + transceiver.sendEncodingParameters = null; + } + }); + } else { + if (description.type === 'offer') { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + var transceiver = pc._transceivers.find(function(t) { + return t.mid === SDPUtils.getMid(mediaSection); + }); + transceiver.localCapabilities = caps; }); - var iceGatherer = transceiver.iceGatherer; - var iceTransport = transceiver.iceTransport; - var dtlsTransport = transceiver.dtlsTransport; - var localCapabilities = transceiver.localCapabilities; - var remoteCapabilities = transceiver.remoteCapabilities; - - // treat bundle-only as not-rejected. - var rejected = SDPUtils.isRejected(mediaSection) && - SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; - if (!rejected && !transceiver.rejected) { - if (transceiver.rtpSender && !transceiver.rtpSender.transport) { - transceiver.rtpSender.setTransport(transceiver.dtlsTransport); - } - var remoteIceParameters = SDPUtils.getIceParameters( - mediaSection, sessionpart); - var remoteDtlsParameters = SDPUtils.getDtlsParameters( - mediaSection, sessionpart); - if (isIceLite) { - remoteDtlsParameters.role = 'server'; - } + pc._transceivers.forEach(pc._gather, pc); + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(pc._remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = pc._transceivers.find(function(t) { + return t.mid === SDPUtils.getMid(mediaSection); + }); + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; - if (!pc._usingBundle || sdpMLineIndex === 0) { - pc._gather(transceiver); - if (iceTransport.state === 'new') { - iceTransport.start(iceGatherer, remoteIceParameters, - isIceLite ? 'controlling' : 'controlled'); + if (!rejected && !transceiver.rejected) { + if (transceiver.rtpSender && !transceiver.rtpSender.transport) { + transceiver.rtpSender.setTransport(transceiver.dtlsTransport); } - if (dtlsTransport.state === 'new') { - dtlsTransport.start(remoteDtlsParameters); + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; } - } - // Calculate intersection of capabilities. - var params = getCommonCapabilities(localCapabilities, - remoteCapabilities); + if (!pc._usingBundle || sdpMLineIndex === 0) { + pc._gather(transceiver); + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } - // Start the RTCRtpSender. The RTCRtpReceiver for this - // transceiver has already been started in setRemoteDescription. - pc._transceive(transceiver, - params.codecs.length > 0, - false); - } - }); - } + // Calculate intersection of capabilities. + var params = getCommonCapabilities(localCapabilities, + remoteCapabilities); - pc._localDescription = { - type: description.type, - sdp: description.sdp - }; + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + pc._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + pc._localDescription = { + type: description.type, + sdp: description.sdp + }; + } if (description.type === 'offer') { pc._updateSignalingState('have-local-offer'); } else { @@ -751,6 +776,11 @@ module.exports = function(window, edgeVersion) { ' in state ' + pc._signalingState)); } + // NOTE(syerrapragada): Mark all newly added tracks as negotiated + if (description.type === 'answer') { + pc._negotiationNeededTracks.splice(0, pc._negotiationNeededTracks.length); + } + // TODO: should be RTCError instead. But for that it would have to // give a line. And we need an RTCError shim. if (!SDPUtils.isValidSDP(description.sdp)) { diff --git a/test/rtcpeerconnection.js b/test/rtcpeerconnection.js index fe34d2f..29b99d8 100644 --- a/test/rtcpeerconnection.js +++ b/test/rtcpeerconnection.js @@ -3884,4 +3884,91 @@ describe('Edge shim', () => { }); }); }); + + describe('Partial rollback,', () => { + describe('RTCPeerConnections 1 and 2 call createOffer, ' + + 'and RTCPeerConnection 1 calls setLocalDescription; then',() => { + let pc1; + let pc2; + beforeEach(() => { + pc1 = new RTCPeerConnection(); + pc2 = new RTCPeerConnection(); + pc1.addEventListener('icecandidate', + e => pc2.addIceCandidate(e.candidate)); + pc2.addEventListener('icecandidate', + e => pc1.addIceCandidate(e.candidate)); + return navigator.mediaDevices.getUserMedia({audio: true}) + .then((stream) => { + pc1.addTrack(stream.getTracks()[0], stream); + return navigator.mediaDevices.getUserMedia({audio: true}); + }) + .then((stream) => { + pc2.addTrack(stream.getTracks()[0], stream); + return pc1.createOffer(); + }) + .then((offer) => pc1.setLocalDescription(offer) + .then(() => pc2.setRemoteDescription(offer))) + .then(() => pc2.createAnswer()) + .then((answer) => pc2.setLocalDescription(answer) + .then(() => pc1.setRemoteDescription(answer))) + .then(() => { + expect((pc1.localDescription.sdp.match(/m=audio/g) + || []).length).equal(1); + expect((pc2.localDescription.sdp.match(/m=audio/g) + || []).length).equal(1); + }) + .then(() => navigator.mediaDevices.getUserMedia({video: true})) + .then((stream) => { + pc1.addTrack(stream.getTracks()[0], stream); + return pc1.createOffer(); + }) + .then((offer) => { + pc1.setLocalDescription(offer); + expect((pc1.localDescription.sdp.match(/m=audio/g) + || []).length).equal(1); + expect((pc1.localDescription.sdp.match(/m=video/g) + || []).length).equal(1); + }); + }); + describe('RTCPeerConnection 1 rolls back and ' + + 'calls setRemoteDescription; then', () => { + beforeEach(() => { + return navigator.mediaDevices.getUserMedia({audio: true}) + .then((stream) => { + pc2.addTrack(stream.getTracks()[0], stream); + return pc2.createOffer(); + }) + .then((offer) => pc2.setLocalDescription(offer) + .then(() => pc1.setLocalDescription({type: 'rollback'}) + .then(() => pc1.setRemoteDescription(offer)))); + }); + describe('RTCPeerConnection 1 calls createAnswer ' + + 'and setLocalDescription; then', () => { + beforeEach(() => { + return pc1.createAnswer() + .then((answer) => pc1.setLocalDescription(answer) + .then(() => pc2.setRemoteDescription(answer))); + }); + it('RTCPeerConnection 1 calls createOffer and negotiate', () => { + return pc1.createOffer() + .then((offer) => pc1.setLocalDescription(offer) + .then(() => pc2.setRemoteDescription(offer))) + .then(() => pc2.createAnswer()) + .then((answer) => pc2.setLocalDescription(answer) + .then(() => pc1.setRemoteDescription(answer))) + .then(() => { + expect((pc1.localDescription.sdp.match(/m=audio/g) + || []).length).equal(2); + expect((pc1.localDescription.sdp.match(/m=video/g) + || []).length).equal(1); + expect((pc2.localDescription.sdp.match(/m=audio/g) + || []).length).equal(2); + expect((pc2.localDescription.sdp.match(/m=video/g) + || []).length).equal(1); + }); + }); + }); + }); + }); + }); }); diff --git a/util.js b/util.js index 023e959..56e9967 100644 --- a/util.js +++ b/util.js @@ -167,6 +167,9 @@ module.exports = { answer: { setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] + }, + rollback: { + setLocalDescription: ['have-local-offer'] } }[type][action].indexOf(signalingState) !== -1; }, From d1491e6571418ee1eba318bfb0b367f4719665c4 Mon Sep 17 00:00:00 2001 From: Sreeram Yerrapragada Date: Mon, 5 Nov 2018 13:52:23 -0800 Subject: [PATCH 5/5] Avoid using array for tracking negotiation needed tracks --- rtcpeerconnection.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/rtcpeerconnection.js b/rtcpeerconnection.js index 30053f9..1848710 100644 --- a/rtcpeerconnection.js +++ b/rtcpeerconnection.js @@ -56,7 +56,6 @@ module.exports = function(window, edgeVersion) { // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... // everything that is needed to describe a SDP m-line. this._transceivers = []; - this._negotiationNeededTracks = []; this._sdpSessionId = SDPUtils.generateSessionId(); this._sdpSessionVersion = 0; @@ -544,8 +543,7 @@ module.exports = function(window, edgeVersion) { transceiver.track = track; transceiver.stream = stream; transceiver.rtpSender = new window.RTCRtpSender(track); - - this._negotiationNeededTracks.push(track); + transceiver.isNegotiated = false; return transceiver.rtpSender; }; @@ -669,8 +667,8 @@ module.exports = function(window, edgeVersion) { if (!pc._localDescription) { transceiver.iceGatherer.onlocalcandidate = null; } - if (pc._negotiationNeededTracks.includes(transceiver.track) - && !transceiver.remoteCapabilities) { + if ('isNegotiated' in transceiver + && !!transceiver.isNegotiated === false) { transceiver.mid = null; delete transceiver.sdpMLineIndex; transceiver.sendEncodingParameters = null; @@ -687,6 +685,7 @@ module.exports = function(window, edgeVersion) { var transceiver = pc._transceivers.find(function(t) { return t.mid === SDPUtils.getMid(mediaSection); }); + transceiver.previousLocalCapabilities = transceiver.localCapabilities; transceiver.localCapabilities = caps; }); @@ -746,6 +745,7 @@ module.exports = function(window, edgeVersion) { }); } + pc._previousLocalDescription = pc._localDescription; pc._localDescription = { type: description.type, sdp: description.sdp @@ -776,11 +776,6 @@ module.exports = function(window, edgeVersion) { ' in state ' + pc._signalingState)); } - // NOTE(syerrapragada): Mark all newly added tracks as negotiated - if (description.type === 'answer') { - pc._negotiationNeededTracks.splice(0, pc._negotiationNeededTracks.length); - } - // TODO: should be RTCError instead. But for that it would have to // give a line. And we need an RTCError shim. if (!SDPUtils.isValidSDP(description.sdp)) { @@ -1108,6 +1103,11 @@ module.exports = function(window, edgeVersion) { // FIXME: actually the receiver should be created later. delete transceiver.rtpReceiver; } + + // NOTE(syerrapragada): Mark negotiated + if (!!transceiver.isNegotiated === false) { + transceiver.isNegotiated = true; + } } });