diff --git a/net/webrtc/gstwebrtc-api/index.html b/net/webrtc/gstwebrtc-api/index.html
index e76eaebc..858696c9 100644
--- a/net/webrtc/gstwebrtc-api/index.html
+++ b/net/webrtc/gstwebrtc-api/index.html
@@ -384,6 +384,8 @@
if (session) {
entryElement._consumerSession = session;
+ session.mungeStereoHack = true;
+
session.addEventListener("error", (event) => {
if (entryElement._consumerSession === session) {
console.error(event.message, event.error);
diff --git a/net/webrtc/gstwebrtc-api/src/consumer-session.js b/net/webrtc/gstwebrtc-api/src/consumer-session.js
index bc13da85..7dddd86f 100644
--- a/net/webrtc/gstwebrtc-api/src/consumer-session.js
+++ b/net/webrtc/gstwebrtc-api/src/consumer-session.js
@@ -44,6 +44,7 @@ export default class ConsumerSession extends WebRTCSession {
this._streams = [];
this._remoteController = null;
this._pendingCandidates = [];
+ this._mungeStereoHack = false;
this._offerOptions = offerOptions;
@@ -57,6 +58,16 @@ export default class ConsumerSession extends WebRTCSession {
});
}
+ /**
+ * Defines whether the SDP should be munged in order to enable stereo with chrome.
+ * @param {boolean} enable - Enable or disable the hack, default is false
+ */
+ set mungeStereoHack(enable) {
+ if (typeof (enable) !== "boolean") { return; }
+
+ this._mungeStereoHack = enable;
+ }
+
/**
* The array of remote media streams consumed locally through this WebRTC channel.
* @member {external:MediaStream[]} GstWebRTCAPI.ConsumerSession#streams
@@ -238,6 +249,36 @@ export default class ConsumerSession extends WebRTCSession {
}
}
+ // Work around Chrome not handling stereo Opus correctly.
+ // See
+ // https://chromium.googlesource.com/external/webrtc/+/194e3bcc53ffa3e98045934377726cb25d7579d2/webrtc/media/engine/webrtcvoiceengine.cc#302
+ // https://bugs.chromium.org/p/webrtc/issues/detail?id=8133
+ //
+ // Technically it's against the spec to modify the SDP
+ // but there's no other API for this and this seems to
+ // be the only possible workaround at this time.
+ mungeStereo(offerSdp, answerSdp) {
+ const stereoRegexp = /a=fmtp:.* sprop-stereo/g;
+ let stereoPayloads = new Set();
+ for (const m of offerSdp.matchAll(stereoRegexp)) {
+ const payloadMatch = m[0].match(/a=fmtp:(\d+) .*/);
+ if (payloadMatch) {
+ stereoPayloads.add(payloadMatch[1]);
+ }
+ }
+
+ for (const payload of stereoPayloads) {
+ const isStereoRegexp = new RegExp("a=fmtp:" + payload + ".*stereo");
+ const answerIsStereo = answerSdp.match(isStereoRegexp);
+
+ if (!answerIsStereo) {
+ answerSdp = answerSdp.replaceAll("a=fmtp:" + payload, "a=fmtp:" + payload + " stereo=1;");
+ }
+ }
+
+ return answerSdp;
+ }
+
onSessionPeerMessage(msg) {
if ((this._state === SessionState.closed) || !this._comChannel || !this._sessionId) {
return;
@@ -268,6 +309,10 @@ export default class ConsumerSession extends WebRTCSession {
}
}).then((desc) => {
if (this._rtcPeerConnection && desc) {
+ if (this._mungeStereoHack) {
+ desc.sdp = this.mungeStereo(msg.sdp.sdp, desc.sdp);
+ }
+
return this._rtcPeerConnection.setLocalDescription(desc);
} else {
return null;