Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
====================

Expand Down
102 changes: 71 additions & 31 deletions lib/signaling/v2/peerconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ const {
createCodecMapForMediaSection,
disableRtx,
enableDtxForOpus,
filterLocalCodecs,
getMediaSections,
removeSSRCAttributes,
revertSimulcast,
setCodecPreferences,
setSimulcast
} = require('../../util/sdp');

Expand All @@ -40,7 +38,6 @@ const {

const {
buildLogLevels,
getPlatform,
isChromeScreenShareTrack,
oncePerTick,
defer
Expand All @@ -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';
Expand Down Expand Up @@ -125,7 +120,6 @@ class PeerConnectionV2 extends StateMachine {
offerOptions: {},
revertSimulcast,
sessionTimeout: DEFAULT_SESSION_TIMEOUT_SEC * 1000,
setCodecPreferences,
setSimulcast,
Backoff: DefaultBackoff,
IceConnectionMonitor: DefaultIceConnectionMonitor,
Expand All @@ -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);
Expand Down Expand Up @@ -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
},
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
Loading