diff --git a/CHANGELOG.md b/CHANGELOG.md index 623c781c2..3f66f99bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Added new telemetry events to enhance monitoring and diagnostics for the followi - [RTCIceGatheringState](https://w3c.github.io/webrtc-pc/#rtcicegatheringstate-enum) - [RTCIceConnectionState](https://w3c.github.io/webrtc-pc/#rtciceconnectionstate-enum) +Changes +------- +- Replaced SDP munging for codec preferences with native `RTCRtpTransceiver.setCodecPreferences()` API. + 2.33.0 (November 6, 2025) ==================== diff --git a/lib/signaling/v2/peerconnection.js b/lib/signaling/v2/peerconnection.js index e79fd6383..ff4f5a6ca 100644 --- a/lib/signaling/v2/peerconnection.js +++ b/lib/signaling/v2/peerconnection.js @@ -23,11 +23,9 @@ const { createCodecMapForMediaSection, disableRtx, enableDtxForOpus, - filterLocalCodecs, getMediaSections, removeSSRCAttributes, revertSimulcast, - setCodecPreferences, setSimulcast } = require('../../util/sdp'); @@ -40,7 +38,6 @@ const { const { buildLogLevels, - getPlatform, isChromeScreenShareTrack, oncePerTick, defer @@ -57,8 +54,6 @@ const workaroundIssue8329 = require('../../util/sdp/issue8329'); const telemetry = require('../../insights/telemetry'); const guess = util.guessBrowser(); -const platform = getPlatform(); -const isAndroid = /android/.test(platform); const isChrome = guess === 'chrome'; const isFirefox = guess === 'firefox'; const isSafari = guess === 'safari'; @@ -125,7 +120,6 @@ class PeerConnectionV2 extends StateMachine { offerOptions: {}, revertSimulcast, sessionTimeout: DEFAULT_SESSION_TIMEOUT_SEC * 1000, - setCodecPreferences, setSimulcast, Backoff: DefaultBackoff, IceConnectionMonitor: DefaultIceConnectionMonitor, @@ -149,7 +143,10 @@ class PeerConnectionV2 extends StateMachine { } const log = options.log ? options.log.createLog('webrtc', this) : new Log('webrtc', this, logLevels, options.loggerName); - const peerConnection = new RTCPeerConnection(configuration, options.chromeSpecificConstraints); + const peerConnection = new RTCPeerConnection(configuration, { + chromeSpecificConstraints: options.chromeSpecificConstraints, + applyCodecPreferences: transceiver => this._applyCodecPreferencesToTransceiver(transceiver) + }); if (options.dummyAudioMediaStreamTrack) { peerConnection.addTrack(options.dummyAudioMediaStreamTrack); @@ -326,15 +323,6 @@ class PeerConnectionV2 extends StateMachine { writable: true, value: new IceBox() }, - _setCodecPreferences: { - // NOTE(mmalavalli): Re-ordering payload types in order to make sure a non-H264 - // preferred codec is selected does not work on Android Firefox due to this behavior: - // https://bugzilla.mozilla.org/show_bug.cgi?id=1683258. So, we work around this by - // not applying any non-H264 preferred video codec. - value: isFirefox && isAndroid && preferredCodecs.video[0] && preferredCodecs.video[0].codec.toLowerCase() !== 'h264' - ? sdp => sdp - : options.setCodecPreferences - }, _setSimulcast: { value: options.setSimulcast }, @@ -586,6 +574,67 @@ class PeerConnectionV2 extends StateMachine { return Promise.all(candidates.map(this._addIceCandidate, this)).then(() => {}); } + /** + * Apply codec preferences to a transceiver. + * @private + * @param {RTCRtpTransceiver} transceiver + * @returns {void} + */ + _applyCodecPreferencesToTransceiver(transceiver) { + if (!transceiver || !transceiver.receiver || !transceiver.receiver.track) { + return; + } + + const kind = transceiver.receiver.track.kind; + const preferredCodecs = kind === 'audio' + ? this._preferredAudioCodecs + : this._preferredVideoCodecs; + + if (!preferredCodecs || preferredCodecs.length === 0) { + return; + } + + try { + const availableCodecs = RTCRtpReceiver.getCapabilities(kind).codecs; + const sortedCodecs = this._sortCodecsByPreference(availableCodecs, preferredCodecs); + transceiver.setCodecPreferences(sortedCodecs); + } catch (error) { + this._log.warn('Failed to apply codec preferences:', error); + } + } + + /** + * Sort codecs by preference. + * @private + * @param {Array} availableCodecs + * @param {Array} preferredCodecs + * @returns {Array} + */ + _sortCodecsByPreference(availableCodecs, preferredCodecs) { + const preferred = []; + const remaining = []; + + const preferredNames = preferredCodecs.map(c => + (typeof c === 'string' ? c : c.codec).toLowerCase() + ); + + availableCodecs.forEach(codec => { + const codecName = codec.mimeType.split('/')[1].toLowerCase(); + const index = preferredNames.indexOf(codecName); + + if (index >= 0) { + if (!preferred[index]) { + preferred[index] = []; + } + preferred[index].push(codec); + } else { + remaining.push(codec); + } + }); + + return preferred.flat().concat(remaining); + } + /** * Add a new RTCRtpTransceiver or update an existing RTCRtpTransceiver for the * given MediaStreamTrack. @@ -613,7 +662,9 @@ class PeerConnectionV2 extends StateMachine { return transceiver; } // NOTE(lrivas): The second argument {} is optional in the specification but required by the Citrix VDI implementations. - return this._peerConnection.addTransceiver(track, {}); + const newTransceiver = this._peerConnection.addTransceiver(track, {}); + this._applyCodecPreferencesToTransceiver(newTransceiver); + return newTransceiver; } /** @@ -1046,6 +1097,8 @@ class PeerConnectionV2 extends StateMachine { } return Promise.all(this._replaceTrackPromises.values()).then(() => { + const transceivers = this._peerConnection.getTransceivers(); + transceivers.forEach(transceiver => this._applyCodecPreferencesToTransceiver(transceiver)); return this._peerConnection.createOffer(offerOptions); }).catch(error => { const errorToThrow = new MediaClientLocalDescFailedError(); @@ -1073,15 +1126,7 @@ class PeerConnectionV2 extends StateMachine { // and PSA: https://groups.google.com/forum/#!searchin/discuss-webrtc/PSA%7Csort:date/discuss-webrtc/jcZO-Wj0Wus/k2XvPCvoAwAJ // Looks like we are not referencing those attributes, but this changes goes ahead and removes them to see if it works. // this also helps reduce bytes on wires - let sdp = removeSSRCAttributes(offer.sdp, ['mslabel', 'label']); - sdp = this._peerConnection.remoteDescription - ? filterLocalCodecs(sdp, this._peerConnection.remoteDescription.sdp) - : sdp; - - let updatedSdp = this._setCodecPreferences( - sdp, - this._preferredAudioCodecs, - this._preferredVideoCodecs); + let updatedSdp = removeSSRCAttributes(offer.sdp, ['mslabel', 'label']); this._shouldOffer = false; if (!this._negotiationRole) { @@ -1226,11 +1271,6 @@ class PeerConnectionV2 extends StateMachine { */ _setRemoteDescription(description) { if (description.sdp) { - description.sdp = this._setCodecPreferences( - description.sdp, - this._preferredAudioCodecs, - this._preferredVideoCodecs); - if (this._shouldApplyDtx) { description.sdp = enableDtxForOpus(description.sdp); } else { diff --git a/lib/util/sdp/index.js b/lib/util/sdp/index.js index 56b6f9a12..58e80cf00 100644 --- a/lib/util/sdp/index.js +++ b/lib/util/sdp/index.js @@ -1,6 +1,5 @@ 'use strict'; -const { difference, flatMap } = require('../'); const setSimulcastInMediaSection = require('./simulcast'); const ptToFixedBitrateAudioCodecName = { @@ -134,20 +133,6 @@ function getPayloadTypesInMediaSection(section) { return matches.slice(1).map(match => parseInt(match, 10)); } -/** - * Create the reordered Codec Payload Types based on the preferred Codec Names. - * @param {Map>} codecMap - Codec Map - * @param {Array} preferredCodecs - Preferred Codecs - * @returns {Array} Reordered Payload Types - */ -function getReorderedPayloadTypes(codecMap, preferredCodecs) { - preferredCodecs = preferredCodecs.map(({ codec }) => codec.toLowerCase()); - const preferredPayloadTypes = flatMap(preferredCodecs, codecName => codecMap.get(codecName) || []); - const remainingCodecs = difference(Array.from(codecMap.keys()), preferredCodecs); - const remainingPayloadTypes = flatMap(remainingCodecs, codecName => codecMap.get(codecName)); - return preferredPayloadTypes.concat(remainingPayloadTypes); -} - /** * Set the given Codec Payload Types in the first line of the given m= section. * @param {Array} payloadTypes - Payload Types @@ -162,41 +147,6 @@ function setPayloadTypesInMediaSection(payloadTypes, section) { return [mLine].concat(otherLines).join('\r\n'); } -/** - * Return a new SDP string with the re-ordered codec preferences. - * @param {string} sdp - * @param {Array} preferredAudioCodecs - If empty, the existing order - * of audio codecs is preserved - * @param {Array} preferredVideoCodecs - If empty, the - * existing order of video codecs is preserved - * @returns {string} Updated SDP string - */ -function setCodecPreferences(sdp, preferredAudioCodecs, preferredVideoCodecs) { - const mediaSections = getMediaSections(sdp); - const session = sdp.split('\r\nm=')[0]; - return [session].concat(mediaSections.map(section => { - // Codec preferences should not be applied to m=application sections. - if (!/^m=(audio|video)/.test(section)) { - return section; - } - const kind = section.match(/^m=(audio|video)/)[1]; - const codecMap = createCodecMapForMediaSection(section); - const preferredCodecs = kind === 'audio' ? preferredAudioCodecs : preferredVideoCodecs; - const payloadTypes = getReorderedPayloadTypes(codecMap, preferredCodecs); - const newSection = setPayloadTypesInMediaSection(payloadTypes, section); - - const pcmaPayloadTypes = codecMap.get('pcma') || []; - const pcmuPayloadTypes = codecMap.get('pcmu') || []; - const fixedBitratePayloadTypes = kind === 'audio' - ? new Set(pcmaPayloadTypes.concat(pcmuPayloadTypes)) - : new Set(); - - return fixedBitratePayloadTypes.has(payloadTypes[0]) - ? newSection.replace(/\r\nb=(AS|TIAS):([0-9]+)/g, '') - : newSection; - })).join('\r\n'); -} - /** * Return a new SDP string with simulcast settings. * @param {string} sdp @@ -222,128 +172,6 @@ function setSimulcast(sdp, trackIdsToAttributes) { })).concat('').join('\r\n'); } -/** - * Get the matching Payload Types in an m= section for a particular peer codec. - * @param {Codec} peerCodec - * @param {PT} peerPt - * @param {Map} codecsToPts - * @param {string} section - * @param {string} peerSection - * @returns {Array} - */ -function getMatchingPayloadTypes(peerCodec, peerPt, codecsToPts, section, peerSection) { - // If there is at most one local Payload Type that matches the remote codec, retain it. - const matchingPts = codecsToPts.get(peerCodec) || []; - if (matchingPts.length <= 1) { - return matchingPts; - } - - // If there are no fmtp attributes for the codec in the peer m= section, then we - // cannot get a match in the m= section. In that case, retain all matching Payload - // Types. - const peerFmtpAttrs = getFmtpAttributesForPt(peerPt, peerSection); - if (!peerFmtpAttrs) { - return matchingPts; - } - - // Among the matched local Payload Types, find the one that matches the remote - // fmtp attributes. - const matchingPt = matchingPts.find(pt => { - const fmtpAttrs = getFmtpAttributesForPt(pt, section); - return fmtpAttrs && Object.keys(peerFmtpAttrs).every(attr => { - return peerFmtpAttrs[attr] === fmtpAttrs[attr]; - }); - }); - - // If none of the matched Payload Types also have matching fmtp attributes, - // then retain all of them, otherwise retain only the Payload Type that - // matches the peer fmtp attributes. - return typeof matchingPt === 'number' ? [matchingPt] : matchingPts; -} - -/** - * Filter codecs in an m= section based on its peer m= section from the other peer. - * @param {string} section - * @param {Map} peerMidsToMediaSections - * @param {Array} codecsToRemove - * @returns {string} - */ -function filterCodecsInMediaSection(section, peerMidsToMediaSections, codecsToRemove) { - // Do nothing if the m= section represents neither audio nor video. - if (!/^m=(audio|video)/.test(section)) { - return section; - } - - // Do nothing if the m= section does not have an equivalent remote m= section. - const mid = getMidForMediaSection(section); - const peerSection = mid && peerMidsToMediaSections.get(mid); - if (!peerSection) { - return section; - } - - // Construct a Map of the peer Payload Types to their codec names. - const peerPtToCodecs = createPtToCodecName(peerSection); - // Construct a Map of the codec names to their Payload Types. - const codecsToPts = createCodecMapForMediaSection(section); - // Maintain a list of non-rtx Payload Types to retain. - let pts = flatMap(Array.from(peerPtToCodecs), ([peerPt, peerCodec]) => - peerCodec !== 'rtx' && !codecsToRemove.includes(peerCodec) - ? getMatchingPayloadTypes( - peerCodec, - peerPt, - codecsToPts, - section, - peerSection) - : []); - - // For each Payload Type that will be retained, retain their corresponding rtx - // Payload Type if present. - const rtxPts = codecsToPts.get('rtx') || []; - // In "a=fmtp: apt=", extract the codec PT associated with rtxPt. - const additionalRtxPts = rtxPts.filter(rtxPt => { - const fmtpAttrs = getFmtpAttributesForPt(rtxPt, section); - return fmtpAttrs && pts.includes(fmtpAttrs.apt); - }); - - // NOTE(lrivas): Retain rtx Payload Types that reference a codec already in `pts`. - // Using an intermediate Set to prevent duplicates from being reintroduced after concatenation. - pts = Array.from(new Set([...pts, ...additionalRtxPts])); - - // Filter out the below mentioned attribute lines in the m= section that do not - // belong to one of the Payload Types that are to be retained. - // 1. "a=rtpmap: " - // 2. "a=rtcp-fb: [ ]*" - // 3. "a=fmtp: =[;=]*" - const lines = section.split('\r\n').filter(line => { - const ptMatches = line.match(/^a=(rtpmap|fmtp|rtcp-fb):(.+) .+$/); - const pt = ptMatches && ptMatches[2]; - return !ptMatches || (pt && pts.includes(parseInt(pt, 10))); - }); - - // Filter the list of Payload Types in the first line of the m= section. - const orderedPts = getPayloadTypesInMediaSection(section).filter(pt => pts.includes(pt)); - - // Ensure only unique Payload Types are retained. - const uniquePayloadTypes = Array.from(new Set(orderedPts)); - - return setPayloadTypesInMediaSection(uniquePayloadTypes, lines.join('\r\n')); -} - -/** - * Filter local codecs based on the remote SDP. - * @param {string} localSdp - * @param {string} remoteSdp - * @returns {string} - Updated local SDP - */ -function filterLocalCodecs(localSdp, remoteSdp) { - const localMediaSections = getMediaSections(localSdp); - const localSession = localSdp.split('\r\nm=')[0]; - const remoteMidsToMediaSections = createMidToMediaSectionMap(remoteSdp); - return [localSession].concat(localMediaSections.map(localSection => { - return filterCodecsInMediaSection(localSection, remoteMidsToMediaSections, []); - })).join('\r\n'); -} - /** * Return a new SDP string after reverting simulcast for non vp8 sections in remote sdp. * @param localSdp - simulcast enabled local sdp @@ -579,9 +407,7 @@ exports.createCodecMapForMediaSection = createCodecMapForMediaSection; exports.createPtToCodecName = createPtToCodecName; exports.disableRtx = disableRtx; exports.enableDtxForOpus = enableDtxForOpus; -exports.filterLocalCodecs = filterLocalCodecs; exports.getMediaSections = getMediaSections; exports.removeSSRCAttributes = removeSSRCAttributes; exports.revertSimulcast = revertSimulcast; -exports.setCodecPreferences = setCodecPreferences; exports.setSimulcast = setSimulcast; diff --git a/lib/webrtc/rtcpeerconnection/chrome.js b/lib/webrtc/rtcpeerconnection/chrome.js index 97d2cf1af..bba60c508 100644 --- a/lib/webrtc/rtcpeerconnection/chrome.js +++ b/lib/webrtc/rtcpeerconnection/chrome.js @@ -23,9 +23,11 @@ const isUnifiedPlan = getSdpFormat() === 'unified'; // 3. Set iceTransportPolicy. // class ChromeRTCPeerConnection extends EventTarget { - constructor(configuration = {}, constraints) { + constructor(configuration = {}, options = {}) { super(); + const constraints = options.chromeSpecificConstraints; + const newConfiguration = Object.assign(configuration.iceTransportPolicy ? { iceTransports: configuration.iceTransportPolicy } : {}, configuration); @@ -40,6 +42,13 @@ class ChromeRTCPeerConnection extends EventTarget { value: new Map(), writable: true }, + _applyCodecPreferences: { + value: options.applyCodecPreferences || (() => {}) + }, + _audioTransceiver: { + value: null, + writable: true + }, _localStream: { value: new MediaStream() }, @@ -70,6 +79,10 @@ class ChromeRTCPeerConnection extends EventTarget { _tracksToSSRCs: { value: new Map(), writable: true + }, + _videoTransceiver: { + value: null, + writable: true } }); @@ -271,6 +284,7 @@ class ChromeRTCPeerConnection extends EventTarget { this._audioTransceiver = isUnifiedPlan ? this.addTransceiver('audio', { direction: 'recvonly' }) : this.addTransceiver('audio'); + this._applyCodecPreferences(this._audioTransceiver); } catch (e) { return Promise.reject(e); } @@ -282,6 +296,7 @@ class ChromeRTCPeerConnection extends EventTarget { this._videoTransceiver = isUnifiedPlan ? this.addTransceiver('video', { direction: 'recvonly' }) : this.addTransceiver('video'); + this._applyCodecPreferences(this._videoTransceiver); } catch (e) { return Promise.reject(e); } diff --git a/lib/webrtc/rtcpeerconnection/firefox.js b/lib/webrtc/rtcpeerconnection/firefox.js index e99780c6e..b8b3a906d 100644 --- a/lib/webrtc/rtcpeerconnection/firefox.js +++ b/lib/webrtc/rtcpeerconnection/firefox.js @@ -27,6 +27,9 @@ const { delegateMethods, interceptEvent, legacyPromise, proxyProperties } = requ // https://bugzilla.mozilla.org/show_bug.cgi?id=1240897 // class FirefoxRTCPeerConnection extends EventTarget { + // NOTE(lrivas): Unlike the Chrome implementation, Firefox's RTCPeerConnection does not + // need a second argument for options and thus we only pass the configuration parameter + // keeping the signature consistent with the native RTCPeerConnection. constructor(configuration) { super(); diff --git a/lib/webrtc/rtcpeerconnection/safari.js b/lib/webrtc/rtcpeerconnection/safari.js index 6dd3c84ba..6a4a6d4a1 100644 --- a/lib/webrtc/rtcpeerconnection/safari.js +++ b/lib/webrtc/rtcpeerconnection/safari.js @@ -13,7 +13,7 @@ const updateTrackIdsToSSRCs = isUnifiedPlan : updatePlanBTrackIdsToSSRCs; class SafariRTCPeerConnection extends EventTarget { - constructor(configuration) { + constructor(configuration, options = {}) { super(); interceptEvent(this, 'datachannel'); @@ -28,6 +28,9 @@ class SafariRTCPeerConnection extends EventTarget { value: new Map(), writable: true }, + _applyCodecPreferences: { + value: options.applyCodecPreferences || (() => {}) + }, _audioTransceiver: { value: null, writable: true @@ -146,6 +149,7 @@ class SafariRTCPeerConnection extends EventTarget { this._audioTransceiver = isUnifiedPlan ? this.addTransceiver('audio', { direction: 'recvonly' }) : this.addTransceiver('audio'); + this._applyCodecPreferences(this._audioTransceiver); } catch (e) { return Promise.reject(e); } @@ -157,6 +161,7 @@ class SafariRTCPeerConnection extends EventTarget { this._videoTransceiver = isUnifiedPlan ? this.addTransceiver('video', { direction: 'recvonly' }) : this.addTransceiver('video'); + this._applyCodecPreferences(this._videoTransceiver); } catch (e) { return Promise.reject(e); } diff --git a/test/integration/spec/connect.js b/test/integration/spec/connect.js index 531e479bb..2f2a1b7a8 100644 --- a/test/integration/spec/connect.js +++ b/test/integration/spec/connect.js @@ -1004,18 +1004,40 @@ describe('connect', function() { before(async () => { [sid, thisRoom, thoseRooms, peerConnections] = await setup({ testOptions }); + + await Promise.all(peerConnections.map(pc => pc.localDescription ? Promise.resolve() : new Promise(resolve => { + pc.addEventListener('signalingstatechange', () => pc.localDescription && resolve()); + }))); }); - it('should apply the codec preferences to all remote descriptions', () => { + it('should apply the codec preferences to local descriptions', () => { flatMap(peerConnections, pc => { - assert(pc.remoteDescription.sdp); - return getMediaSections(pc.remoteDescription.sdp); + assert(pc.localDescription.sdp); + // Preferred codecs are only applied to offer descriptions + if (pc.localDescription.type !== 'offer') { + return []; + } + return getMediaSections(pc.localDescription.sdp); }).forEach(section => { const codecMap = createCodecMapForMediaSection(section); - const expectedPayloadTypes = /m=audio/.test(section) - ? flatMap(testOptions.preferredAudioCodecs, codec => codecMap.get(codec.toLowerCase()) || []) - : flatMap(testOptions.preferredVideoCodecs, codec => codecMap.get((codec.codec || codec).toLowerCase()) || []); - const actualPayloadTypes = getPayloadTypes(section); + const isAudio = /m=audio/.test(section); + + const preferredCodecs = isAudio + ? testOptions.preferredAudioCodecs + : testOptions.preferredVideoCodecs; + + const supportedPreferredCodecs = preferredCodecs.filter(codec => { + const codecName = (codec.codec || codec).toLowerCase(); + return codecMap.has(codecName); + }); + + const expectedPayloadTypes = flatMap(supportedPreferredCodecs, codec => + codecMap.get((codec.codec || codec).toLowerCase()) || [] + ); + const allPayloadTypes = getPayloadTypes(section); + const ptToCodec = createPtToCodecName(section); + const actualPayloadTypes = allPayloadTypes.filter(pt => ptToCodec.get(pt) !== 'rtx'); + expectedPayloadTypes.forEach((expectedPayloadType, i) => assert.equal(expectedPayloadType, actualPayloadTypes[i])); }); }); diff --git a/test/unit/spec/signaling/v2/peerconnection.js b/test/unit/spec/signaling/v2/peerconnection.js index 52c376eb9..3a7d0c256 100644 --- a/test/unit/spec/signaling/v2/peerconnection.js +++ b/test/unit/spec/signaling/v2/peerconnection.js @@ -978,17 +978,7 @@ describe('PeerConnectionV2', () => { sinon.assert.calledOnce(test.pc.createOffer); }); - // NOTE(mroberts): This test should really be extended. Instead of popping - // arguments off of `setCodecPreferences`, we should validate that we - // apply transformed remote SDPs and emit transformed local SDPs. - it('should transform the resulting offer by applying any codec preferences', () => { - const preferredVideoCodecs = test.setCodecPreferences.args[0].pop(); - const preferredAudioCodecs = test.setCodecPreferences.args[0].pop(); - assert.equal(preferredAudioCodecs, test.preferredCodecs.audio); - assert.equal(preferredVideoCodecs, test.preferredCodecs.video); - }); - - it('should call setLocalDescription on the underlying RTCPeerConnection with the transformed offer', () => { + it('should call setLocalDescription on the underlying RTCPeerConnection with the offer', () => { sinon.assert.calledOnce(test.pc.setLocalDescription); sinon.assert.calledWith(test.pc.setLocalDescription, test.offers[expectedOfferIndex]); }); @@ -1523,18 +1513,6 @@ describe('PeerConnectionV2', () => { }); } - // NOTE(mroberts): This test should really be extended. Instead of popping - // arguments off of `setCodecPreferences`, we should validate that we - // apply transformed remote SDPs and emit transformed local SDPs. - function itShouldApplyCodecPreferences() { - it('should apply the specified codec preferences to the remote description', () => { - const preferredVideoCodecs = test.setCodecPreferences.args[0].pop(); - const preferredAudioCodecs = test.setCodecPreferences.args[0].pop(); - assert.equal(preferredAudioCodecs, test.preferredCodecs.audio); - assert.equal(preferredVideoCodecs, test.preferredCodecs.video); - }); - } - function itShouldAnswer() { it('returns a Promise that resolves to undefined', () => { assert(!result); @@ -1567,7 +1545,6 @@ describe('PeerConnectionV2', () => { }); itShouldApplyBandwidthConstraints(); - itShouldApplyCodecPreferences(); itShouldNotSetResolutionScale(); itShouldMaybeSetNetworkPriority(); } @@ -1604,7 +1581,6 @@ describe('PeerConnectionV2', () => { }); itShouldApplyBandwidthConstraints(); - itShouldApplyCodecPreferences(); }); } @@ -1675,7 +1651,6 @@ describe('PeerConnectionV2', () => { }); itShouldApplyBandwidthConstraints(); - itShouldApplyCodecPreferences(); } function itShouldApplyAnswer() { @@ -1717,7 +1692,6 @@ describe('PeerConnectionV2', () => { }); itShouldApplyBandwidthConstraints(); - itShouldApplyCodecPreferences(); } function itShouldCreateOffer() { @@ -1749,8 +1723,6 @@ describe('PeerConnectionV2', () => { it('should leave the underlying RTCPeerConnection in signalingState "have-local-offer"', () => { assert.equal(test.pc.signalingState, 'have-local-offer'); }); - - itShouldApplyCodecPreferences(); } function itShouldEventuallyCreateOffer() { @@ -1797,7 +1769,6 @@ describe('PeerConnectionV2', () => { }); itShouldApplyBandwidthConstraints(); - itShouldApplyCodecPreferences(); }); } @@ -2689,7 +2660,6 @@ function makePeerConnectionV2(options) { options.RTCPeerConnection = options.RTCPeerConnection || RTCPeerConnection; options.isChromeScreenShareTrack = options.isChromeScreenShareTrack || sinon.spy(() => false); options.sessionTimeout = options.sessionTimeout || 100; - options.setCodecPreferences = options.setCodecPreferences || sinon.spy(sdp => sdp); options.preferredCodecs = options.preferredCodecs || { audio: [], video: [] }; options.options = { Backoff: options.Backoff, @@ -2699,8 +2669,7 @@ function makePeerConnectionV2(options) { RTCSessionDescription: identity, isChromeScreenShareTrack: options.isChromeScreenShareTrack, eventObserver: options.eventObserver || { emit: sinon.spy() }, - sessionTimeout: options.sessionTimeout, - setCodecPreferences: options.setCodecPreferences + sessionTimeout: options.sessionTimeout }; if (options.enableDscp !== undefined) { @@ -2986,3 +2955,41 @@ function makePeerConnection(options) { function oneTick() { return new Promise(resolve => setTimeout(resolve)); } + +describe('_sortCodecsByPreference', () => { + it('should prioritize preferred codecs in order, case-insensitively', () => { + const pcv2 = makeTest({ preferredCodecs: { audio: [], video: [] } }).pcv2; + + const availableCodecs = [ + { mimeType: 'audio/opus' }, + { mimeType: 'audio/PCMU' }, + { mimeType: 'audio/PCMA' } + ]; + + // Intentionally mixing case and object/string formats + const preferredCodecs = [ + 'pcmu', + { codec: 'OPUS' } + ]; + + const sorted = pcv2._sortCodecsByPreference(availableCodecs, preferredCodecs); + + assert.equal(sorted[0].mimeType, 'audio/PCMU'); + assert.equal(sorted[1].mimeType, 'audio/opus'); + assert.equal(sorted[2].mimeType, 'audio/PCMA'); + }); + + it('should handle empty preferred codecs gracefully', () => { + const pcv2 = makeTest({ preferredCodecs: { audio: [], video: [] } }).pcv2; + + const availableCodecs = [ + { mimeType: 'audio/opus' }, + { mimeType: 'audio/PCMU' } + ]; + + const preferredCodecs = []; + + const sorted = pcv2._sortCodecsByPreference(availableCodecs, preferredCodecs); + assert.deepEqual(sorted, availableCodecs); + }); +}); diff --git a/test/unit/spec/util/sdp/index.js b/test/unit/spec/util/sdp/index.js index a22933017..b929210b1 100644 --- a/test/unit/spec/util/sdp/index.js +++ b/test/unit/spec/util/sdp/index.js @@ -9,9 +9,7 @@ const { addOrRewriteTrackIds, disableRtx, enableDtxForOpus, - filterLocalCodecs, getMediaSections, - setCodecPreferences, setSimulcast, removeSSRCAttributes, revertSimulcast @@ -20,33 +18,6 @@ const { const { makeSdpForSimulcast, makeSdpWithTracks } = require('../../../../lib/mocksdp'); const { combinationContext } = require('../../../../lib/util'); -describe('setCodecPreferences', () => { - combinationContext([ - [ - ['', 'PCMA,G722'], - x => `when preferredAudioCodecs is ${x ? 'not ' : ''}empty` - ], - [ - ['', 'H264,VP9'], - x => `when preferredVideoCodecs is ${x ? 'not ' : ''}empty` - ] - ], ([preferredAudioCodecs, preferredVideoCodecs]) => { - preferredAudioCodecs = preferredAudioCodecs ? preferredAudioCodecs.split(',').map(codec => ({ codec })) : []; - preferredVideoCodecs = preferredVideoCodecs ? preferredVideoCodecs.split(',').map(codec => ({ codec })) : []; - context(`should ${preferredAudioCodecs.length ? 'update the' : 'preserve the existing'} audio codec order`, () => { - it(`and ${preferredVideoCodecs.length ? 'update the' : 'preserve the existing'} video codec order`, () => { - const expectedAudioCodecIds = preferredAudioCodecs.length - ? ['8', '101', '9', '109', '0'] - : ['109', '9', '0', '8', '101']; - const expectedVideoCodecIds = preferredVideoCodecs.length - ? ['126', '97', '121', '120', '99'] - : ['120', '121', '126', '97', '99']; - itShouldHaveCodecOrder(preferredAudioCodecs, preferredVideoCodecs, expectedAudioCodecIds, expectedVideoCodecIds); - }); - }); - }); -}); - describe('setSimulcast', () => { combinationContext([ [ @@ -143,310 +114,6 @@ describe('setSimulcast', () => { }); }); -describe('filterLocalCodecs', () => { - it('should filter codecs in a local SDP based on those advertised in the remote SDP', () => { - const localSdp = `\ -v=0\r -o=- 6385359508499371184 3 IN IP4 127.0.0.1\r -s=-\r -t=0 0\r -a=group:BUNDLE 0 1 2\r -a=msid-semantic: WMS 7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -m=audio 22602 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r -c=IN IP4 34.203.250.135\r -a=rtcp:9 IN IP4 0.0.0.0\r -a=candidate:2235265311 1 udp 7935 34.203.250.135 22602 typ relay raddr 107.20.226.156 rport 51463 generation 0 network-cost 50\r -a=ice-ufrag:Cmuk\r -a=ice-pwd:qjHlb5sxe0bozbwpRSYqil3v\r -a=ice-options:trickle\r -a=fingerprint:sha-256 BE:29:0C:60:05:B6:6E:E6:EA:A8:28:D5:89:41:F9:5B:22:11:CD:26:01:98:E0:55:9D:FE:C2:F8:EA:4C:17:91\r -a=setup:actpass\r -a=mid:0\r -a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r -a=sendrecv\r -a=rtcp-mux\r -a=rtpmap:111 opus/48000/2\r -a=rtcp-fb:111 transport-cc\r -a=fmtp:111 minptime=10;useinbandfec=1\r -a=rtpmap:103 ISAC/16000\r -a=rtpmap:104 ISAC/32000\r -a=rtpmap:9 G722/8000\r -a=rtpmap:0 PCMU/8000\r -a=rtpmap:8 PCMA/8000\r -a=rtpmap:106 CN/32000\r -a=rtpmap:105 CN/16000\r -a=rtpmap:13 CN/8000\r -a=rtpmap:110 telephone-event/48000\r -a=rtpmap:112 telephone-event/32000\r -a=rtpmap:113 telephone-event/16000\r -a=rtpmap:126 telephone-event/8000\r -m=video 9 UDP/TLS/RTP/SAVPF 96 97 99 101 123 122 107 109 98 100 102 127 125 108 124\r -c=IN IP4 0.0.0.0\r -a=rtcp:9 IN IP4 0.0.0.0\r -a=ice-ufrag:Cmuk\r -a=ice-pwd:qjHlb5sxe0bozbwpRSYqil3v\r -a=ice-options:trickle\r -a=fingerprint:sha-256 BE:29:0C:60:05:B6:6E:E6:EA:A8:28:D5:89:41:F9:5B:22:11:CD:26:01:98:E0:55:9D:FE:C2:F8:EA:4C:17:91\r -a=setup:actpass\r -a=mid:1\r -a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r -a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r -a=extmap:4 urn:3gpp:video-orientation\r -a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r -a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r -a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r -a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r -a=sendrecv\r -a=rtcp-mux\r -a=rtcp-rsize\r -a=rtpmap:96 VP8/90000\r -a=rtcp-fb:96 goog-remb\r -a=rtcp-fb:96 transport-cc\r -a=rtcp-fb:96 ccm fir\r -a=rtcp-fb:96 nack\r -a=rtcp-fb:96 nack pli\r -a=rtpmap:97 rtx/90000\r -a=fmtp:97 apt=96\r -a=rtpmap:99 rtx/90000\r -a=fmtp:99 apt=98\r -a=rtpmap:101 rtx/90000\r -a=fmtp:101 apt=100\r -a=rtpmap:123 rtx/90000\r -a=fmtp:123 apt=102\r -a=rtpmap:122 rtx/90000\r -a=fmtp:122 apt=127\r -a=rtpmap:107 rtx/90000\r -a=fmtp:107 apt=125\r -a=rtpmap:109 rtx/90000\r -a=fmtp:109 apt=108\r -a=rtpmap:98 VP9/90000\r -a=rtcp-fb:98 goog-remb\r -a=rtcp-fb:98 transport-cc\r -a=rtcp-fb:98 ccm fir\r -a=rtcp-fb:98 nack\r -a=rtcp-fb:98 nack pli\r -a=rtpmap:100 H264/90000\r -a=rtcp-fb:100 goog-remb\r -a=rtcp-fb:100 transport-cc\r -a=rtcp-fb:100 ccm fir\r -a=rtcp-fb:100 nack\r -a=rtcp-fb:100 nack pli\r -a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r -a=rtpmap:102 H264/90000\r -a=rtcp-fb:102 goog-remb\r -a=rtcp-fb:102 transport-cc\r -a=rtcp-fb:102 ccm fir\r -a=rtcp-fb:102 nack\r -a=rtcp-fb:102 nack pli\r -a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r -a=rtpmap:127 H264/90000\r -a=rtcp-fb:127 goog-remb\r -a=rtcp-fb:127 transport-cc\r -a=rtcp-fb:127 ccm fir\r -a=rtcp-fb:127 nack\r -a=rtcp-fb:127 nack pli\r -a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032\r -a=rtpmap:125 H264/90000\r -a=rtcp-fb:125 goog-remb\r -a=rtcp-fb:125 transport-cc\r -a=rtcp-fb:125 ccm fir\r -a=rtcp-fb:125 nack\r -a=rtcp-fb:125 nack pli\r -a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032\r -a=rtpmap:108 red/90000\r -a=rtpmap:124 ulpfec/90000\r -a=ssrc-group:FID 0000000000 1111111111\r -a=ssrc:0000000000 cname:s9hDwDQNjISOxWtK\r -a=ssrc:0000000000 msid:7a9d401b-3cf6-4216-b260-78f93ba4c32e d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:0000000000 mslabel:7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -a=ssrc:0000000000 label:d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:1111111111 cname:s9hDwDQNjISOxWtK\r -a=ssrc:1111111111 msid:7a9d401b-3cf6-4216-b260-78f93ba4c32e d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:1111111111 mslabel:7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -a=ssrc:1111111111 label:d8b9a935-da54-4d21-a8de-522c87258244\r -m=video 9 UDP/TLS/RTP/SAVPF 96 97 99 101 123 122 107 109 98 100 102 127 125 108 124\r -c=IN IP4 0.0.0.0\r -a=rtcp:9 IN IP4 0.0.0.0\r -a=ice-ufrag:Cmuk\r -a=ice-pwd:qjHlb5sxe0bozbwpRSYqil3v\r -a=ice-options:trickle\r -a=fingerprint:sha-256 BE:29:0C:60:05:B6:6E:E6:EA:A8:28:D5:89:41:F9:5B:22:11:CD:26:01:98:E0:55:9D:FE:C2:F8:EA:4C:17:91\r -a=setup:actpass\r -a=mid:2\r -a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r -a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r -a=extmap:4 urn:3gpp:video-orientation\r -a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r -a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r -a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r -a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r -a=sendrecv\r -a=rtcp-mux\r -a=rtcp-rsize\r -a=rtpmap:96 VP8/90000\r -a=rtcp-fb:96 goog-remb\r -a=rtcp-fb:96 transport-cc\r -a=rtcp-fb:96 ccm fir\r -a=rtcp-fb:96 nack\r -a=rtcp-fb:96 nack pli\r -a=rtpmap:97 rtx/90000\r -a=fmtp:97 apt=96\r -a=rtpmap:99 rtx/90000\r -a=fmtp:99 apt=98\r -a=rtpmap:101 rtx/90000\r -a=fmtp:101 apt=100\r -a=rtpmap:123 rtx/90000\r -a=fmtp:123 apt=102\r -a=rtpmap:122 rtx/90000\r -a=fmtp:122 apt=127\r -a=rtpmap:107 rtx/90000\r -a=fmtp:107 apt=125\r -a=rtpmap:109 rtx/90000\r -a=fmtp:109 apt=108\r -a=rtpmap:98 VP9/90000\r -a=rtcp-fb:98 goog-remb\r -a=rtcp-fb:98 transport-cc\r -a=rtcp-fb:98 ccm fir\r -a=rtcp-fb:98 nack\r -a=rtcp-fb:98 nack pli\r -a=rtpmap:100 H264/90000\r -a=rtcp-fb:100 goog-remb\r -a=rtcp-fb:100 transport-cc\r -a=rtcp-fb:100 ccm fir\r -a=rtcp-fb:100 nack\r -a=rtcp-fb:100 nack pli\r -a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r -a=rtpmap:102 H264/90000\r -a=rtcp-fb:102 goog-remb\r -a=rtcp-fb:102 transport-cc\r -a=rtcp-fb:102 ccm fir\r -a=rtcp-fb:102 nack\r -a=rtcp-fb:102 nack pli\r -a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r -a=rtpmap:127 H264/90000\r -a=rtcp-fb:127 goog-remb\r -a=rtcp-fb:127 transport-cc\r -a=rtcp-fb:127 ccm fir\r -a=rtcp-fb:127 nack\r -a=rtcp-fb:127 nack pli\r -a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032\r -a=rtpmap:125 H264/90000\r -a=rtcp-fb:125 goog-remb\r -a=rtcp-fb:125 transport-cc\r -a=rtcp-fb:125 ccm fir\r -a=rtcp-fb:125 nack\r -a=rtcp-fb:125 nack pli\r -a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032\r -a=rtpmap:108 red/90000\r -a=rtpmap:124 ulpfec/90000\r -a=ssrc-group:FID 0000000000 1111111111\r -a=ssrc:0000000000 cname:s9hDwDQNjISOxWtK\r -a=ssrc:0000000000 msid:7a9d401b-3cf6-4216-b260-78f93ba4c32e d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:0000000000 mslabel:7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -a=ssrc:0000000000 label:d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:1111111111 cname:s9hDwDQNjISOxWtK\r -a=ssrc:1111111111 msid:7a9d401b-3cf6-4216-b260-78f93ba4c32e d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:1111111111 mslabel:7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -a=ssrc:1111111111 label:d8b9a935-da54-4d21-a8de-522c87258244\r -`; - const remoteSdp = `\ -v=0\r -o=- 6385359508499371184 3 IN IP4 127.0.0.1\r -s=-\r -t=0 0\r -a=group:BUNDLE 0 1\r -a=msid-semantic: WMS 7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -m=audio 22602 UDP/TLS/RTP/SAVPF 111 0\r -c=IN IP4 34.203.250.135\r -a=rtcp:9 IN IP4 0.0.0.0\r -a=candidate:2235265311 1 udp 7935 34.203.250.135 22602 typ relay raddr 107.20.226.156 rport 51463 generation 0 network-cost 50\r -a=ice-ufrag:Cmuk\r -a=ice-pwd:qjHlb5sxe0bozbwpRSYqil3v\r -a=ice-options:trickle\r -a=fingerprint:sha-256 BE:29:0C:60:05:B6:6E:E6:EA:A8:28:D5:89:41:F9:5B:22:11:CD:26:01:98:E0:55:9D:FE:C2:F8:EA:4C:17:91\r -a=setup:actpass\r -a=mid:0\r -a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r -a=recvonly\r -a=rtcp-mux\r -a=rtpmap:111 opus/48000/2\r -a=rtcp-fb:111 transport-cc\r -a=fmtp:111 minptime=10;useinbandfec=1\r -a=rtpmap:0 PCMU/8000\r -m=video 9 UDP/TLS/RTP/SAVPF 99 22\r -c=IN IP4 0.0.0.0\r -a=rtcp:9 IN IP4 0.0.0.0\r -a=ice-ufrag:Cmuk\r -a=ice-pwd:qjHlb5sxe0bozbwpRSYqil3v\r -a=ice-options:trickle\r -a=fingerprint:sha-256 BE:29:0C:60:05:B6:6E:E6:EA:A8:28:D5:89:41:F9:5B:22:11:CD:26:01:98:E0:55:9D:FE:C2:F8:EA:4C:17:91\r -a=setup:actpass\r -a=mid:1\r -a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r -a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r -a=extmap:4 urn:3gpp:video-orientation\r -a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r -a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r -a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r -a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r -a=recvonly\r -a=rtcp-mux\r -a=rtcp-rsize\r -a=rtpmap:99 H264/90000\r -a=rtcp-fb:99 goog-remb\r -a=rtcp-fb:99 transport-cc\r -a=rtcp-fb:99 ccm fir\r -a=rtcp-fb:99 nack\r -a=rtcp-fb:99 nack pli\r -a=fmtp:99 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r -a=rtpmap:22 rtx/90000\r -a=fmtp:22 apt=99\r -a=ssrc-group:FID 0000000000 1111111111\r -a=ssrc:0000000000 cname:s9hDwDQNjISOxWtK\r -a=ssrc:0000000000 msid:7a9d401b-3cf6-4216-b260-78f93ba4c32e d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:0000000000 mslabel:7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -a=ssrc:0000000000 label:d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:1111111111 cname:s9hDwDQNjISOxWtK\r -a=ssrc:1111111111 msid:7a9d401b-3cf6-4216-b260-78f93ba4c32e d8b9a935-da54-4d21-a8de-522c87258244\r -a=ssrc:1111111111 mslabel:7a9d401b-3cf6-4216-b260-78f93ba4c32e\r -a=ssrc:1111111111 label:d8b9a935-da54-4d21-a8de-522c87258244\r -`; - - const filteredLocalSdp = filterLocalCodecs(localSdp, remoteSdp); - const [audioSection, videoSection, newVideoSection] = getMediaSections(localSdp); - const [filteredAudioSection, filteredVideoSection, filteredNewVideoSection] = getMediaSections(filteredLocalSdp); - - [ - ['audio', filteredAudioSection, audioSection, [111, 0], [103, 104, 9, 8, 106, 105, 13, 110, 112, 113, 126]], - ['video', filteredVideoSection, videoSection, [123, 102], [96, 97, 99, 101, 122, 107, 109, 98, 100, 127, 125, 108, 124]] - ].forEach(([kind, filteredSection, section, expectedPtsRetained, expectedPtsFiltered]) => { - const mLineRegex = new RegExp(`^m=${kind} .+ ${expectedPtsRetained.join(' ')}$`, 'm'); - assert(mLineRegex.test(filteredSection)); - - expectedPtsRetained.forEach(pt => { - ['rtpmap', 'rtcp-fb', 'fmtp'].forEach(attr => { - const attrRegex = new RegExp(`^a=${attr}:${pt} (.+)$`, 'm'); - const match = section.match(attrRegex); - const filteredMatch = filteredSection.match(attrRegex); - assert.equal(!!filteredMatch, !!match); - if (filteredMatch) { - assert.equal(filteredMatch[1], match[1]); - } - }); - }); - - expectedPtsFiltered.forEach(pt => { - ['rtpmap', 'rtcp-fb', 'fmtp'].forEach(attr => { - const attrRegex = new RegExp(`^a=${attr}:${pt} .+$`, 'm'); - assert(!attrRegex.test(filteredSection)); - }); - }); - - assert.equal(filteredNewVideoSection, newVideoSection); - }); - }); -}); - describe('revertSimulcast', () => { combinationContext([ [ @@ -477,10 +144,11 @@ describe('revertSimulcast', () => { audio: ['audio-1'], video: [{ id: 'video-1', ssrc: ssrcs[0] }] }); + // NOTE(lrivas): Manually set codec order to test revertSimulcast behavior if (isVP8PreferredPayloadType) { - remoteSdp = setCodecPreferences(sdp, [{ codec: 'PCMU' }], [{ codec: 'VP8' }]); + remoteSdp = remoteSdp.replace(/m=video (\d+) [^\r]+/, 'm=video $1 UDP/TLS/RTP/SAVPF 120 121 126 97'); } else { - remoteSdp = setCodecPreferences(sdp, [{ codec: 'PCMU' }], [{ codec: 'H264' }]); + remoteSdp = remoteSdp.replace(/m=video (\d+) [^\r]+/, 'm=video $1 UDP/TLS/RTP/SAVPF 126 121 120 97'); } revertedSdp = revertSimulcast(simSdp, sdp, remoteSdp, revertForAll); }); @@ -945,16 +613,3 @@ a=rtpmap:126 telephone-event/8000\r }); }); -function itShouldHaveCodecOrder(preferredAudioCodecs, preferredVideoCodecs, expectedAudioCodecIds, expectedVideoCodecIds) { - const sdp = makeSdpWithTracks({ - audio: ['audio-1', 'audio-2'], - video: ['video-1', 'video-2'] - }); - const modifiedSdp = setCodecPreferences(sdp, preferredAudioCodecs, preferredVideoCodecs); - modifiedSdp.split('\r\nm=').slice(1).forEach(section => { - const kind = section.split(' ')[0]; - const expectedCodecIds = kind === 'audio' ? expectedAudioCodecIds : expectedVideoCodecIds; - const codecIds = section.split('\r\n')[0].match(/([0-9]+)/g).slice(1); - assert.equal(codecIds.join(' '), expectedCodecIds.join(' ')); - }); -}