diff --git a/lib/connect.js b/lib/connect.js index d5e048ad3..d27e6f912 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -1,6 +1,5 @@ 'use strict'; -const { MediaStreamTrack } = require('./webrtc'); const { guessBrowser, guessBrowserVersion } = require('./webrtc/util'); const createCancelableRoomPromise = require('./cancelableroompromise'); const EncodingParametersImpl = require('./encodingparameters'); diff --git a/lib/createlocaltracks.ts b/lib/createlocaltracks.ts index 2dd432156..91b52f968 100644 --- a/lib/createlocaltracks.ts +++ b/lib/createlocaltracks.ts @@ -13,7 +13,7 @@ import { import { applyNoiseCancellation } from './media/track/noisecancellationimpl'; const { buildLogLevels } = require('./util'); -const { getUserMedia, MediaStreamTrack } = require('./webrtc'); +const { getUserMedia } = require('./webrtc'); const { LocalAudioTrack, diff --git a/lib/localparticipant.js b/lib/localparticipant.js index 7057eb5a7..a9103e67c 100644 --- a/lib/localparticipant.js +++ b/lib/localparticipant.js @@ -1,6 +1,5 @@ 'use strict'; -const { MediaStreamTrack } = require('./webrtc'); const { asLocalTrack, asLocalTrackPublication, trackClass } = require('./util'); const { typeErrors: E, trackPriority } = require('./util/constants'); const { validateLocalTrack } = require('./util/validate'); diff --git a/lib/media/track/mediatrack.js b/lib/media/track/mediatrack.js index 02328c1a0..4e31a46c7 100644 --- a/lib/media/track/mediatrack.js +++ b/lib/media/track/mediatrack.js @@ -1,8 +1,6 @@ 'use strict'; const { isIOS } = require('../../util/browserdetection'); -const { MediaStream } = require('../../webrtc'); - const { waitForEvent, waitForSometime } = require('../../util'); const localMediaRestartDeferreds = require('../../util/localmediarestartdeferreds'); const Track = require('./'); diff --git a/lib/signaling/v2/peerconnection.js b/lib/signaling/v2/peerconnection.js index 6b1e62e08..db101bb8b 100644 --- a/lib/signaling/v2/peerconnection.js +++ b/lib/signaling/v2/peerconnection.js @@ -1,10 +1,9 @@ 'use strict'; const DefaultBackoff = require('../../util/backoff'); + const { - RTCIceCandidate: DefaultRTCIceCandidate, RTCPeerConnection: DefaultRTCPeerConnection, - RTCSessionDescription: DefaultRTCSessionDescription, getStats: getStatistics } = require('../../webrtc'); @@ -128,9 +127,9 @@ class PeerConnectionV2 extends StateMachine { setSimulcast, Backoff: DefaultBackoff, IceConnectionMonitor: DefaultIceConnectionMonitor, - RTCIceCandidate: DefaultRTCIceCandidate, + RTCIceCandidate, RTCPeerConnection: DefaultRTCPeerConnection, - RTCSessionDescription: DefaultRTCSessionDescription, + RTCSessionDescription, Timeout: DefaultTimeout }, options); diff --git a/lib/util/sdp/issue8329.js b/lib/util/sdp/issue8329.js index e661d97ec..670a0fee0 100644 --- a/lib/util/sdp/issue8329.js +++ b/lib/util/sdp/issue8329.js @@ -1,7 +1,5 @@ 'use strict'; -const { RTCSessionDescription } = require('../../webrtc'); - const { createPtToCodecName, getMediaSections } = require('./'); /** diff --git a/lib/util/support.js b/lib/util/support.js index 701e53411..57c456298 100644 --- a/lib/util/support.js +++ b/lib/util/support.js @@ -1,7 +1,8 @@ +/* globals RTCRtpTransceiver */ + 'use strict'; const { guessBrowser, support: isWebRTCSupported } = require('../webrtc/util'); -const { getSdpFormat } = require('../webrtc/util/sdp'); const { isAndroid, isMobile, isNonChromiumEdge, rebrandedChromeBrowser, mobileWebKitBrowser } = require('./browserdetection'); const SUPPORTED_CHROME_BASED_BROWSERS = [ @@ -22,11 +23,56 @@ const SUPPORTED_IOS_BROWSERS = [ // Currently none. Add 'brave', 'edg', and 'edge' here once we start supporting them const SUPPORTED_MOBILE_WEBKIT_BASED_BROWSERS = []; +// NOTE(mmalavalli): We cache Chrome's unified plan support in order +// to prevent instantiation of more than one RTCPeerConnection. +let isChromeUnifiedPlanSupported; + +/** + * Check whether the current browser supports unified plan sdps. + * @param {boolean} [shouldCacheResultForChrome=true] - For unit tests only + */ +function isUnifiedPlanSupported(shouldCacheResultForChrome = true) { + const maybeCacheResultForChrome = shouldCacheResultForChrome + ? result => { isChromeUnifiedPlanSupported = result; } + : () => { /* Do not cache result for Chrome */ }; + + let isSupported; + + return ({ + chrome: () => { + if (typeof isChromeUnifiedPlanSupported === 'boolean') { + return isChromeUnifiedPlanSupported; + } + if (typeof RTCPeerConnection === 'undefined' || !('addTransceiver' in RTCPeerConnection.prototype)) { + maybeCacheResultForChrome(isSupported = false); + return isSupported; + } + const peerConnection = new RTCPeerConnection(); + try { + peerConnection.addTransceiver('audio'); + maybeCacheResultForChrome(isSupported = true); + } catch (e) { + maybeCacheResultForChrome(isSupported = false); + } + peerConnection.close(); + return isSupported; + }, + firefox: () => { + return true; + }, + safari: () => { + return typeof RTCRtpTransceiver !== 'undefined' + && 'currentDirection' in RTCRtpTransceiver.prototype; + } + }[guessBrowser()] || (() => false))(); +} + /** * Check if the current browser is officially supported by twilio-video.js. + @param {boolean} [shouldCacheUnifiedPlanSupportForChrome=true] - For unit tests only * @returns {boolean} */ -function isSupported() { +function isSupported(shouldCacheUnifiedPlanSupportForChrome = true) { const browser = guessBrowser(); // NOTE (csantos): Return right away if there is no browser detected @@ -42,7 +88,7 @@ function isSupported() { return !!browser && isWebRTCSupported() - && getSdpFormat() === 'unified' + && isUnifiedPlanSupported(shouldCacheUnifiedPlanSupportForChrome) && (!rebrandedChrome || SUPPORTED_CHROME_BASED_BROWSERS.includes(rebrandedChrome)) && !isNonChromiumEdge(browser) && (!mobileWebKit || SUPPORTED_MOBILE_WEBKIT_BASED_BROWSERS.includes(mobileWebKit)) diff --git a/lib/webrtc/getstats.js b/lib/webrtc/getstats.js index 453ba6572..5e1137691 100644 --- a/lib/webrtc/getstats.js +++ b/lib/webrtc/getstats.js @@ -1,7 +1,6 @@ 'use strict'; const { flatMap, guessBrowser, guessBrowserVersion } = require('./util'); -const { getSdpFormat } = require('./util/sdp'); const guess = guessBrowser(); const guessVersion = guessBrowserVersion(); @@ -277,14 +276,9 @@ function standardizeFirefoxActiveIceCandidatePairStats(stats) { */ function getTracks(peerConnection, kind, localOrRemote) { const getSendersOrReceivers = localOrRemote === 'local' ? 'getSenders' : 'getReceivers'; - if (peerConnection[getSendersOrReceivers]) { - return peerConnection[getSendersOrReceivers]() - .map(({ track }) => track) - .filter(track => track && track.kind === kind); - } - const getStreams = localOrRemote === 'local' ? 'getLocalStreams' : 'getRemoteStreams'; - const getTracks = kind === 'audio' ? 'getAudioTracks' : 'getVideoTracks'; - return flatMap(peerConnection[getStreams](), stream => stream[getTracks]()); + return peerConnection[getSendersOrReceivers]() + .map(({ track }) => track) + .filter(track => track && track.kind === kind); } /** @@ -298,20 +292,11 @@ function getTrackStats(peerConnection, track, options = {}) { if (typeof options.testForChrome !== 'undefined' || isChrome) { return chromeOrSafariGetTrackStats(peerConnection, track); } - if (typeof options.testForFirefox !== 'undefined' || isFirefox) { + if (typeof options.testForFirefox !== 'undefined' || isFirefox) { return firefoxGetTrackStats(peerConnection, track, options.isRemote); } - if (typeof options.testForSafari !== 'undefined' || isSafari) { - if (typeof options.testForSafari !== 'undefined' || getSdpFormat() === 'unified') { - return chromeOrSafariGetTrackStats(peerConnection, track); - } - // NOTE(syerrapragada): getStats() is not supported on - // Safari versions where plan-b is the SDP format - // due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601 - return Promise.reject(new Error([ - 'getStats() is not supported on this version of Safari', - 'due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601' - ].join(' '))); + if (typeof options.testForSafari !== 'undefined' || isSafari) { + return chromeOrSafariGetTrackStats(peerConnection, track); } return Promise.reject(new Error('RTCPeerConnection#getStats() not supported')); } diff --git a/lib/webrtc/index.js b/lib/webrtc/index.js index 95f04c73a..bec3e5993 100644 --- a/lib/webrtc/index.js +++ b/lib/webrtc/index.js @@ -11,25 +11,9 @@ Object.defineProperties(WebRTC, { enumerable: true, value: require('./getusermedia') }, - MediaStream: { - enumerable: true, - value: require('./mediastream') - }, - MediaStreamTrack: { - enumerable: true, - value: require('./mediastreamtrack') - }, - RTCIceCandidate: { - enumerable: true, - value: require('./rtcicecandidate') - }, RTCPeerConnection: { enumerable: true, value: require('./rtcpeerconnection') - }, - RTCSessionDescription: { - enumerable: true, - value: require('./rtcsessiondescription') } }); diff --git a/lib/webrtc/mediastream.js b/lib/webrtc/mediastream.js deleted file mode 100644 index 6be0f53ac..000000000 --- a/lib/webrtc/mediastream.js +++ /dev/null @@ -1,10 +0,0 @@ -/* globals MediaStream */ -'use strict'; - -if (typeof MediaStream === 'function') { - module.exports = MediaStream; -} else { - module.exports = function MediaStream() { - throw new Error('MediaStream is not supported'); - }; -} diff --git a/lib/webrtc/mediastreamtrack.js b/lib/webrtc/mediastreamtrack.js deleted file mode 100644 index 892c10bef..000000000 --- a/lib/webrtc/mediastreamtrack.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global MediaStreamTrack */ -'use strict'; - -if (typeof MediaStreamTrack === 'function') { - module.exports = MediaStreamTrack; -} else { - module.exports = function MediaStreamTrack() { - throw new Error('MediaStreamTrack is not supported'); - }; -} diff --git a/lib/webrtc/rtcicecandidate.js b/lib/webrtc/rtcicecandidate.js deleted file mode 100644 index b3a54e20c..000000000 --- a/lib/webrtc/rtcicecandidate.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global RTCIceCandidate */ -'use strict'; - -if (typeof RTCIceCandidate === 'function') { - module.exports = RTCIceCandidate; -} else { - module.exports = function RTCIceCandidate() { - throw new Error('RTCIceCandidate is not supported'); - }; -} diff --git a/lib/webrtc/rtcpeerconnection/chrome.js b/lib/webrtc/rtcpeerconnection/chrome.js index 97d2cf1af..387be2d87 100644 --- a/lib/webrtc/rtcpeerconnection/chrome.js +++ b/lib/webrtc/rtcpeerconnection/chrome.js @@ -1,343 +1,120 @@ -/* globals RTCDataChannel, RTCPeerConnection, RTCSessionDescription */ +/* globals RTCPeerConnection, RTCSessionDescription */ 'use strict'; -const ChromeRTCSessionDescription = require('../rtcsessiondescription/chrome'); const EventTarget = require('../../eventtarget'); -const Latch = require('../util/latch'); -const MediaStream = require('../mediastream'); -const RTCRtpSenderShim = require('../rtcrtpsender'); -const { getSdpFormat, updatePlanBTrackIdsToSSRCs, updateUnifiedPlanTrackIdsToSSRCs } = require('../util/sdp'); -const { delegateMethods, interceptEvent, isIOSChrome, legacyPromise, proxyProperties } = require('../util'); +const { updateTrackIdsToSSRCs } = require('../util/sdp'); +const { delegateMethods, interceptEvent, isIOSChrome, proxyProperties } = require('../util'); -const isUnifiedPlan = getSdpFormat() === 'unified'; - -// NOTE(mroberts): This class wraps Chrome's RTCPeerConnection implementation. -// It provides some functionality not currently present in Chrome, namely the -// abilities to -// -// 1. Rollback, per the workaround suggested here: -// https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3 -// -// 2. Listen for track events, per the adapter.js workaround. -// -// 3. Set iceTransportPolicy. -// class ChromeRTCPeerConnection extends EventTarget { constructor(configuration = {}, constraints) { super(); - const newConfiguration = Object.assign(configuration.iceTransportPolicy - ? { iceTransports: configuration.iceTransportPolicy } - : {}, configuration); - - interceptEvent(this, 'datachannel'); interceptEvent(this, 'signalingstatechange'); - const sdpFormat = getSdpFormat(newConfiguration.sdpSemantics); - const peerConnection = new RTCPeerConnection(newConfiguration, constraints); + const peerConnection = new RTCPeerConnection(configuration, constraints); Object.defineProperties(this, { _appliedTracksToSSRCs: { value: new Map(), writable: true }, - _localStream: { - value: new MediaStream() - }, _peerConnection: { value: peerConnection }, - _pendingLocalOffer: { - value: null, - writable: true - }, - _pendingRemoteOffer: { - value: null, - writable: true - }, _rolledBackTracksToSSRCs: { value: new Map(), writable: true }, - _sdpFormat: { - value: sdpFormat - }, _senders: { value: new Map() }, - _signalingStateLatch: { - value: new Latch() - }, _tracksToSSRCs: { value: new Map(), writable: true } }); - peerConnection.addEventListener('datachannel', event => { - shimDataChannel(event.channel); - this.dispatchEvent(event); - }); - peerConnection.addEventListener('signalingstatechange', (...args) => { if (peerConnection.signalingState === 'stable') { this._appliedTracksToSSRCs = new Map(this._tracksToSSRCs); } - if (!this._pendingLocalOffer && !this._pendingRemoteOffer) { - this.dispatchEvent(...args); - } + this.dispatchEvent(...args); }); - peerConnection.ontrack = () => { - // NOTE(mroberts): adapter.js's "track" event shim only kicks off if we set - // the ontrack property of the RTCPeerConnection. - }; - - if (typeof peerConnection.addTrack !== 'function') { - peerConnection.addStream(this._localStream); - } proxyProperties(RTCPeerConnection.prototype, this, peerConnection); } - get localDescription() { - return this._pendingLocalOffer ? this._pendingLocalOffer : this._peerConnection.localDescription; - } - - get remoteDescription() { - return this._pendingRemoteOffer ? this._pendingRemoteOffer : this._peerConnection.remoteDescription; - } - - get signalingState() { - if (this._pendingLocalOffer) { - return 'have-local-offer'; - } else if (this._pendingRemoteOffer) { - return 'have-remote-offer'; - } - return this._peerConnection.signalingState; - } - - // NOTE(mmalavalli): This shim supports our limited case of adding - // all MediaStreamTracks to one MediaStream. It has been implemented this - // keeping in mind that this is to be maintained only until "addTrack" is - // supported natively in Chrome. - addTrack(track, ...rest) { - if (typeof this._peerConnection.addTrack === 'function') { - return this._peerConnection.addTrack(track, ...rest); - } - if (this._peerConnection.signalingState === 'closed') { - throw new Error(`Cannot add MediaStreamTrack [${track.id}, - ${track.kind}]: RTCPeerConnection is closed`); - } - - let sender = this._senders.get(track); - if (sender && sender.track) { - throw new Error(`Cannot add MediaStreamTrack ['${track.id}, - ${track.kind}]: RTCPeerConnection already has it`); - } - this._peerConnection.removeStream(this._localStream); - this._localStream.addTrack(track); - this._peerConnection.addStream(this._localStream); - sender = new RTCRtpSenderShim(track); - this._senders.set(track, sender); - return sender; - } - - // NOTE(mmalavalli): This shim supports our limited case of removing - // MediaStreamTracks from one MediaStream. It has been implemented this - // keeping in mind that this is to be maintained only until "removeTrack" is - // supported natively in Chrome. - removeTrack(sender) { - if (this._peerConnection.signalingState === 'closed') { - throw new Error('Cannot remove MediaStreamTrack: RTCPeerConnection is closed'); - } - if (typeof this._peerConnection.addTrack === 'function') { - try { - return this._peerConnection.removeTrack(sender); - } catch (e) { - // NOTE(mhuynh): Do nothing. In Chrome, will throw if a 'sender was not - // created by this peer connection'. This behavior does not seem to be - // spec compliant, so a temporary shim is introduced. A bug has been filed, - // and is tracked here: - // https://bugs.chromium.org/p/chromium/issues/detail?id=860853 - } - } else { - const { track } = sender; - if (!track) { - return; - } - sender = this._senders.get(track); - if (sender && sender.track) { - sender.track = null; - this._peerConnection.removeStream(this._localStream); - this._localStream.removeTrack(track); - this._peerConnection.addStream(this._localStream); - } - } - } - - getSenders() { - if (typeof this._peerConnection.addTrack === 'function') { - return this._peerConnection.getSenders(); - } - return Array.from(this._senders.values()); - } - - addIceCandidate(candidate, ...rest) { - let promise; - - if (this.signalingState === 'have-remote-offer') { - // NOTE(mroberts): Because the ChromeRTCPeerConnection simulates the - // "have-remote-offer" signalingStates, we only want to invoke the true - // addIceCandidates method when the remote description has been applied. - promise = this._signalingStateLatch.when('low').then(() => - this._peerConnection.addIceCandidate(candidate)); - } else { - promise = this._peerConnection.addIceCandidate(candidate); - } - - return rest.length > 0 - ? legacyPromise(promise, ...rest) - : promise; - } - - // NOTE(mroberts): The WebRTC spec does not specify that close should throw an - // Error; however, in Chrome it does. We workaround this by checking the - // signalingState manually. - close() { - if (this.signalingState !== 'closed') { - this._pendingLocalOffer = null; - this._pendingRemoteOffer = null; - this._peerConnection.close(); - } - } - - // NOTE(mroberts): Because we workaround Chrome's lack of rollback support by - // "faking" setRemoteDescription, we cannot create an answer until we actually - // apply the remote description. This means, once you call createAnswer, you - // can no longer rollback. This is acceptable for our use case because we will - // apply the newly-created answer almost immediately; however, this may be - // unacceptable for other use cases. - createAnswer(...args) { - let promise; - - if (this._pendingRemoteOffer) { - promise = this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(() => { - // NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection - // and the underlying RTCPeerConnection implementation have converged. We - // can unblock any pending calls to addIceCandidate now. - this._signalingStateLatch.lower(); - return this._peerConnection.createAnswer(); - }).then(answer => { - this._pendingRemoteOffer = null; - - // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no - // longer need to retain the rolled back tracks to SSRCs Map. - this._rolledBackTracksToSSRCs.clear(); - - return new ChromeRTCSessionDescription({ - type: 'answer', - sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, answer.sdp) - }); - }, error => { - this._pendingRemoteOffer = null; - throw error; - }); - } else { - promise = this._peerConnection.createAnswer().then(answer => { - // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no - // longer need to retain the rolled back tracks to SSRCs Map. - this._rolledBackTracksToSSRCs.clear(); - - return new ChromeRTCSessionDescription({ - type: 'answer', - sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, answer.sdp) - }); - }); - } - - return args.length > 1 - ? legacyPromise(promise, ...args) - : promise; + createAnswer(options = {}) { + return createLocalDescription(this, options, 'answer'); } - createOffer(...args) { - const [arg1, arg2, arg3] = args; - const options = arg3 || arg1 || {}; - + createOffer(options = {}) { if (isIOSChrome()) { - // NOTE (joma): From SafariRTCPeerConnection in order to support iOS Chrome. - if (options.offerToReceiveVideo && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) { + // NOTE(joma): From SafariRTCPeerConnection in order to support iOS Chrome. + if (options.offerToReceiveVideo && !this._audioTransceiver && !hasReceiversForTracksOfKind(this, 'audio')) { delete options.offerToReceiveAudio; try { - this._audioTransceiver = isUnifiedPlan - ? this.addTransceiver('audio', { direction: 'recvonly' }) - : this.addTransceiver('audio'); + this._audioTransceiver = this.addTransceiver('audio', { direction: 'recvonly' }); } catch (e) { return Promise.reject(e); } } - if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) { + if (options.offerToReceiveVideo && !this._videoTransceiver && !hasReceiversForTracksOfKind(this, 'video')) { delete options.offerToReceiveVideo; try { - this._videoTransceiver = isUnifiedPlan - ? this.addTransceiver('video', { direction: 'recvonly' }) - : this.addTransceiver('video'); + this._videoTransceiver = this.addTransceiver('video', { direction: 'recvonly' }); } catch (e) { return Promise.reject(e); } } } - - const promise = this._peerConnection.createOffer(options).then(offer => { - // NOTE(mmalavalli): If createOffer() is called immediately after rolling back, then we no - // longer need to retain the rolled back tracks to SSRCs Map. - this._rolledBackTracksToSSRCs.clear(); - - return new ChromeRTCSessionDescription({ - type: offer.type, - sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, offer.sdp) - }); - }); - - return args.length > 1 - ? legacyPromise(promise, arg1, arg2) - : promise; + return createLocalDescription(this, options, 'offer'); } - createDataChannel(label, dataChannelDict) { - dataChannelDict = shimDataChannelInit(dataChannelDict); - const dataChannel = this._peerConnection.createDataChannel(label, dataChannelDict); - shimDataChannel(dataChannel); - return dataChannel; - } - - setLocalDescription(...args) { - const [description, arg1, arg2] = args; - - // NOTE(mmalavalli): If setLocalDescription() is called immediately after rolling back, - // then we need to restore the rolled back tracks to SSRCs Map. - if (this._rolledBackTracksToSSRCs.size > 0) { + setLocalDescription(description) { + if (description.type === 'rollback') { + // NOTE(mmalavalli): Since Chrome does not throw an exception when setLocalDescription() + // is called in the signaling state 'have-remote-offer', we do so here. This is done + // to preserve the legacy behavior which is consistent with Firefox and Safari. + if (this.signalingState === 'have-remote-offer') { + return Promise.reject(new DOMException('Failed to execute ' + + '\'setLocalDescription\' on \'RTCPeerConnection\': ' + + 'Called in wrong signalingState: ' + + this.signalingState, 'InvalidStateError')); + } + // NOTE(mmalavalli): We store the rolled back tracks to SSRCs Map here in case + // setLocalDescription() is called immediately after a rollback (without calling + // createOffer() or createAnswer()), in which case this roll back is not due to a + // glare scenario and this Map should be restored. + this._rolledBackTracksToSSRCs = new Map(this._tracksToSSRCs); + this._tracksToSSRCs = new Map(this._appliedTracksToSSRCs); + } else if (this._rolledBackTracksToSSRCs.size > 0) { + // NOTE(mmalavalli): If setLocalDescription() is called immediately after rolling back, + // then we need to restore the rolled back tracks to SSRCs Map. this._tracksToSSRCs = new Map(this._rolledBackTracksToSSRCs); this._rolledBackTracksToSSRCs.clear(); } - - const promise = setDescription(this, true, description); - return args.length > 1 - ? legacyPromise(promise, arg1, arg2) - : promise; + return this._peerConnection.setLocalDescription(description); } - setRemoteDescription(...args) { - const [description, arg1, arg2] = args; + setRemoteDescription(description) { + if (['offer', 'rollback'].includes(description.type)) { + // NOTE(mmalavalli): Since Chrome does not throw an exception when setLocalDescription() + // is called in the signaling state 'have-remote-offer', we do so here. This is done + // to preserve the legacy behavior which is consistent with Firefox and Safari + if (this.signalingState === 'have-local-offer') { + return Promise.reject(new DOMException('Failed to execute ' + + '\'setLocalDescription\' on \'RTCPeerConnection\': ' + + 'Called in wrong signalingState: ' + + this.signalingState, 'InvalidStateError')); + } + } // NOTE(mmalavalli): If setRemoteDescription() is called immediately after rolling back, // then we no longer need to retain the rolled back tracks to SSRCs Map. this._rolledBackTracksToSSRCs.clear(); - const promise = setDescription(this, false, description); - return args.length > 1 - ? legacyPromise(promise, arg1, arg2) - : promise; + return this._peerConnection.setRemoteDescription(description); } } @@ -346,93 +123,28 @@ delegateMethods( ChromeRTCPeerConnection.prototype, '_peerConnection'); -// NOTE(mroberts): We workaround Chrome's lack of rollback support, per the -// workaround suggested here: https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3 -// Namely, we "fake" setting the local or remote description and instead buffer -// it. If we receive or create an answer, then we will actually apply the -// description. Until we receive or create an answer, we will be able to -// "rollback" by simply discarding the buffer description. -function setDescription(peerConnection, local, description) { - function setPendingLocalOffer(offer) { - if (local) { - peerConnection._pendingLocalOffer = offer; - } else { - peerConnection._pendingRemoteOffer = offer; - } - } - - function clearPendingLocalOffer() { - if (local) { - peerConnection._pendingLocalOffer = null; - } else { - peerConnection._pendingRemoteOffer = null; - } - } - - const pendingLocalOffer = local ? peerConnection._pendingLocalOffer : peerConnection._pendingRemoteOffer; - const pendingRemoteOffer = local ? peerConnection._pendingRemoteOffer : peerConnection._pendingLocalOffer; - const intermediateState = local ? 'have-local-offer' : 'have-remote-offer'; - const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription'; - let promise; - - if (!local && pendingRemoteOffer && description.type === 'answer') { - promise = setRemoteAnswer(peerConnection, description); - } else if (description.type === 'offer') { - if (peerConnection.signalingState !== intermediateState && peerConnection.signalingState !== 'stable') { - // NOTE(mroberts): Error message copied from Firefox. - return Promise.reject(new Error(`Cannot set ${local ? 'local' : 'remote'} offer in state ${peerConnection.signalingState}`)); - } - - // We need to save this local offer in case of a rollback. We also need to - // check to see if the signalingState between the ChromeRTCPeerConnection - // and the underlying RTCPeerConnection implementation are about to diverge. - // If so, we need to ensure subsequent calls to addIceCandidate will block. - if (!pendingLocalOffer && peerConnection._signalingStateLatch.state === 'low') { - peerConnection._signalingStateLatch.raise(); - } - const previousSignalingState = peerConnection.signalingState; - setPendingLocalOffer(unwrap(description)); - promise = Promise.resolve(); - - // Only dispatch a signalingstatechange event if we transitioned. - if (peerConnection.signalingState !== previousSignalingState) { - promise.then(() => peerConnection.dispatchEvent(new Event('signalingstatechange'))); - } - - } else if (description.type === 'rollback') { - if (peerConnection.signalingState !== intermediateState) { - // NOTE(mroberts): Error message copied from Firefox. - promise = Promise.reject(new Error(`Cannot rollback ${local ? 'local' : 'remote'} description in ${peerConnection.signalingState}`)); - } else { - // Reset the pending offer. - clearPendingLocalOffer(); - - // NOTE(mmalavalli): We store the rolled back tracks to SSRCs Map here in case - // setLocalDescription() is called immediately after a rollback (without calling - // createOffer() or createAnswer()), in which case this roll back is not due to a - // glare scenario and this Map should be restored. - peerConnection._rolledBackTracksToSSRCs = new Map(peerConnection._tracksToSSRCs); - peerConnection._tracksToSSRCs = new Map(peerConnection._appliedTracksToSSRCs); - - promise = Promise.resolve(); - promise.then(() => peerConnection.dispatchEvent(new Event('signalingstatechange'))); - } - } - - return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description)); -} - -function setRemoteAnswer(peerConnection, answer) { - // Apply the pending local offer. - const pendingLocalOffer = peerConnection._pendingLocalOffer; - return peerConnection._peerConnection.setLocalDescription(pendingLocalOffer).then(() => { - peerConnection._pendingLocalOffer = null; - return peerConnection.setRemoteDescription(answer); - }).then(() => { - // NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection - // and the underlying RTCPeerConnection implementation have converged. We - // can unblock any pending calls to addIceCandidate now. - peerConnection._signalingStateLatch.lower(); +/** + * Create a local RTCSessionDescription. + * @param {ChromeRTCPeerConnection} chromeRTCPeerConnection + * @param {*} options + * @param {'answer'|'offer'} type + * @return {Promise} + */ +function createLocalDescription({ + _peerConnection: peerConnection, + _rolledBackTracksToSSRCs: rolledBackTracksToSSRCs, + _tracksToSSRCs: tracksToSSRCs +}, options, type) { + const { createAnswer, createOffer } = peerConnection; + const createDescription = { answer: createAnswer, offer: createOffer }[type]; + return createDescription.call(peerConnection, options).then(({ sdp }) => { + // NOTE(mmalavalli): If createAnswer() and createOffer() is called immediately after + // rolling back, then we no longer need to retain the rolled back tracks to SSRCs Map. + rolledBackTracksToSSRCs.clear(); + return new RTCSessionDescription({ + sdp: updateTrackIdsToSSRCs(tracksToSSRCs, sdp), + type + }); }); } @@ -450,81 +162,4 @@ function hasReceiversForTracksOfKind(peerConnection, kind) { }); } -function unwrap(description) { - if (description instanceof ChromeRTCSessionDescription) { - if (description._description) { - return description._description; - } - } - return new RTCSessionDescription(description); -} - -/** - * Check whether or not we need to apply our maxPacketLifeTime shim. We are - * pretty conservative: we'll only apply it if the legacy maxRetransmitTime - * property is available _and_ the standard maxPacketLifeTime property is _not_ - * available (the thinking being that Chrome will land the standards-compliant - * property). - * @returns {boolean} - */ -function needsMaxPacketLifeTimeShim() { - return 'maxRetransmitTime' in RTCDataChannel.prototype - && !('maxPacketLifeTime' in RTCDataChannel.prototype); -} - -/** - * Shim an RTCDataChannelInit dictionary (if necessary). This function returns - * a copy of the original RTCDataChannelInit. - * @param {RTCDataChannelInit} dataChannelDict - * @returns {RTCDataChannelInit} - */ -function shimDataChannelInit(dataChannelDict) { - dataChannelDict = Object.assign({}, dataChannelDict); - if (needsMaxPacketLifeTimeShim() && 'maxPacketLifeTime' in dataChannelDict) { - dataChannelDict.maxRetransmitTime = dataChannelDict.maxPacketLifeTime; - } - return dataChannelDict; -} - -/** - * Shim an RTCDataChannel (if necessary). This function mutates the - * RTCDataChannel. - * @param {RTCDataChannel} dataChannel - * @returns {RTCDataChannel} - */ -function shimDataChannel(dataChannel) { - Object.defineProperty(dataChannel, 'maxRetransmits', { - value: dataChannel.maxRetransmits === 65535 - ? null - : dataChannel.maxRetransmits - }); - if (needsMaxPacketLifeTimeShim()) { - // NOTE(mroberts): We can rename `maxRetransmitTime` to `maxPacketLifeTime`. - // - // https://bugs.chromium.org/p/chromium/issues/detail?id=696681 - // - Object.defineProperty(dataChannel, 'maxPacketLifeTime', { - value: dataChannel.maxRetransmitTime === 65535 - ? null - : dataChannel.maxRetransmitTime - }); - } - return dataChannel; -} - -/** - * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both - * the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method - * ensures that SSRCs never change once announced. - * @param {'planb'|'unified'} sdpFormat - * @param {Map>} tracksToSSRCs - * @param {string} sdp - an SDP whose format is determined by `sdpSemantics` - * @returns {string} updatedSdp - updated SDP - */ -function updateTrackIdsToSSRCs(sdpFormat, tracksToSSRCs, sdp) { - return sdpFormat === 'unified' - ? updateUnifiedPlanTrackIdsToSSRCs(tracksToSSRCs, sdp) - : updatePlanBTrackIdsToSSRCs(tracksToSSRCs, sdp); -} - module.exports = ChromeRTCPeerConnection; diff --git a/lib/webrtc/rtcpeerconnection/firefox.js b/lib/webrtc/rtcpeerconnection/firefox.js index e99780c6e..65f90cfb9 100644 --- a/lib/webrtc/rtcpeerconnection/firefox.js +++ b/lib/webrtc/rtcpeerconnection/firefox.js @@ -2,8 +2,7 @@ 'use strict'; const EventTarget = require('../../eventtarget'); -const FirefoxRTCSessionDescription = require('../rtcsessiondescription/firefox'); -const { updateUnifiedPlanTrackIdsToSSRCs: updateTracksToSSRCs } = require('../util/sdp'); +const { updateTrackIdsToSSRCs } = require('../util/sdp'); const { delegateMethods, interceptEvent, legacyPromise, proxyProperties } = require('../util'); // NOTE(mroberts): This class wraps Firefox's RTCPeerConnection implementation. @@ -135,9 +134,9 @@ class FirefoxRTCPeerConnection extends EventTarget { } promise = promise.then(offer => { - return new FirefoxRTCSessionDescription({ + return new RTCSessionDescription({ type: offer.type, - sdp: updateTracksToSSRCs(this._tracksToSSRCs, offer.sdp) + sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, offer.sdp) }); }); @@ -221,7 +220,7 @@ delegateMethods( function rollback(peerConnection, local, onceRolledBack) { const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription'; peerConnection._rollingBack = true; - return peerConnection._peerConnection[setLocalDescription](new FirefoxRTCSessionDescription({ + return peerConnection._peerConnection[setLocalDescription](new RTCSessionDescription({ type: 'rollback' })).then(onceRolledBack).then(result => { peerConnection._rollingBack = false; @@ -280,7 +279,7 @@ function saveInitiallyNegotiatedDtlsRole(peerConnection, description, remote) { */ function overwriteWithInitiallyNegotiatedDtlsRole(description, dtlsRole) { if (description && description.type === 'answer' && dtlsRole) { - return new FirefoxRTCSessionDescription({ + return new RTCSessionDescription({ type: description.type, sdp: description.sdp.replace(/a=setup:[a-z]+/g, 'a=setup:' + dtlsRole) }); diff --git a/lib/webrtc/rtcpeerconnection/safari.js b/lib/webrtc/rtcpeerconnection/safari.js index 6dd3c84ba..f11cc190b 100644 --- a/lib/webrtc/rtcpeerconnection/safari.js +++ b/lib/webrtc/rtcpeerconnection/safari.js @@ -3,15 +3,9 @@ const EventTarget = require('../../eventtarget'); const Latch = require('../util/latch'); -const { getSdpFormat, updatePlanBTrackIdsToSSRCs, updateUnifiedPlanTrackIdsToSSRCs } = require('../util/sdp'); +const { updateTrackIdsToSSRCs } = require('../util/sdp'); const { delegateMethods, interceptEvent, proxyProperties } = require('../util'); -const isUnifiedPlan = getSdpFormat() === 'unified'; - -const updateTrackIdsToSSRCs = isUnifiedPlan - ? updateUnifiedPlanTrackIdsToSSRCs - : updatePlanBTrackIdsToSSRCs; - class SafariRTCPeerConnection extends EventTarget { constructor(configuration) { super(); @@ -140,23 +134,19 @@ class SafariRTCPeerConnection extends EventTarget { // NOTE(mroberts): In general, this is not the way to do this; however, it's // good enough for our application. - if (options.offerToReceiveVideo && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) { + if (options.offerToReceiveVideo && !this._audioTransceiver && !hasReceiversForTracksOfKind(this, 'audio')) { delete options.offerToReceiveAudio; try { - this._audioTransceiver = isUnifiedPlan - ? this.addTransceiver('audio', { direction: 'recvonly' }) - : this.addTransceiver('audio'); + this._audioTransceiver = this.addTransceiver('audio', { direction: 'recvonly' }); } catch (e) { return Promise.reject(e); } } - if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) { + if (options.offerToReceiveVideo && !this._videoTransceiver && !hasReceiversForTracksOfKind(this, 'video')) { delete options.offerToReceiveVideo; try { - this._videoTransceiver = isUnifiedPlan - ? this.addTransceiver('video', { direction: 'recvonly' }) - : this.addTransceiver('video'); + this._videoTransceiver = this.addTransceiver('video', { direction: 'recvonly' }); } catch (e) { return Promise.reject(e); } @@ -186,10 +176,10 @@ class SafariRTCPeerConnection extends EventTarget { // longer need to retain the rolled back tracks to SSRCs Map. this._rolledBackTracksToSSRCs.clear(); - return isUnifiedPlan ? new RTCSessionDescription({ + return new RTCSessionDescription({ type: answer.type, sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, answer.sdp) - }) : answer; + }); }, error => { this._pendingRemoteOffer = null; throw error; @@ -201,10 +191,10 @@ class SafariRTCPeerConnection extends EventTarget { // longer need to retain the rolled back tracks to SSRCs Map. this._rolledBackTracksToSSRCs.clear(); - return isUnifiedPlan ? new RTCSessionDescription({ + return new RTCSessionDescription({ type: answer.type, sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, answer.sdp) - }) : answer; + }); }); } diff --git a/lib/webrtc/rtcsessiondescription/chrome.js b/lib/webrtc/rtcsessiondescription/chrome.js deleted file mode 100644 index 4a5184d84..000000000 --- a/lib/webrtc/rtcsessiondescription/chrome.js +++ /dev/null @@ -1,39 +0,0 @@ -/* globals RTCSessionDescription */ -'use strict'; - -// This class wraps Chrome's RTCSessionDescription implementation. It provides -// one piece of functionality not currently present in Chrome, namely -// -// 1. Rollback support -// https://bugs.chromium.org/p/webrtc/issues/detail?id=4676 -// -class ChromeRTCSessionDescription { - constructor(descriptionInitDict) { - this.descriptionInitDict = descriptionInitDict; - - // If this constructor is called with an object with a .type property set to - // "rollback", we should not call Chrome's RTCSessionDescription constructor, - // because this would throw an RTCSdpType error. - const description = descriptionInitDict && descriptionInitDict.type === 'rollback' - ? null - : new RTCSessionDescription(descriptionInitDict); - - Object.defineProperties(this, { - _description: { - get: function() { - return description; - } - } - }); - } - - get sdp() { - return this._description ? this._description.sdp : this.descriptionInitDict.sdp; - } - - get type() { - return this._description ? this._description.type : this.descriptionInitDict.type; - } -} - -module.exports = ChromeRTCSessionDescription; diff --git a/lib/webrtc/rtcsessiondescription/firefox.js b/lib/webrtc/rtcsessiondescription/firefox.js deleted file mode 100644 index 2afdc9813..000000000 --- a/lib/webrtc/rtcsessiondescription/firefox.js +++ /dev/null @@ -1,4 +0,0 @@ -/* globals RTCSessionDescription */ -'use strict'; - -module.exports = RTCSessionDescription; diff --git a/lib/webrtc/rtcsessiondescription/index.js b/lib/webrtc/rtcsessiondescription/index.js deleted file mode 100644 index 33759ecfd..000000000 --- a/lib/webrtc/rtcsessiondescription/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* globals RTCSessionDescription */ -'use strict'; - -if (typeof RTCSessionDescription === 'function') { - const { guessBrowser } = require('../util'); - switch (guessBrowser()) { - case 'chrome': - module.exports = require('./chrome'); - break; - case 'firefox': - module.exports = require('./firefox'); - break; - default: - module.exports = RTCSessionDescription; - break; - } -} else { - module.exports = function RTCSessionDescription() { - throw new Error('RTCSessionDescription is not supported'); - }; -} diff --git a/lib/webrtc/util/sdp.js b/lib/webrtc/util/sdp.js index 7cefbafa1..4b1ece750 100644 --- a/lib/webrtc/util/sdp.js +++ b/lib/webrtc/util/sdp.js @@ -1,107 +1,6 @@ -/* globals RTCPeerConnection, RTCRtpTransceiver */ - 'use strict'; -const { flatMap, guessBrowser } = require('./'); - -// NOTE(mmalavalli): We cache Chrome's sdpSemantics support in order to prevent -// instantiation of more than one RTCPeerConnection. -let isSdpSemanticsSupported = null; - -/** - * Check if Chrome supports specifying sdpSemantics for an RTCPeerConnection. - * @return {boolean} - */ -function checkIfSdpSemanticsIsSupported() { - if (typeof isSdpSemanticsSupported === 'boolean') { - return isSdpSemanticsSupported; - } - if (typeof RTCPeerConnection === 'undefined') { - isSdpSemanticsSupported = false; - return isSdpSemanticsSupported; - } - try { - // eslint-disable-next-line no-new - new RTCPeerConnection({ sdpSemantics: 'foo' }); - isSdpSemanticsSupported = false; - } catch (e) { - isSdpSemanticsSupported = true; - } - return isSdpSemanticsSupported; -} - -// NOTE(mmalavalli): We cache Chrome's SDP format in order to prevent -// instantiation of more than one RTCPeerConnection. -let chromeSdpFormat = null; - -/** - * Clear cached Chrome's SDP format - */ -function clearChromeCachedSdpFormat() { - chromeSdpFormat = null; -} - -/** - * Get Chrome's default SDP format. - * @returns {'planb'|'unified'} - */ -function getChromeDefaultSdpFormat() { - if (!chromeSdpFormat) { - if (typeof RTCPeerConnection !== 'undefined' - && 'addTransceiver' in RTCPeerConnection.prototype) { - const pc = new RTCPeerConnection(); - try { - pc.addTransceiver('audio'); - chromeSdpFormat = 'unified'; - } catch (e) { - chromeSdpFormat = 'planb'; - } - pc.close(); - } else { - chromeSdpFormat = 'planb'; - } - } - return chromeSdpFormat; -} - -/** - * Get Chrome's SDP format. - * @param {'plan-b'|'unified-plan'} [sdpSemantics] - * @returns {'planb'|'unified'} - */ -function getChromeSdpFormat(sdpSemantics) { - if (!sdpSemantics || !checkIfSdpSemanticsIsSupported()) { - return getChromeDefaultSdpFormat(); - } - return { - 'plan-b': 'planb', - 'unified-plan': 'unified' - }[sdpSemantics]; -} - -/** - * Get Safari's default SDP format. - * @returns {'planb'|'unified'} - */ -function getSafariSdpFormat() { - return typeof RTCRtpTransceiver !== 'undefined' - && 'currentDirection' in RTCRtpTransceiver.prototype - ? 'unified' - : 'planb'; -} - -/** - * Get the browser's default SDP format. - * @param {'plan-b'|'unified-plan'} [sdpSemantics] - * @returns {'planb'|'unified'} - */ -function getSdpFormat(sdpSemantics) { - return { - chrome: getChromeSdpFormat(sdpSemantics), - firefox: 'unified', - safari: getSafariSdpFormat() - }[guessBrowser()] || null; -} +const { flatMap } = require('./'); /** * Match a pattern across lines, returning the first capture group for any @@ -120,41 +19,11 @@ function getMatches(pattern, lines) { /** * Get a Set of MediaStreamTrack IDs from an SDP. - * @param {string} pattern - * @param {string} sdp - * @returns {Set} - */ -function getTrackIds(pattern, sdp) { - return getMatches(pattern, sdp); -} - -/** - * Get a Set of MediaStreamTrack IDs from a Plan B SDP. - * @param {string} sdp - Plan B SDP - * @returns {Set} trackIds - */ -function getPlanBTrackIds(sdp) { - return getTrackIds('^a=ssrc:[0-9]+ +msid:.+ +(.+) *$', sdp); -} - -/** - * Get a Set of MediaStreamTrack IDs from a Unified Plan SDP. - * @param {string} sdp - Unified Plan SDP + * @param {string} sdp - SDP * @returns {Set} trackIds */ -function getUnifiedPlanTrackIds(sdp) { - return getTrackIds('^a=msid:.+ +(.+) *$', sdp); -} - -/** - * Get a Set of SSRCs for a MediaStreamTrack from a Plan B SDP. - * @param {string} sdp - Plan B SDP - * @param {string} trackId - MediaStreamTrack ID - * @returns {Set} - */ -function getPlanBSSRCs(sdp, trackId) { - const pattern = `^a=ssrc:([0-9]+) +msid:[^ ]+ +${trackId} *$`; - return getMatches(pattern, sdp); +function getTrackIds(sdp) { + return getMatches('^a=msid:.+ +(.+) *$', sdp); } /** @@ -182,12 +51,12 @@ function getMediaSectionSSRCs(mediaSection) { } /** - * Get a Set of SSRCs for a MediaStreamTrack from a Unified Plan SDP. - * @param {string} sdp - Unified Plan SDP + * Get a Set of SSRCs for a MediaStreamTrack from an SDP. + * @param {string} sdp - SDP * @param {string} trackId - MediaStreamTrack ID * @returns {Set} */ -function getUnifiedPlanSSRCs(sdp, trackId) { +function getSSRCs(sdp, trackId) { const mediaSections = getMediaSections(sdp); const msidAttrRegExp = new RegExp(`^a=msid:[^ ]+ +${trackId} *$`, 'gm'); @@ -198,43 +67,22 @@ function getUnifiedPlanSSRCs(sdp, trackId) { /** * Get a Map from MediaStreamTrack IDs to SSRCs from an SDP. - * @param {function(string): Set} getTrackIds - * @param {function(string, string): Set} getSSRCs * @param {string} sdp - SDP * @returns {Map>} trackIdsToSSRCs */ -function getTrackIdsToSSRCs(getTrackIds, getSSRCs, sdp) { +function getTrackIdsToSSRCs(sdp) { return new Map(Array.from(getTrackIds(sdp)).map(trackId => [trackId, getSSRCs(sdp, trackId)])); } -/** - * Get a Map from MediaStreamTrack IDs to SSRCs from a Plan B SDP. - * @param {string} sdp - Plan B SDP - * @returns {Map>} trackIdsToSSRCs - */ -function getPlanBTrackIdsToSSRCs(sdp) { - return getTrackIdsToSSRCs(getPlanBTrackIds, getPlanBSSRCs, sdp); -} - -/** - * Get a Map from MediaStreamTrack IDs to SSRCs from a Plan B SDP. - * @param {string} sdp - Plan B SDP - * @returns {Map>} trackIdsToSSRCs - */ -function getUnifiedPlanTrackIdsToSSRCs(sdp) { - return getTrackIdsToSSRCs(getUnifiedPlanTrackIds, getUnifiedPlanSSRCs, sdp); -} - /** * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both * the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method * ensures that SSRCs never change once announced. - * @param {function(string): Map>} getTrackIdsToSSRCs * @param {Map>} trackIdsToSSRCs * @param {string} sdp - SDP - * @returns {strinng} updatedSdp - updated SDP + * @returns {string} updatedSdp - updated SDP */ -function updateTrackIdsToSSRCs(getTrackIdsToSSRCs, trackIdsToSSRCs, sdp) { +function updateTrackIdsToSSRCs(trackIdsToSSRCs, sdp) { const newTrackIdsToSSRCs = getTrackIdsToSSRCs(sdp); const newSSRCsToOldSSRCs = new Map(); @@ -275,36 +123,7 @@ function updateTrackIdsToSSRCs(getTrackIdsToSSRCs, trackIdsToSSRCs, sdp) { return sdp; } -/** - * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both - * the Map from MediaStreamTrack IDs to SSRCs and the Plan B SDP itself. This - * method ensures that SSRCs never change once announced. - * @param {Map>} trackIdsToSSRCs - * @param {string} sdp - Plan B SDP - * @returns {string} updatedSdp - updated Plan B SDP - */ -function updatePlanBTrackIdsToSSRCs(trackIdsToSSRCs, sdp) { - return updateTrackIdsToSSRCs(getPlanBTrackIdsToSSRCs, trackIdsToSSRCs, sdp); -} - -/** - * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both - * the Map from MediaStreamTrack IDs to SSRCs and the Plan B SDP itself. This - * method ensures that SSRCs never change once announced. - * @param {Map>} trackIdsToSSRCs - * @param {string} sdp - Plan B SDP - * @returns {string} updatedSdp - updated Plan B SDP - */ -function updateUnifiedPlanTrackIdsToSSRCs(trackIdsToSSRCs, sdp) { - return updateTrackIdsToSSRCs(getUnifiedPlanTrackIdsToSSRCs, trackIdsToSSRCs, sdp); -} - -exports.clearChromeCachedSdpFormat = clearChromeCachedSdpFormat; -exports.getSdpFormat = getSdpFormat; exports.getMediaSections = getMediaSections; -exports.getPlanBTrackIds = getPlanBTrackIds; -exports.getUnifiedPlanTrackIds = getUnifiedPlanTrackIds; -exports.getPlanBSSRCs = getPlanBSSRCs; -exports.getUnifiedPlanSSRCs = getUnifiedPlanSSRCs; -exports.updatePlanBTrackIdsToSSRCs = updatePlanBTrackIdsToSSRCs; -exports.updateUnifiedPlanTrackIdsToSSRCs = updateUnifiedPlanTrackIdsToSSRCs; +exports.getTrackIds = getTrackIds; +exports.getSSRCs = getSSRCs; +exports.updateTrackIdsToSSRCs = updateTrackIdsToSSRCs; diff --git a/test/integration/index.js b/test/integration/index.js index f68a80553..014cb2966 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -23,4 +23,3 @@ require('./spec/util/support'); require('./spec/util/simulcast'); require('./spec/webrtc/getstats'); require('./spec/webrtc/rtcpeerconnection'); -require('./spec/webrtc/rtcsessiondescription'); diff --git a/test/integration/spec/util/simulcast.js b/test/integration/spec/util/simulcast.js index 6dc38f7f3..6083eb97c 100644 --- a/test/integration/spec/util/simulcast.js +++ b/test/integration/spec/util/simulcast.js @@ -3,7 +3,7 @@ const assert = require('assert'); const { guessBrowser } = require('../../../../es5/webrtc/util'); const { getMediaSections, setSimulcast } = require('../../../../es5/util/sdp'); -const { RTCPeerConnection, RTCSessionDescription } = require('../../../../es5/webrtc/index'); +const { RTCPeerConnection } = require('../../../../es5/webrtc/index'); const isChrome = guessBrowser() === 'chrome'; diff --git a/test/integration/spec/webrtc/getstats.js b/test/integration/spec/webrtc/getstats.js index d090f411c..6a3625100 100644 --- a/test/integration/spec/webrtc/getstats.js +++ b/test/integration/spec/webrtc/getstats.js @@ -10,14 +10,11 @@ const getStats = require('../../../../es5/webrtc/getstats'); const getUserMedia = require('../../../../es5/webrtc/getusermedia'); const RTCPeerConnection = require('../../../../es5/webrtc/rtcpeerconnection'); const { guessBrowser } = require('../../../../es5/webrtc/util'); -const { getSdpFormat } = require('../../../../es5/webrtc/util/sdp'); const guess = guessBrowser(); const isFirefox = guess === 'firefox'; -const isSafari = guess === 'safari'; -const sdpFormat = getSdpFormat(); -(isSafari && sdpFormat === 'planb' ? describe.skip : describe)(`getStats(${sdpFormat})`, function() { +describe('getStats()', function() { // eslint-disable-next-line no-invalid-this this.timeout(10000); diff --git a/test/integration/spec/webrtc/rtcpeerconnection.js b/test/integration/spec/webrtc/rtcpeerconnection.js index db0394ed6..5960f2310 100644 --- a/test/integration/spec/webrtc/rtcpeerconnection.js +++ b/test/integration/spec/webrtc/rtcpeerconnection.js @@ -6,25 +6,18 @@ /* eslint-disable no-undef */ 'use strict'; -var assert = require('assert'); -var MediaStream = require('../../../../es5/webrtc/mediastream'); -var MediaStreamTrack = require('../../../../es5/webrtc/mediastreamtrack'); -var RTCIceCandidate = require('../../../../es5/webrtc/rtcicecandidate'); -var RTCSessionDescription = require('../../../../es5/webrtc/rtcsessiondescription'); -var RTCPeerConnection = require('../../../../es5/webrtc/rtcpeerconnection'); -var util = require('../../../lib/webrtc/util'); -var { flatMap, guessBrowser } = require('../../../../es5/webrtc/util'); -var { getSdpFormat } = require('../../../../es5/webrtc/util/sdp'); - -const detectSilence = require('../../../lib/webrtc/detectsilence'); - -var sdpTypes = [ +const assert = require('assert'); +const RTCPeerConnection = require('../../../../es5/webrtc/rtcpeerconnection'); +const { a } = require('../../../lib/webrtc/util'); +const { flatMap, guessBrowser } = require('../../../../es5/webrtc/util'); + +const sdpTypes = [ 'answer', 'offer', 'rollback' ]; -var signalingStates = [ +const signalingStates = [ 'closed', 'have-local-offer', 'have-remote-offer', @@ -35,7 +28,6 @@ const guess = guessBrowser(); const isChrome = guess === 'chrome'; const isFirefox = guess === 'firefox'; const isSafari = guess === 'safari'; -const sdpFormat = getSdpFormat(); const chromeVersion = isChrome && typeof navigator === 'object' ? navigator.userAgent.match(/Chrom(e|ium)\/(\d+)\./)[2] @@ -45,7 +37,7 @@ const firefoxVersion = isFirefox && typeof navigator === 'object' ? navigator.userAgent.match(/Firefox\/(\d+)\./)[1] : null; -describe(`RTCPeerConnection(${sdpFormat})`, function() { +describe('RTCPeerConnection', function() { after(() => { if (typeof gc === 'function') { gc(); @@ -153,7 +145,7 @@ describe(`RTCPeerConnection(${sdpFormat})`, function() { }); }); - (isSafari && sdpFormat === 'planb' ? describe.skip : describe)('#createOffer, called twice from signaling state "stable" without calling #setLocalDescription', () => { + describe('#createOffer, called twice from signaling state "stable" without calling #setLocalDescription', () => { let offer1; let offer2; @@ -182,7 +174,7 @@ describe(`RTCPeerConnection(${sdpFormat})`, function() { }); }); - (isSafari && sdpFormat === 'planb' ? describe.skip : describe)('#createAnswer, called twice from signaling state "stable" without calling #setLocalDescription', () => { + describe('#createAnswer, called twice from signaling state "stable" without calling #setLocalDescription', () => { let answer1; let answer2; @@ -231,7 +223,7 @@ describe(`RTCPeerConnection(${sdpFormat})`, function() { }); }); - (isSafari && sdpFormat === 'planb' ? describe.skip : describe)('#setRemoteDescription, called twice from signaling state "stable" with the same MediaStreamTrack IDs but different SSRCs', () => { + describe('#setRemoteDescription, called twice from signaling state "stable" with the same MediaStreamTrack IDs but different SSRCs', () => { let offer1; let offer2; @@ -263,20 +255,8 @@ describe(`RTCPeerConnection(${sdpFormat})`, function() { }); }); - // NOTE(mroberts): This is the crux of the issue at the heart of CSDK-1206: - // - // Chrome's WebRTC implementation treats changing the SSRC as removing a track - // with the old SSRC and adding a track with the new one. This probably isn't - // the right thing to do (especially when we go to Unified Plan SDP) but it's - // the way it's worked for a while. - // - (isFirefox || isSafari || (isChrome && sdpFormat === 'unified') - ? it - : it.skip - )('should create a single MediaStreamTrack for each MediaStreamTrack ID in the SDP, regardless of SSRC changes', async () => { - const getRemoteTracks = pc => sdpFormat === 'planb' - ? flatMap(pc.getRemoteStreams(), stream => stream.getTracks()) - : flatMap(pc.getTransceivers(), ({ receiver }) => receiver.track); + it('should create a single MediaStreamTrack for each MediaStreamTrack ID in the SDP, regardless of SSRC changes', async () => { + const getRemoteTracks = pc => flatMap(pc.getTransceivers(), ({ receiver }) => receiver.track); const pc = new RTCPeerConnection({ iceServers: [] }); await pc.setRemoteDescription(offer1); @@ -454,159 +434,156 @@ describe(`RTCPeerConnection(${sdpFormat})`, function() { }); }); - if (RTCPeerConnection.prototype.addTransceiver && sdpFormat !== 'planb') { - describe('RTCRtpTransceiver', () => { - describe('addTransceiver(kind, init)', () => { - const kind = 'audio'; - - let pc; - let transceiver; + describe('RTCRtpTransceiver', () => { + describe('addTransceiver(kind, init)', () => { + const kind = 'audio'; + let pc; + let transceiver; - before(() => { - pc = new RTCPeerConnection(); - transceiver = pc.addTransceiver(kind, {}); - }); + before(() => { + pc = new RTCPeerConnection(); + transceiver = pc.addTransceiver(kind, {}); + }); - it('returns an RTCRtpTransceiver', () => { - assert(transceiver instanceof RTCRtpTransceiver); - }); + it('returns an RTCRtpTransceiver', () => { + assert(transceiver instanceof RTCRtpTransceiver); + }); - it('returns an RTCRtpTransceiver that is present in `getTransceivers()`', () => { - assert(pc.getTransceivers().includes(transceiver)); - }); + it('returns an RTCRtpTransceiver that is present in `getTransceivers()`', () => { + assert(pc.getTransceivers().includes(transceiver)); + }); - it('returns an RTCRtpTransceiver whose `mid` is `null`', () => { - assert.equal(transceiver.mid, null); - }); + it('returns an RTCRtpTransceiver whose `mid` is `null`', () => { + assert.equal(transceiver.mid, null); + }); - it('returns an RTCRtpTransceiver whose `sender.track` is `null`', () => { - assert.equal(transceiver.sender.track, null); - }); + it('returns an RTCRtpTransceiver whose `sender.track` is `null`', () => { + assert.equal(transceiver.sender.track, null); + }); - it('returns an RTCRtpTransceiver whose `receiver.track.kind` is `kind`', () => { - assert.equal(transceiver.receiver.track.kind, kind); - }); + it('returns an RTCRtpTransceiver whose `receiver.track.kind` is `kind`', () => { + assert.equal(transceiver.receiver.track.kind, kind); + }); - it('returns an RTCRtpTransceiver whose `stopped` is `false`', () => { - assert(!transceiver.stopped); - }); + it('returns an RTCRtpTransceiver whose `stopped` is `false`', () => { + assert(!transceiver.stopped); + }); - it('returns an RTCRtpTransceiver whose `direction` is "sendrecv"', () => { - assert.equal(transceiver.direction, 'sendrecv'); - }); + it('returns an RTCRtpTransceiver whose `direction` is "sendrecv"', () => { + assert.equal(transceiver.direction, 'sendrecv'); + }); - it('returns an RTCRtpTransceiver whose `currentDirection` is `null`', () => { - assert.equal(transceiver.currentDirection, null); - }); + it('returns an RTCRtpTransceiver whose `currentDirection` is `null`', () => { + assert.equal(transceiver.currentDirection, null); }); + }); - describe('addTransceiver(track, init)', () => { - let pc; - let track; - let transceiver; + describe('addTransceiver(track, init)', () => { + let pc; + let track; + let transceiver; - before(async () => { - pc = new RTCPeerConnection(); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true, fake: true }); - [track] = await stream.getAudioTracks(); - transceiver = pc.addTransceiver(track, {}); - }); + before(async () => { + pc = new RTCPeerConnection(); + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, fake: true }); + [track] = await stream.getAudioTracks(); + transceiver = pc.addTransceiver(track, {}); + }); - it('returns an RTCRtpTransceiver', () => { - assert(transceiver instanceof RTCRtpTransceiver); - }); + it('returns an RTCRtpTransceiver', () => { + assert(transceiver instanceof RTCRtpTransceiver); + }); - it('returns an RTCRtpTransceiver that is present in `getTransceivers()`', () => { - assert(pc.getTransceivers().includes(transceiver)); - }); + it('returns an RTCRtpTransceiver that is present in `getTransceivers()`', () => { + assert(pc.getTransceivers().includes(transceiver)); + }); - it('returns an RTCRtpTransceiver whose `mid` is `null`', () => { - assert.equal(transceiver.mid, null); - }); + it('returns an RTCRtpTransceiver whose `mid` is `null`', () => { + assert.equal(transceiver.mid, null); + }); - it('returns an RTCRtpTransceiver whose `sender.track` is `track`', () => { - assert.equal(transceiver.sender.track, track); - }); + it('returns an RTCRtpTransceiver whose `sender.track` is `track`', () => { + assert.equal(transceiver.sender.track, track); + }); - it('returns an RTCRtpTransceiver whose `receiver.track.kind` is `track.kind`', () => { - assert.equal(transceiver.receiver.track.kind, track.kind); - }); + it('returns an RTCRtpTransceiver whose `receiver.track.kind` is `track.kind`', () => { + assert.equal(transceiver.receiver.track.kind, track.kind); + }); - it('returns an RTCRtpTransceiver whose `stopped` is `false`', () => { - assert(!transceiver.stopped); - }); + it('returns an RTCRtpTransceiver whose `stopped` is `false`', () => { + assert(!transceiver.stopped); + }); - it('returns an RTCRtpTransceiver whose `direction` is "sendrecv"', () => { - assert.equal(transceiver.direction, 'sendrecv'); - }); + it('returns an RTCRtpTransceiver whose `direction` is "sendrecv"', () => { + assert.equal(transceiver.direction, 'sendrecv'); + }); - it('returns an RTCRtpTransceiver whose `currentDirection` is `null`', () => { - assert.equal(transceiver.currentDirection, null); - }); + it('returns an RTCRtpTransceiver whose `currentDirection` is `null`', () => { + assert.equal(transceiver.currentDirection, null); }); + }); + describe('Recycling Behavior', () => { // TODO(mpatwardhan): VIDEO-4940: chrome now supports RTCRtpTransceiver.prototype.stop, but test needs to be fixed for chrome - (RTCRtpTransceiver.prototype.stop && !isChrome ? describe : describe.skip)('Recycling Behavior', () => { - it('Scenario 1', async () => { - const configuration = { - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require' - }; - - const [pc1, pc2] = createPeerConnections(configuration); - - // Round 1 - console.log('Round 1'); - const t1 = pc1.addTransceiver('audio'); - await negotiate(pc1, pc2); - assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); - - console.log('Round 2'); - // Round 2 - t1.stop(); - await negotiate(pc1, pc2); - assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); - - console.log('Round 3'); - // Round 3 - const t2 = pc1.addTransceiver('audio'); - await negotiate(pc1, pc2); - assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); - }); + (isChrome ? it.skip : it)('Scenario 1', async () => { + const configuration = { + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require' + }; + + const [pc1, pc2] = createPeerConnections(configuration); + + // Round 1 + console.log('Round 1'); + const t1 = pc1.addTransceiver('audio'); + await negotiate(pc1, pc2); + assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); + + console.log('Round 2'); + // Round 2 + t1.stop(); + await negotiate(pc1, pc2); + assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); + + console.log('Round 3'); + // Round 3 + const t2 = pc1.addTransceiver('audio'); + await negotiate(pc1, pc2); + assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); + }); - // NOTE(mmalavalli): Because of a bug where "max-bundle" does not work - // with stopped RTCRtpTransceivers this scenario fails. So this test is - // disabled for Safari unified plan. - // - // Bug: https://bugs.chromium.org/p/webrtc/issues/detail?id=9954 - // - (isSafari && sdpFormat === 'unified' ? it.skip : it)('Scenario 2', async () => { - const configuration = { - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require' - }; - - const [pc1, pc2] = createPeerConnections(configuration); - - // Round 1 - const t1 = pc1.addTransceiver('audio'); - await negotiate(pc1, pc2); - assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); - - // Round 2 - t1.stop(); - const t2 = pc1.addTransceiver('audio'); - await negotiate(pc1, pc2); - assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 2); - - // Round 3 - const t3 = pc1.addTransceiver('audio'); - await negotiate(pc1, pc2); - assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 2); - }); + // NOTE(mmalavalli): Because of a bug where "max-bundle" does not work + // with stopped RTCRtpTransceivers this scenario fails. So this test is + // disabled for Safari unified plan. + // + // Bug: https://bugs.chromium.org/p/webrtc/issues/detail?id=9954 + // + (isSafari ? it.skip : it)('Scenario 2', async () => { + const configuration = { + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require' + }; + + const [pc1, pc2] = createPeerConnections(configuration); + + // Round 1 + const t1 = pc1.addTransceiver('audio'); + await negotiate(pc1, pc2); + assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 1); + + // Round 2 + t1.stop(); + const t2 = pc1.addTransceiver('audio'); + await negotiate(pc1, pc2); + assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 2); + + // Round 3 + const t3 = pc1.addTransceiver('audio'); + await negotiate(pc1, pc2); + assert.equal(pc1.localDescription.sdp.match(/\r\nm=/g).length, 2); }); }); - } + }); }); function assertEqualDescriptions(actual, expected) { @@ -618,8 +595,8 @@ function assertEqualDescriptions(actual, expected) { // NOTE(mroberts): The .sdp property of a local description may change on // subsequent accesses (as ICE candidates are gathered); so, rather than // compare the entire SDP string, let us just compare the o=line. - var expectedOLine = expected.sdp.match(/^o=.*\r$/m)[0]; - var actualOLine = actual.sdp.match(/^o=.*\r$/m)[0]; + const expectedOLine = expected.sdp.match(/^o=.*\r$/m)[0]; + const actualOLine = actual.sdp.match(/^o=.*\r$/m)[0]; assert.equal(actualOLine, expectedOLine); } } @@ -632,7 +609,7 @@ function emptyDescription() { } function testConstructor() { - var test; + let test; beforeEach(() => { return makeTest().then(_test => test = _test); @@ -642,7 +619,7 @@ function testConstructor() { assert(test.peerConnection instanceof RTCPeerConnection); }); - var expected = { + const expected = { iceConnectionState: 'new', iceGatheringState: 'new', localDescription: emptyDescription(), @@ -675,21 +652,21 @@ function testAddIceCandidate(signalingState) { // NOTE(mroberts): "stable" and "have-local-offer" only trigger failure here // because we test one round of negotiation. If we tested multiple rounds, // such that remoteDescription was non-null, we would accept a success here. - var shouldFail = { + const shouldFail = { 'closed': true, 'stable': !isSafari, 'have-local-offer': !isSafari }[signalingState] || false; - var needsTransition = { + const needsTransition = { 'have-local-offer': true, 'have-remote-offer': true }[signalingState] || false; (signalingState === 'closed' && isSafari ? context.skip : context)(JSON.stringify(signalingState), () => { - var error; - var result; - var test; + let error; + let result; + let test; beforeEach(() => { error = null; @@ -700,8 +677,8 @@ function testAddIceCandidate(signalingState) { }).then(_test => { test = _test; - var candidate = test.createRemoteCandidate(); - var promise = test.peerConnection.addIceCandidate(candidate); + const candidate = test.createRemoteCandidate(); + const promise = test.peerConnection.addIceCandidate(candidate); // NOTE(mroberts): Because of the way the ChromeRTCPeerConnection // simulates signalingStates "have-local-offer" and "have-remote-offer", @@ -738,9 +715,9 @@ function testAddIceCandidate(signalingState) { } function testGetSenders(signalingState) { - var senders; - var stream; - var test; + let senders; + let stream; + let test; before(async () => { stream = await makeStream({ audio: true, video: true }); @@ -755,7 +732,6 @@ function testGetSenders(signalingState) { // NOTE(mmalavalli): Safari 12.2+ and Firefox 67+ implement the spec-compliant // version of RTCPeerConnection.getSenders() for signalingState "closed". const isSpecCompliantForClosed = signalingState === 'closed' - && sdpFormat === 'unified' && (isSafari || (isFirefox && firefoxVersion > 66)); it(`should return ${isSpecCompliantForClosed ? 'an empty list' : 'a list of senders'}`, () => { @@ -763,9 +739,6 @@ function testGetSenders(signalingState) { if (isFirefox && signalingState === 'have-remote-offer') { assert.equal(actualSenders.length, senders.length); return; - } else if (isSafari && sdpFormat === 'planb' && signalingState === 'have-local-offer') { - assert.equal(actualSenders.length, senders.length + 1); - return; } if (isSpecCompliantForClosed) { assert.deepEqual(actualSenders, []); @@ -777,8 +750,8 @@ function testGetSenders(signalingState) { } function testGetReceivers(signalingState) { - var pc2; - var stream; + let pc2; + let stream; before(async () => { const pc1 = new RTCPeerConnection({ iceServers: [] }); @@ -851,9 +824,9 @@ function expectSinglingStateChangeOnClose() { function testClose(signalingState) { context(JSON.stringify(signalingState), () => { - var result; - var test; - var signalingStateChangeInThisTick; + let result; + let test; + let signalingStateChangeInThisTick; beforeEach(() => { function onSigalingStateChanged() { @@ -874,7 +847,7 @@ function testClose(signalingState) { } test.peerConnection.addEventListener('signalingstatechange', onSigalingStateChanged); - var closePromise = test.close(); + const closePromise = test.close(); test.peerConnection.removeEventListener('signalingstatechange', onSigalingStateChanged); return closePromise.then(results => result = results[0]); }); @@ -884,7 +857,7 @@ function testClose(signalingState) { assert.equal(result, undefined); }); - var expected = { + const expected = { iceConnectionState: 'closed', iceGatheringState: 'complete', signalingState: 'closed' @@ -909,7 +882,7 @@ function testClose(signalingState) { }); } else { - var events = []; + const events = []; if (expectSinglingStateChangeOnClose()) { events.push('signalingstatechange'); @@ -920,7 +893,7 @@ function testClose(signalingState) { } events.forEach(event => { - it('should raise ' + util.a(event) + ' ' + event + ' event', () => { + it('should raise ' + a(event) + ' ' + event + ' event', () => { return test.waitFor(event); }); }); @@ -975,7 +948,7 @@ function testDtlsRoleNegotiation() { }); }); - (isSafari && sdpFormat === 'planb' ? it.skip : it)('RTCPeerConnection 1 answers with "a=setup:passive"', () => { + it('RTCPeerConnection 1 answers with "a=setup:passive"', () => { return pc1.createAnswer().then(answer => { assert(answer.sdp.match(/a=setup:passive/)); }); @@ -1021,7 +994,7 @@ function testGlare() { }); }); - (isSafari && sdpFormat === 'planb' ? it.skip : it)('RTCPeerConnection 1 calls createOffer and setLocalDescription', () => { + it('RTCPeerConnection 1 calls createOffer and setLocalDescription', () => { return pc1.createOffer().then(offer => { return pc1.setLocalDescription(offer); }); @@ -1047,17 +1020,17 @@ function makeStream(constraints) { return navigator.mediaDevices.getUserMedia(constraints); } - var getUserMedia = navigator.webkitGetUserMedia; + let getUserMedia = navigator.webkitGetUserMedia; getUserMedia = getUserMedia || navigator.mozGetUserMedia; getUserMedia = getUserMedia.bind(navigator, constraints); return new Promise((resolve, reject) => getUserMedia(resolve, reject)); } function testAddTrack() { - var test; - var stream; - var tracks; - var trackToAdd; + let test; + let stream; + let tracks; + let trackToAdd; before(async () => { stream = await makeStream(); @@ -1080,7 +1053,7 @@ function testAddTrack() { ] ].forEach(([scenario, setup]) => { context(scenario, () => { - var exception; + let exception; beforeEach(() => { setup(); @@ -1115,12 +1088,12 @@ function testAddTrack() { } function testRemoveTrack() { - var test; - var tracks; - var stream; - var localAudioSender; - var localAudioTrack; - var localVideoSender; + let test; + let tracks; + let stream; + let localAudioSender; + let localAudioTrack; + let localVideoSender; before(async () => { stream = await makeStream(); @@ -1147,7 +1120,7 @@ function testRemoveTrack() { ] ].forEach(([scenario, setup, shouldThrow]) => { context(scenario, () => { - var exception; + let exception; beforeEach(() => { setup(); @@ -1189,15 +1162,12 @@ function testRemoveTrack() { assert.deepEqual(presentTracks, stream.getVideoTracks()); }); - // NOTE(mmalavalli): Once RTCRtpSender is supported in Chrome, and we - // actually start using the native RTCPeerConnection's addTrack()/removeTrack() - // APIs in Firefox and Safari, these next two tests should be unskipped. - it.skip('should set the .track on its corresponding RTCRtpSender to null', () => { + it('should set the .track on its corresponding RTCRtpSender to null', () => { test.peerConnection.removeTrack(localAudioSender); assert.equal(localAudioSender.track, null); }); - it.skip('should retain the same RTCRtpSender instance in the list of RTCRtpSenders maintained by the RTCPeerConnection', () => { + it('should retain the same RTCRtpSender instance in the list of RTCRtpSenders maintained by the RTCPeerConnection', () => { test.peerConnection.removeTrack(localAudioSender); const senders = new Set(test.peerConnection.getSenders()); assert(senders.has(localAudioSender)); @@ -1205,13 +1175,13 @@ function testRemoveTrack() { } function testCreateAnswer(signalingState) { - var error; - var localDescription; - var remoteDescription; - var result; - var test; + let error; + let localDescription; + let remoteDescription; + let result; + let test; - var shouldFail = { + const shouldFail = { 'closed': true, 'have-local-offer': true, 'stable': true @@ -1235,7 +1205,7 @@ function testCreateAnswer(signalingState) { // an Error. } - var promise = test.peerConnection.createAnswer(); + const promise = test.peerConnection.createAnswer(); if (shouldFail) { return promise.catch(_error => error = _error); @@ -1291,13 +1261,13 @@ function testCreateAnswer(signalingState) { } function testCreateOffer(signalingState) { - var error; - var localDescription; - var remoteDescription; - var result; - var test; + let error; + let localDescription; + let remoteDescription; + let result; + let test; - var shouldFail = { + const shouldFail = { 'closed': true }[signalingState] || false; @@ -1319,7 +1289,7 @@ function testCreateOffer(signalingState) { // an Error. } - var promise = test.peerConnection.createOffer(test.offerOptions); + const promise = test.peerConnection.createOffer(test.offerOptions); if (shouldFail) { return promise.catch(_error => error = _error); @@ -1381,10 +1351,10 @@ function testCreateOffer(signalingState) { } function testSetDescription(local, signalingState, sdpType) { - var createLocalDescription = local ? 'createLocalDescription' : 'createRemoteDescription'; - var setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription'; + const createLocalDescription = local ? 'createLocalDescription' : 'createRemoteDescription'; + const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription'; - var nextSignalingState = { + const nextSignalingState = { true: { answer: { 'have-remote-offer': 'stable' @@ -1416,16 +1386,16 @@ function testSetDescription(local, signalingState, sdpType) { } }[local][sdpType][signalingState]; - var shouldFail = !nextSignalingState; + const shouldFail = !nextSignalingState; context(JSON.stringify(sdpType), () => { - var description; - var error; - var localDescription; - var nextDescription; - var remoteDescription; - var result; - var test; + let description; + let error; + let localDescription; + let nextDescription; + let remoteDescription; + let result; + let test; beforeEach(() => { error = null; @@ -1453,7 +1423,7 @@ function testSetDescription(local, signalingState, sdpType) { ? emptyDescription() : description; - var promise = test.peerConnection[setLocalDescription](description); + const promise = test.peerConnection[setLocalDescription](description); if (shouldFail) { return promise.catch(_error => error = _error); @@ -1531,14 +1501,13 @@ function testSetDescription(local, signalingState, sdpType) { } function makeTest(options) { - var dummyOfferSdp = `v=0\r + let dummyOfferSdp = `v=0\r o=- 2018425083800689377 2 IN IP4 127.0.0.1\r s=-\r t=0 0\r a=group:BUNDLE ${isFirefox && firefoxVersion < 63 ? 'sdparta_0' - : sdpFormat === 'unified' - ? '0' : 'audio'}\r + : '0'}\r a=msid-semantic: WMS\r m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r c=IN IP4 0.0.0.0\r @@ -1548,8 +1517,7 @@ a=ice-pwd:VSJteFVvAyoewWkSfaxKgU6C\r a=ice-options:trickle\r a=mid:${isFirefox && firefoxVersion < 63 ? 'sdparta_0' - : sdpFormat === 'unified' - ? '0' : 'audio'}\r + : '0'}\r a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r a=recvonly\r a=rtcp-mux\r @@ -1571,14 +1539,14 @@ a=rtpmap:126 telephone-event/8000\r a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8A:A4:59:E4:66:22:3D:B7:4E:9E:E1:AB:E4\r `; - var dummyAnswerSdp = dummyOfferSdp.replace(/a=recvonly/mg, 'a=inactive'); + let dummyAnswerSdp = dummyOfferSdp.replace(/a=recvonly/mg, 'a=inactive'); if (isFirefox) { dummyOfferSdp += 'a=setup:actpass\r\n'; - dummyAnswerSdp.replace(/a=setup:actpass\r/mg, ''); + dummyAnswerSdp = dummyAnswerSdp.replace(/a=setup:actpass\r/mg, ''); } - var test = Object.assign({ + const test = Object.assign({ dummyAnswerSdp: dummyAnswerSdp, dummyOfferSdp: dummyOfferSdp, events: new Map(), @@ -1621,7 +1589,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 }; test.createLocalDescription = function createLocalDesription(sdpType) { - var promise; + let promise; switch (sdpType) { case 'answer': switch (test.peerConnection.signalingState) { @@ -1671,7 +1639,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 }; test.createRemoteDescription = function createRemoteDesription(sdpType) { - var description; + let description; switch (sdpType) { case 'answer': description = new RTCSessionDescription({ @@ -1701,7 +1669,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 }); }; - var events = [ + const events = [ 'iceconnectionstatechange', 'signalingstatechange' ]; @@ -1710,7 +1678,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 if (!test.events.has(event)) { test.events.set(event, []); } - var events = test.events.get(event); + const events = test.events.get(event); test.peerConnection.addEventListener(event, event => events.push(event)); }); @@ -1724,7 +1692,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 test.waitFor = async function waitFor(event) { const timeoutMS = 10 * 1000; - var events = test.events.get(event); + const events = test.events.get(event); if (events.length) { return Promise.resolve(events[0]); } @@ -1751,7 +1719,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 // ensure that events like signalingstatechange are not raised in // response to one of our API calls. setTimeout(() => { - var events = test.events.get(event); + const events = test.events.get(event); if (events.length) { return reject(new Error('Event was raised')); } @@ -1760,7 +1728,7 @@ a=fingerprint:sha-256 0F:F6:1E:6F:88:AC:BA:0F:D1:4D:D7:0C:E2:B7:8E:93:CA:75:C8:8 }); }; - var setup; + let setup; switch (test.signalingState) { case 'closed': setup = test.close().then(() => test.resetEvents()); diff --git a/test/integration/spec/webrtc/rtcsessiondescription.js b/test/integration/spec/webrtc/rtcsessiondescription.js deleted file mode 100644 index 48dce7dbe..000000000 --- a/test/integration/spec/webrtc/rtcsessiondescription.js +++ /dev/null @@ -1,152 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const ChromeRTCSessionDescription = require('../../../../es5/webrtc/rtcsessiondescription/chrome'); -const SessionDescription = require('../../../../es5/webrtc/rtcsessiondescription'); -const { guessBrowser } = require('../../../../es5/webrtc/util'); -const { combinationContext } = require('../../../lib/webrtc/util'); - -const guess = guessBrowser(); -const isChrome = guess === 'chrome'; -const isFirefox = guess === 'firefox'; -const isSafari = guess === 'safari'; - -describe('RTCSessionDescription', () => { - describe('constructor', () => { - var description; - - context('called with an invalid .type', () => { - var descriptionInitDict = { type: 'bogus' }; - testErrorMessages('throws the same error as RTCSessionDescription', - () => new RTCSessionDescription(descriptionInitDict), - () => new SessionDescription(descriptionInitDict)); - }); - - function testErrorMessages(description, fn1, fn2) { - it(description, () => { - var error1; - try { - fn1(); - } catch (error) { - error1 = error; - } - - var error2; - try { - fn2(); - } catch (error) { - error2 = error; - } - - assert(error1 instanceof Error); - assert(error2 instanceof Error); - assert.equal(error1.message, error2.message); - }); - } - - context('called with .type "rollback" and', () => { - combinationContext([ - [ - [true, false], - x => x ? 'an .sdp' : 'no .sdp' - ] - ], ([hasSdp]) => { - const sdp = hasSdp ? 'fake sdp' : null; - - beforeEach(() => { - const descriptionInitDict = { - type: 'rollback' - }; - if (sdp) { - descriptionInitDict.sdp = sdp; - } - description = new SessionDescription(descriptionInitDict); - }); - - testConstructor(); - testRollback(sdp); - }); - }); - - function testConstructor() { - it('returns an instance of SessionDescription', () => { - assert(description instanceof SessionDescription); - }); - } - - function testRollback(sdp) { - it('unwraps to null', () => { - assert.equal(unwrap(description), null); - }); - - it('sets .sdp', () => { - if (!sdp) { - if (description.sdp === '') { - return; - } - } - assert.equal(description.sdp, sdp); - }); - - it('sets .type to "rollback"', () => { - assert.equal(description.type, 'rollback'); - }); - } - - combinationContext([ - [ - ['offer', 'answer', 'pranswer'], - x => `called with .type "${x}" and` - ], - [ - [true, false], - x => x ? 'an .sdp' : 'no .sdp' - ] - ], ([type, hasSdp]) => { - const sdp = 'fake sdp'; - - beforeEach(() => { - const descriptionInitDict = { - type: type - }; - if (hasSdp) { - descriptionInitDict.sdp = sdp; - } - description = new SessionDescription(descriptionInitDict); - }); - - testConstructor(); - testRTCSessionDescription(type, hasSdp ? sdp : null); - }); - - function testRTCSessionDescription(type, sdp) { - if (sdp) { - it('sets .sdp', () => { - assert.equal(description.sdp, sdp); - }); - - if (SessionDescription === ChromeRTCSessionDescription) { - it('sets .sdp on the unwrapped RTCSessionDescription', () => { - assert.equal(unwrap(description).sdp, description.sdp); - }); - } - } - - it('sets .type', () => { - assert.equal(description.type, type); - }); - - if (SessionDescription === ChromeRTCSessionDescription) { - it('sets .type on the unwrapped RTCSessionDescription', () => { - assert.equal(unwrap(description).type, description.type); - }); - } - } - }); -}); - -function unwrap(description) { - return description instanceof ChromeRTCSessionDescription - ? description._description - : null; -} diff --git a/test/lib/mockwebrtc.js b/test/lib/mockwebrtc.js index b53b47027..fcfd6302b 100644 --- a/test/lib/mockwebrtc.js +++ b/test/lib/mockwebrtc.js @@ -3,51 +3,15 @@ const { EventEmitter } = require('events'); const WebSocket = require('ws'); -const util = require('../../es5/util'); +const { + FakeMediaStream: MediaStream, + FakeMediaStreamTrack: MediaStreamTrack +} = require('./fakemediastream'); function Event(type) { this.type = type; } -class MediaStream { - constructor() { - this.ended = false; - this.id = util.makeUUID(); - this._tracks = new Set(); - } - - addTrack(track) { - this._tracks.add(track); - } - - clone() { - return new MediaStream(); - } - - getTracks() { - return Array.from(this._tracks); - } - - getAudioTracks() { - return Array.from(this._tracks).filter(track => track.kind === 'audio'); - } - - getTrackById() { - return null; - } - - getVideoTracks() { - return Array.from(this._tracks).filter(track => track.kind === 'video'); - } - - removeTrack(track) { - this._tracks.delete(track); - } - - stop() { - } -} - const navigator = { userAgent: 'Node' }; @@ -90,6 +54,12 @@ RTCDataChannel.id = 0; const DUMMY_SDP = 'v=0\r\no=- 4676571761825475727 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE audio\r\na=msid-semantic: WMS EMeI3G202R6Q6h3SNWynn4aSHT8JbeeYozwq\r\nm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=ice-ufrag:YDUcqfaDo8TP7sAf\r\na=ice-pwd:6pBfcQxQqfHcUN90IcETG9ag\r\na=ice-options:google-ice\r\na=fingerprint:sha-256 C9:98:D1:85:C6:79:AF:26:76:80:28:B5:19:B3:65:DA:D6:E8:BC:29:6A:48:59:8C:13:06:6C:3B:D3:EE:86:01\r\na=setup:actpass\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 minptime=10\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\na=ssrc:489352021 cname:aDhWDndkoIsLM2YP\r\na=ssrc:489352021 msid:EMeI3G202R6Q6h3SNWynn4aSHT8JbeeYozwq fabea357-f6cf-4967-aa7c-800bedf06927\r\na=ssrc:489352021 mslabel:EMeI3G202R6Q6h3SNWynn4aSHT8JbeeYozwq\r\na=ssrc:489352021 label:fabea357-f6cf-4967-aa7c-800bedf06927\r\n'; +class RTCIceCandidate { + constructor() { + this.candidate = ''; + } +} + class RTCSessionDescription { constructor(init) { init = init || {}; @@ -159,33 +129,10 @@ class RTCPeerConnection extends EventEmitter { }); } - updateIce() { - } - addIceCandidate() { return Promise.resolve(); } - getConfiguration() { - } - - getLocalStreams() { - return []; - } - - getRemoteStreams() { - return []; - } - - getStreamById() { - } - - addStream() { - } - - removeStream() { - } - close() { } @@ -193,18 +140,12 @@ class RTCPeerConnection extends EventEmitter { return new RTCDataChannel(label); } - createDTMFSender() { - } - getState() { } setIdentityProvider() { } - getIdentityAssertion() { - } - getStats() { return Promise.resolve([]); } @@ -244,9 +185,11 @@ function mockWebRTC(_global) { _global.navigator = navigator; _global.webkitMediaStream = MediaStream; _global.MediaStream = MediaStream; + _global.MediaStreamTrack = MediaStreamTrack; _global.RTCDataChannel = RTCDataChannel; _global.RTCRtpTransceiver = RTCRtpTransceiver; _global.RTCPeerConnection = RTCPeerConnection; + _global.RTCIceCandidate = RTCIceCandidate; _global.RTCSessionDescription = RTCSessionDescription; _global.attachMediaStream = attachMediaStream; _global.URL = URL; @@ -261,12 +204,14 @@ module.exports = mockWebRTC; module.exports.WebSocket = WebSocket; module.exports.MediaStream = MediaStream; module.exports.webkitMediaStream = MediaStream; +module.exports.MediaStreamTrack = MediaStreamTrack; module.exports.getUserMedia = getUserMedia; module.exports.navigator = navigator; module.exports.RTCDataChannel = RTCDataChannel; module.exports.RTCRtpTransceiver = RTCRtpTransceiver; module.exports.DUMMY_SDP = DUMMY_SDP; module.exports.RTCPeerConnection = RTCPeerConnection; +module.exports.RTCIceCandidate = RTCIceCandidate; module.exports.RTCSessionDescription = RTCSessionDescription; module.exports.attachMediaStream = attachMediaStream; module.exports.URL = URL; diff --git a/test/unit/spec/util/sdp/issue8329.js b/test/unit/spec/util/sdp/issue8329.js index be2dd5549..037cdfd0d 100644 --- a/test/unit/spec/util/sdp/issue8329.js +++ b/test/unit/spec/util/sdp/issue8329.js @@ -2,10 +2,7 @@ 'use strict'; const assert = require('assert'); -const { RTCSessionDescription } = require('../../../../../lib/webrtc'); - const workaround = require('../../../../../lib/util/sdp/issue8329'); - const { a } = require('../../../../lib/util'); describe('Issue 8329', function() { diff --git a/test/unit/spec/util/support.js b/test/unit/spec/util/support.js index 77f7d9fcb..f1c4ba2e0 100644 --- a/test/unit/spec/util/support.js +++ b/test/unit/spec/util/support.js @@ -2,7 +2,6 @@ const assert = require('assert'); const isSupported = require('../../../../lib/util/support'); -const { clearChromeCachedSdpFormat } = require('../../../../lib/webrtc/util/sdp'); describe('isSupported', () => { let oldAgent; @@ -11,7 +10,6 @@ describe('isSupported', () => { }); afterEach(() => { - clearChromeCachedSdpFormat(); navigator.userAgent = oldAgent; if (global.chrome) { delete global.chrome; @@ -122,7 +120,7 @@ describe('isSupported', () => { if (chrome) { global.chrome = chrome; } - assert.equal(isSupported(), true); + assert.equal(isSupported(false), true); }); }); @@ -209,7 +207,7 @@ describe('isSupported', () => { if (brave) { navigator.brave = brave; } - assert.equal(isSupported(), false); + assert.equal(isSupported(false), false); }); }); @@ -231,7 +229,7 @@ describe('isSupported', () => { it('and RTCPeerConnection.prototype.addTransceiver is not supported', () => { global.RTCPeerConnection.prototype = {}; - assert.equal(isSupported(), false); + assert.equal(isSupported(false), false); }); it('and RTCPeerConnection.prototype.addTransceiver throws an exception', () => { @@ -242,7 +240,7 @@ describe('isSupported', () => { this.close = function() {}; }; global.RTCPeerConnection.prototype.addTransceiver = function() {}; - assert.equal(isSupported(), false); + assert.equal(isSupported(false), false); }); }); @@ -263,12 +261,12 @@ describe('isSupported', () => { it('and RTCRtpTransceiver is not supported', () => { delete global.RTCRtpTransceiver; - assert.equal(isSupported(), false); + assert.equal(isSupported(false), false); }); it('and RTCRtpTransceiver is supported but currentDirection is missing', () => { delete global.RTCRtpTransceiver.prototype.currentDirection; - assert.equal(isSupported(), false); + assert.equal(isSupported(false), false); }); }); }); diff --git a/test/unit/spec/webrtc/util/sdp.js b/test/unit/spec/webrtc/util/sdp.js index a6643cb53..8eb585670 100644 --- a/test/unit/spec/webrtc/util/sdp.js +++ b/test/unit/spec/webrtc/util/sdp.js @@ -4,109 +4,12 @@ const assert = require('assert'); const { makeUUID } = require('../../../../../lib/webrtc/util'); const { - getPlanBTrackIds, - getUnifiedPlanTrackIds, - getPlanBSSRCs, - getUnifiedPlanSSRCs, - updatePlanBTrackIdsToSSRCs, - updateUnifiedPlanTrackIdsToSSRCs + getTrackIds, + getSSRCs, + updateTrackIdsToSSRCs } = require('../../../../../lib/webrtc/util/sdp'); -function makePlanBSDP(version, trackIds, ssrcs) { - return `v=0 -o=- 7286723344670728240 ${version} IN IP4 127.0.0.1 -s=- -t=0 0 -a=group:BUNDLE audio video -a=msid-semantic: WMS 71d61989-ae80-4f93-aa6a-c764957b9785 -m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 -c=IN IP4 0.0.0.0 -a=rtcp:9 IN IP4 0.0.0.0 -a=ice-ufrag:I8XF -a=ice-pwd:V1LUXrmNieMufDUPxWT8eFR0 -a=fingerprint:sha-256 5C:F7:7A:79:A8:60:84:B1:5E:88:43:C0:64:D1:DB:12:06:16:C2:37:4E:A9:EB:26:7F:84:7B:0E:B9:52:A4:8D -a=setup:actpass -a=mid:audio -a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level -a=sendrecv -a=rtcp-mux -a=rtpmap:111 opus/48000/2 -a=rtcp-fb:111 transport-cc -a=fmtp:111 minptime=10;useinbandfec=1 -a=rtpmap:103 ISAC/16000 -a=rtpmap:104 ISAC/32000 -a=rtpmap:9 G722/8000 -a=rtpmap:0 PCMU/8000 -a=rtpmap:8 PCMA/8000 -a=rtpmap:106 CN/32000 -a=rtpmap:105 CN/16000 -a=rtpmap:13 CN/8000 -a=rtpmap:110 telephone-event/48000 -a=rtpmap:112 telephone-event/32000 -a=rtpmap:113 telephone-event/16000 -a=rtpmap:126 telephone-event/8000 -a=ssrc:${ssrcs[0][0]} cname:ZVrerVFXW7ne/tcZ -a=ssrc:${ssrcs[0][0]} msid:71d61989-ae80-4f93-aa6a-c764957b9785 ${trackIds[0]} -a=ssrc:${ssrcs[0][0]} mslabel:71d61989-ae80-4f93-aa6a-c764957b9785 -a=ssrc:${ssrcs[0][0]} label:8c5067ce-869b-4bdc-bf96-6a1c5f9c6ce7 -m=video 9 UDP/TLS/RTP/SAVPF 96 98 100 102 127 97 99 101 125 -c=IN IP4 0.0.0.0 -a=rtcp:9 IN IP4 0.0.0.0 -a=ice-ufrag:I8XF -a=ice-pwd:V1LUXrmNieMufDUPxWT8eFR0 -a=fingerprint:sha-256 5C:F7:7A:79:A8:60:84:B1:5E:88:43:C0:64:D1:DB:12:06:16:C2:37:4E:A9:EB:26:7F:84:7B:0E:B9:52:A4:8D -a=setup:actpass -a=mid:video -a=extmap:2 urn:ietf:params:rtp-hdrext:toffset -a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time -a=extmap:4 urn:3gpp:video-orientation -a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 -a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay -a=sendrecv -a=rtcp-mux -a=rtcp-rsize -a=rtpmap:96 VP8/90000 -a=rtcp-fb:96 ccm fir -a=rtcp-fb:96 nack -a=rtcp-fb:96 nack pli -a=rtcp-fb:96 goog-remb -a=rtcp-fb:96 transport-cc -a=rtpmap:98 VP9/90000 -a=rtcp-fb:98 ccm fir -a=rtcp-fb:98 nack -a=rtcp-fb:98 nack pli -a=rtcp-fb:98 goog-remb -a=rtcp-fb:98 transport-cc -a=rtpmap:100 H264/90000 -a=rtcp-fb:100 ccm fir -a=rtcp-fb:100 nack -a=rtcp-fb:100 nack pli -a=rtcp-fb:100 goog-remb -a=rtcp-fb:100 transport-cc -a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f -a=rtpmap:102 red/90000 -a=rtpmap:127 ulpfec/90000 -a=rtpmap:97 rtx/90000 -a=fmtp:97 apt=96 -a=rtpmap:99 rtx/90000 -a=fmtp:99 apt=98 -a=rtpmap:101 rtx/90000 -a=fmtp:101 apt=100 -a=rtpmap:125 rtx/90000 -a=fmtp:125 apt=102 -a=ssrc-group:FID ${ssrcs[1][0]} ${ssrcs[1][1]} -a=ssrc:${ssrcs[1][0]} cname:ZVrerVFXW7ne/tcZ -a=ssrc:${ssrcs[1][0]} msid:71d61989-ae80-4f93-aa6a-c764957b9785 ${trackIds[1]} -a=ssrc:${ssrcs[1][0]} mslabel:71d61989-ae80-4f93-aa6a-c764957b9785 -a=ssrc:${ssrcs[1][0]} label:${trackIds[1]} -a=ssrc:${ssrcs[1][1]} cname:ZVrerVFXW7ne/tcZ -a=ssrc:${ssrcs[1][1]} msid:71d61989-ae80-4f93-aa6a-c764957b9785 ${trackIds[1]} -a=ssrc:${ssrcs[1][1]} mslabel:71d61989-ae80-4f93-aa6a-c764957b9785 -a=ssrc:${ssrcs[1][1]} label:${trackIds[1]} -`.split('\n').join('\r\n'); -} - -function makeUnifiedPlanSDP(version, trackIds, ssrcs) { +function makeSDP(version, trackIds, ssrcs) { return `v=0 o=mozilla...THIS_IS_SDPARTA-53.0 3990212676194185183 0 IN IP4 0.0.0.0 s=- @@ -175,59 +78,32 @@ function makeSSRC() { return Math.floor(Math.random() * 1e9); } -function makeChromeStyleTrackId() { - return makeUUID(); -} - -function makeFirefoxStyleTrackId() { - return `{${makeChromeStyleTrackId()}}`; +function makeTrackId() { + return `{${makeUUID()}}`; } -[ - [ - 'PlanB', - makePlanBSDP, - getPlanBTrackIds, - getPlanBSSRCs, - updatePlanBTrackIdsToSSRCs - ], - [ - 'UnifiedPlan', - makeUnifiedPlanSDP, - getUnifiedPlanTrackIds, - getUnifiedPlanSSRCs, - updateUnifiedPlanTrackIdsToSSRCs - ] -].forEach(([format, makeSDP, getTrackIds, getSSRCs, updateTrackIdsToSSRCs]) => { - const makeTrackId = format === 'UnifiedPlan' - ? makeFirefoxStyleTrackId - : makeChromeStyleTrackId; - +describe('sdp utils', () => { const trackIds = [makeTrackId(), makeTrackId()]; const audioSSRCs = [makeSSRC()]; - const videoSSRCs = format === 'PlanB' - ? [makeSSRC(), makeSSRC()] - : [makeSSRC()]; + const videoSSRCs = [makeSSRC()]; const ssrcs = [audioSSRCs, videoSSRCs]; const changedAudioSSRCs = [makeSSRC()]; - const changedVideoSSRCs = format === 'PlanB' - ? [makeSSRC(), makeSSRC()] - : [makeSSRC()]; + const changedVideoSSRCs = [makeSSRC()]; const changedSSRCs = [changedAudioSSRCs, changedVideoSSRCs]; const sdp1 = makeSDP(1, trackIds, ssrcs); const sdp2 = makeSDP(2, trackIds, changedSSRCs); - describe(`get${format}TrackIds`, () => { + describe('getTrackIds', () => { it('should return the MediaStreamTrack IDs announced in the SDP', () => { assert.deepEqual([...getTrackIds(sdp1)], trackIds); }); }); - describe(`get${format}SSRCs`, () => { + describe('getSSRCs', () => { it('should return the SSRCs for the given MediaStreamTrack ID as announced in the SDP', () => { trackIds.forEach((trackId, i) => { assert.deepEqual([...getSSRCs(sdp1, trackId)], ssrcs[i]); @@ -235,7 +111,7 @@ function makeFirefoxStyleTrackId() { }); }); - describe(`update${format}TrackIdsToSSRCs`, () => { + describe('updateTrackIdsToSSRCs', () => { let updatedSDP2; beforeEach(() => {