|  |  | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(7, 7)">7x7</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(8, 8)">8x8</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(9, 9)">9x9</button> | 
 |  |  |             <input id="videoUrl" type="text" value="http://192.168.1.227:8889/164/" style="width: 250px"/> | 
 |  |  |             <input id="videoUrl" type="text" value="http://192.168.1.227:8889/0d1c9f80a7b4480c8b401ba6b140b581_1/" style="width: 250px"/> | 
 |  |  |         </div> | 
 |  |  |     </div> | 
 |  |  | </div> | 
 |  |  |  | 
 |  |  | <script> | 
 |  |  |     console.log(RTCRtpReceiver.getCapabilities('video').codecs) | 
 |  |  |     console.log(RTCRtpReceiver.getCapabilities('audio').codecs) | 
 |  |  |     //whep操作方法 | 
 |  |  |     const restartPause = 2000; | 
 |  |  |     const unquoteCredential = (v) => ( | 
 |  |  |         JSON.parse(`"${v}"`) | 
 |  |  |     ); | 
 |  |  |     const linkToIceServers = (links) => ( | 
 |  |  |         (links !== null) ? links.split(', ').map((link) => { | 
 |  |  |             const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i); | 
 |  |  | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     class WHEPClient { | 
 |  |  |         constructor(wurl, videoId) { | 
 |  |  |         constructor(whepUrl, videoId) { | 
 |  |  |             this.video = videoId; | 
 |  |  |             this.url = new URL('whep', wurl); | 
 |  |  |             this.wurl = new URL('whep', whepUrl); | 
 |  |  |             this.pc = null; | 
 |  |  |             this.restartTimeout = null; | 
 |  |  |             this.eTag = ''; | 
 |  |  | 
 |  |  |  | 
 |  |  |         start() { | 
 |  |  |             console.log("requesting ICE servers"); | 
 |  |  |             fetch(this.url, { | 
 |  |  |             fetch(this.wurl, { | 
 |  |  |                 method: 'OPTIONS', | 
 |  |  |             }) | 
 |  |  |                 .then((res) => this.onIceServers(res)) | 
 |  |  | 
 |  |  |  | 
 |  |  |             console.log("sending offer"); | 
 |  |  |  | 
 |  |  |             fetch(this.url, { | 
 |  |  |             fetch(this.wurl, { | 
 |  |  |                 method: 'POST', | 
 |  |  |                 headers: { | 
 |  |  |                     'Content-Type': 'application/sdp', | 
 |  |  | 
 |  |  |                     if (res.status !== 201) { | 
 |  |  |                         throw new Error('bad status code'); | 
 |  |  |                     } | 
 |  |  |                     this.eTag = res.headers.get('E-Tag'); | 
 |  |  |                     // this.eTag = res.headers.get('ETag'); | 
 |  |  |                     this.eTag = res.headers.get("ETag") || res.headers.get('E-Tag'); | 
 |  |  |                     this.wurl = new URL(res.headers.get('location'),  this.wurl.origin).toString(); | 
 |  |  |                     return res.text(); | 
 |  |  |                 }) | 
 |  |  |                 .then((sdp) => this.onRemoteAnswer(new RTCSessionDescription({ | 
 |  |  | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         sendLocalCandidates(candidates) { | 
 |  |  |             fetch(this.url, { | 
 |  |  |             fetch(this.wurl, { | 
 |  |  |                 method: 'PATCH', | 
 |  |  |                 headers: { | 
 |  |  |                     'Content-Type': 'application/trickle-ice-sdpfrag', | 
 |  |  |                     'If-Match': this.eTag, | 
 |  |  |                 }, | 
 |  |  |  | 
 |  |  |                 body: generateSdpFragment(this.offerData, candidates), | 
 |  |  |             }) | 
 |  |  |                 .then((res) => { |