I created a video-call + multiple screen sharing app that works well in every case except one.
The main flow is:
The webcam flow always works well, while the screen sharing breaks in a specific case:
Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to parse SessionDescription. a=rtpmap:127 H264/90000 Duplicate payload type with conflicting codec name or clock rate
Looking at the offer's SDP, i see that the payload for the line a=rtpmap:127
is duplicated:
a=rtpmap:127 H264/90000
...
a=rtpmap:127 rtx/90000
This error happens ONLY in the specified flow (Chrome sends an offer first, Firefox answers (and camera works well), Firefox share a screen and i get the SDP error). If the first offer is sent by a Firefox user, everything works well. If the first offer is sent by a Chrome, everything works well if the first screen sharing is started by a Chrome user.
If the first offer is sent by a Chrome user and then a Firefox user shares a screen, it breaks. Only in this case.
So, the problem is that the offer created by the Firefox user during the first screen sharing contains that payload conflict.
Why does it happen (only in that case) and how can i prevent this error?
I also run into this bug and i didn't found a solution other than modifying the SDP description by myself. Although this is a rough workaround, it fix the codec collision bug.
You want to remove the duplicate codec number form the video track (127 here):
m=video 9 UDP/TLS/RTP/SAVPF 100 101 127 127 ...
then remove the payload of the RTX codec.
a=rtpmap:127 RTX/90000
...
Keep in mind that RTX is used to resend corrupted packages
RTX stands for retransmission. RTX
RTP retransmission is an effective packet loss recovery technique for real-time applications with relaxed delay bounds rfc4588
So removing it from payload may void this capability.
Here my JS (typescript) function
removeDuplicateCodecs(sdp: string): string {
// split sdp for each video track
const lines = sdp.split(/^(?=m=video)/gm);
for (let i = 0, videosLength = lines.length; i < videosLength; i++) {
if (lines[i].startsWith("m=video")) {
// split each line
let rows = lines[i].split(/\r\n/gm);
const codecDuplicated: string[]= [];
if (rows.length) {
// take first row and get all codecs
const duplicates = rows[0].match(/(\b\d+\b)(?=.*\b\1\b)/g);
if (duplicates?.length) {
duplicates.forEach(duplicate => {
// remove duplicates from row
rows[0] = rows[0].replace(` ${duplicate}`, '')
codecDuplicated.push(duplicate);
});
}
}
// join back all rows
lines[i] = rows.join('\r\n');
// split by rtpmap
rows = lines[i].split(/^(?=a=rtpmap:)/gm);
if (rows) {
codecDuplicated.forEach(duplicate => {
// remove duplicate codec definitions rows
rows = rows.filter(row => !row.startsWith(`a=rtpmap:${duplicate} rtx`));
});
lines[i] = rows.join('');
}
}
}
return lines.join('');
}