summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/tests/mochitests/simulcast.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/media/webrtc/tests/mochitests/simulcast.js
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/media/webrtc/tests/mochitests/simulcast.js232
1 files changed, 232 insertions, 0 deletions
diff --git a/dom/media/webrtc/tests/mochitests/simulcast.js b/dom/media/webrtc/tests/mochitests/simulcast.js
new file mode 100644
index 0000000000..0af36478c4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/simulcast.js
@@ -0,0 +1,232 @@
+"use strict";
+/* Helper functions to munge SDP and split the sending track into
+ * separate tracks on the receiving end. This can be done in a number
+ * of ways, the one used here uses the fact that the MID and RID header
+ * extensions which are used for packet routing share the same wire
+ * format. The receiver interprets the rids from the sender as mids
+ * which allows receiving the different spatial resolutions on separate
+ * m-lines and tracks.
+ */
+
+// Borrowed from wpt, with some dependencies removed.
+
+const ridExtensions = [
+ "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
+ "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
+];
+
+function ridToMid(description, rids) {
+ const sections = SDPUtils.splitSections(description.sdp);
+ const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
+ const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
+ const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
+ const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
+ const directionValue =
+ description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
+ "a=sendrecv";
+ const mline = SDPUtils.parseMLine(sections[1]);
+
+ // Skip mid extension; we are replacing it with the rid extmap
+ rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
+ ext => ext.uri != "urn:ietf:params:rtp-hdrext:sdes:mid"
+ );
+
+ for (const ext of rtpParameters.headerExtensions) {
+ if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") {
+ ext.uri = "urn:ietf:params:rtp-hdrext:sdes:mid";
+ }
+ }
+
+ // Filter rtx as we have no way to (re)interpret rrid.
+ // Not doing this makes probing use RTX, it's not understood and ramp-up is slower.
+ rtpParameters.codecs = rtpParameters.codecs.filter(
+ c => c.name.toUpperCase() !== "RTX"
+ );
+
+ if (!rids) {
+ rids = Array.from(description.sdp.matchAll(/a=rid:(.*) send/g)).map(
+ r => r[1]
+ );
+ }
+
+ let sdp =
+ SDPUtils.writeSessionBoilerplate() +
+ SDPUtils.writeDtlsParameters(dtls, setupValue) +
+ SDPUtils.writeIceParameters(ice) +
+ "a=group:BUNDLE " +
+ rids.join(" ") +
+ "\r\n";
+ const baseRtpDescription = SDPUtils.writeRtpDescription(
+ mline.kind,
+ rtpParameters
+ );
+ for (const rid of rids) {
+ sdp +=
+ baseRtpDescription +
+ "a=mid:" +
+ rid +
+ "\r\n" +
+ "a=msid:rid-" +
+ rid +
+ " rid-" +
+ rid +
+ "\r\n";
+ sdp += directionValue + "\r\n";
+ }
+ return sdp;
+}
+
+function midToRid(description, localDescription, rids) {
+ const sections = SDPUtils.splitSections(description.sdp);
+ const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
+ const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
+ const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
+ const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
+ const directionValue =
+ description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
+ "a=sendrecv";
+ const mline = SDPUtils.parseMLine(sections[1]);
+
+ // Skip rid extensions; we are replacing them with the mid extmap
+ rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
+ ext => !ridExtensions.includes(ext.uri)
+ );
+
+ for (const ext of rtpParameters.headerExtensions) {
+ if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:mid") {
+ ext.uri = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
+ }
+ }
+
+ const localMid = localDescription
+ ? SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1])
+ : "0";
+
+ if (!rids) {
+ rids = [];
+ for (let i = 1; i < sections.length; i++) {
+ rids.push(SDPUtils.getMid(sections[i]));
+ }
+ }
+
+ let sdp =
+ SDPUtils.writeSessionBoilerplate() +
+ SDPUtils.writeDtlsParameters(dtls, setupValue) +
+ SDPUtils.writeIceParameters(ice) +
+ "a=group:BUNDLE " +
+ localMid +
+ "\r\n";
+ sdp += SDPUtils.writeRtpDescription(mline.kind, rtpParameters);
+ // Although we are converting mids to rids, we still need a mid.
+ // The first one will be consistent with trickle ICE candidates.
+ sdp += "a=mid:" + localMid + "\r\n";
+ sdp += directionValue + "\r\n";
+
+ for (const rid of rids) {
+ const stringrid = String(rid); // allow integers
+ const choices = stringrid.split(",");
+ choices.forEach(choice => {
+ sdp += "a=rid:" + choice + " recv\r\n";
+ });
+ }
+ if (rids.length) {
+ sdp += "a=simulcast:recv " + rids.join(";") + "\r\n";
+ }
+
+ return sdp;
+}
+
+async function doOfferToSendSimulcast(offerer, answerer) {
+ await offerer.setLocalDescription();
+
+ // Is this a renegotiation? If so, we cannot remove (or reorder!) any mids,
+ // even if some rids have been removed or reordered.
+ let mids = [];
+ if (answerer.localDescription) {
+ // Renegotiation. Mids must be the same as before, because renegotiation
+ // can never remove or reorder mids, nor can it expand the simulcast
+ // envelope.
+ mids = [...answerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
+ e => e[1]
+ );
+ } else {
+ // First negotiation; the mids will be exactly the same as the rids
+ const simulcastAttr = offerer.localDescription.sdp.match(
+ /a=simulcast:send (.*)/
+ );
+ if (simulcastAttr) {
+ mids = simulcastAttr[1].split(";");
+ }
+ }
+
+ const nonSimulcastOffer = ridToMid(offerer.localDescription, mids);
+ await answerer.setRemoteDescription({
+ type: "offer",
+ sdp: nonSimulcastOffer,
+ });
+}
+
+async function doAnswerToRecvSimulcast(offerer, answerer, rids) {
+ await answerer.setLocalDescription();
+ const simulcastAnswer = midToRid(
+ answerer.localDescription,
+ offerer.localDescription,
+ rids
+ );
+ await offerer.setRemoteDescription({ type: "answer", sdp: simulcastAnswer });
+}
+
+async function doOfferToRecvSimulcast(offerer, answerer, rids) {
+ await offerer.setLocalDescription();
+ const simulcastOffer = midToRid(
+ offerer.localDescription,
+ answerer.localDescription,
+ rids
+ );
+ await answerer.setRemoteDescription({ type: "offer", sdp: simulcastOffer });
+}
+
+async function doAnswerToSendSimulcast(offerer, answerer) {
+ await answerer.setLocalDescription();
+
+ // See which mids the offerer had; it will barf if we remove or reorder them
+ const mids = [...offerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
+ e => e[1]
+ );
+
+ const nonSimulcastAnswer = ridToMid(answerer.localDescription, mids);
+ await offerer.setRemoteDescription({
+ type: "answer",
+ sdp: nonSimulcastAnswer,
+ });
+}
+
+async function doOfferToSendSimulcastAndAnswer(offerer, answerer, rids) {
+ await doOfferToSendSimulcast(offerer, answerer);
+ await doAnswerToRecvSimulcast(offerer, answerer, rids);
+}
+
+async function doOfferToRecvSimulcastAndAnswer(offerer, answerer, rids) {
+ await doOfferToRecvSimulcast(offerer, answerer, rids);
+ await doAnswerToSendSimulcast(offerer, answerer);
+}
+
+// This would be useful for cases other than simulcast, but we do not use it
+// anywhere else right now, nor do we have a place for wpt-friendly helpers at
+// the moment.
+function createPlaybackElement(track) {
+ const elem = document.createElement(track.kind);
+ elem.autoplay = true;
+ elem.srcObject = new MediaStream([track]);
+ elem.id = track.id;
+ return elem;
+}
+
+async function getPlaybackWithLoadedMetadata(track) {
+ const elem = createPlaybackElement(track);
+ return new Promise(resolve => {
+ elem.addEventListener("loadedmetadata", () => {
+ resolve(elem);
+ });
+ });
+}