From 0bd7a674e9c7834efca00da2a6c5c85726801f7c Mon Sep 17 00:00:00 2001
From: a-wing <1@233.email>
Date: Sun, 7 Jan 2024 00:17:27 +0800
Subject: [PATCH] feat: add webui audio && video codec

---
 assets/index.html | 43 +++++++++++++++++++++++++++++++++------
 assets/sdp.js     | 52 +++++++++++++++++++++++++++++++++++++++++++++++
 assets/whep.js    |  2 +-
 assets/whip.js    |  8 ++++++--
 4 files changed, 96 insertions(+), 9 deletions(-)
 create mode 100644 assets/sdp.js

diff --git a/assets/index.html b/assets/index.html
index 91bcac38..69a52ada 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -27,6 +27,23 @@
         <div style="display: flex;justify-content: space-evenly;flex-wrap: wrap;">
             <fieldset>
                 <legend>WHIP</legend>
+                <section>
+                    Audio Codec: <select id="whip-audio-codec">
+                        <option value="" selected>default</option>
+                        <option value="opus/48000">OPUS</option>
+                        <option value="g722/8000">G722</option>
+                        <option value="pcmu/8000">PCMU</option>
+                        <option value="pcma/8000">PCMA</option>
+                    </select>
+                    Video Codec: <select id="whip-video-codec">
+                        <option value="" selected>default</option>
+                        <option value="av1/90000">AV1</option>
+                        <option value="vp9/90000">VP9</option>
+                        <option value="vp8/90000">VP8</option>
+                        <option value="h264/90000">H264</option>
+                    </select>
+                </section>
+
                 <section>Max Width: <select id="whip-video-width-max">
                     <option value="" selected>Max</option>
                     <option value="3840">3840px</option>
@@ -51,10 +68,10 @@
         Video<br />
         <div id="remoteVideos"></div> <br />
 
-        <script src="./whip.js"></script>
-        <script src="./whep.js"></script>
-
-        <script>
+        <script type="module">
+        import convertSessionDescription from "./sdp.js"
+        import { WHIPClient } from "./whip.js"
+        import { WHEPClient } from "./whep.js"
 
         // Common
         const idResourceId = "resource"
@@ -111,6 +128,8 @@
 
         // WHIP
         const idWhipLayerSelect = "whip-layer-select"
+        const idWhipAudioCodec = "whip-audio-codec"
+        const idWhipVideoCodec = "whip-video-codec"
         const idWhipVideoWidthMax = "whip-video-width-max"
 
         initLayerSelect(idWhipLayerSelect, [
@@ -140,13 +159,23 @@
 
             const layer = getElementValue(idWhipLayerSelect)
             const index = layers.findIndex(i => i.rid === layer)
-            sendEncodings = layers.slice(0 - (layers.length - index))
 
             pc.addTransceiver(stream.getVideoTracks()[0], {
                 direction: 'sendonly',
-                sendEncodings: sendEncodings,
+                sendEncodings: layers.slice(0 - (layers.length - index)),
             })
+
+            const audioCodec = getElementValue(idWhipAudioCodec)
+            document.getElementById(idWhipAudioCodec).disabled = true
+            logWhip(`audio codec: ${!audioCodec ? "default" : audioCodec}`)
+
+            const videoCodec = getElementValue(idWhipVideoCodec)
+            document.getElementById(idWhipVideoCodec).disabled = true
+            logWhip(`video codec: ${!videoCodec ? "default" : videoCodec}`)
+
             const whip = new WHIPClient()
+            whip.onAnswer = answer => convertSessionDescription(answer, audioCodec, videoCodec)
+
             const url = location.origin + "/whip/" + resource
             const token = getElementValue(idBearerToken)
             try {
@@ -158,6 +187,7 @@
 
             document.getElementById(idWhipLayerSelect).disabled = true
         }
+        window.startWhip = startWhip
 
         // WHEP
         const idWhepLayerSelect = "whep-layer-select"
@@ -218,6 +248,7 @@
                 initEvevt()
             }
         }
+        window.startWhep = startWhep
         </script>
     </body>
 </html>
diff --git a/assets/sdp.js b/assets/sdp.js
new file mode 100644
index 00000000..1c5f2303
--- /dev/null
+++ b/assets/sdp.js
@@ -0,0 +1,52 @@
+// @params {string} SDP
+// @params {string} audioCodec
+// @params {string} videoCodec
+// @return {string} SDP
+function convertSessionDescription(sdp, audioCodec, videoCodec) {
+    const sections = sdp.split("m=")
+    for (let i = 0; i < sections.length; i++) {
+        const section = sections[i]
+        if (section.startsWith("audio") && !!audioCodec) {
+            sections[i] = setCodec(section, audioCodec)
+        } else if (section.startsWith("video") && !!videoCodec) {
+            sections[i] = setCodec(section, videoCodec)
+        }
+    }
+    return sections.join("m=")
+}
+
+function setCodec(section, codec) {
+    const lines = section.split("\r\n")
+    const lines2 = []
+    const payloadFormats = []
+    for (const line of lines) {
+        if (!line.startsWith("a=rtpmap:")) {
+            lines2.push(line)
+        } else {
+            if (line.toLowerCase().includes(codec)) {
+                payloadFormats.push(line.slice("a=rtpmap:".length).split(" ")[0])
+                lines2.push(line)
+            }
+        }
+    }
+
+    const lines3 = []
+
+    for (const line of lines2) {
+        if (line.startsWith("a=fmtp:")) {
+            if (payloadFormats.includes(line.slice("a=fmtp:".length).split(" ")[0])) {
+                lines3.push(line)
+            }
+        } else if (line.startsWith("a=rtcp-fb:")) {
+            if (payloadFormats.includes(line.slice("a=rtcp-fb:".length).split(" ")[0])) {
+                lines3.push(line)
+            }
+        } else {
+            lines3.push(line)
+        }
+    }
+
+    return lines3.join("\r\n")
+}
+
+export default convertSessionDescription
diff --git a/assets/whep.js b/assets/whep.js
index 0f784dc6..d22dca9a 100644
--- a/assets/whep.js
+++ b/assets/whep.js
@@ -6,7 +6,7 @@ const Extensions = {
 }
 
 
-class WHEPClient extends EventTarget {
+export class WHEPClient extends EventTarget {
     constructor() {
         super();
         //Ice properties
diff --git a/assets/whip.js b/assets/whip.js
index dbb2673d..fbdb9821 100644
--- a/assets/whip.js
+++ b/assets/whip.js
@@ -1,4 +1,4 @@
-class WHIPClient {
+export class WHIPClient {
     constructor() {
         //Ice properties
         this.iceUsername = null;
@@ -7,6 +7,9 @@ class WHIPClient {
         this.candidates = [];
         this.endOfcandidates = false;
         this.etag = "";
+
+        this.onOffer = offer => offer;
+        this.onAnswer = answer => answer;
     }
 
     async publish(pc, url, token) {
@@ -38,6 +41,7 @@ class WHIPClient {
         }
         //Create SDP offer
         const offer = await pc.createOffer();
+        offer.sdp = this.onOffer(offer.sdp)
 
         //Request headers
         const headers = {
@@ -172,7 +176,7 @@ class WHIPClient {
         //}
 
         //And set remote description
-        await pc.setRemoteDescription({ type: "answer", sdp: answer });
+        await pc.setRemoteDescription({ type: "answer", sdp: this.onAnswer(answer) });
     }
 
     restart() {