|  |  | 
 |  |  |  | 
 |  |  |         .container { | 
 |  |  |             background-color: #151414; /* 将网格项目的颜色设置为红色背景 */ | 
 |  |  |             flex: 9; | 
 |  |  |             flex: 30; | 
 |  |  |             border: 10px solid; | 
 |  |  |             box-sizing: border-box; | 
 |  |  |             display: grid; | 
 |  |  | 
 |  |  |             height: 100%; | 
 |  |  |             object-fit: fill; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         #loadingMessage { | 
 |  |  |             position: absolute; | 
 |  |  |             top: 50%; | 
 |  |  | 
 |  |  |     <div id="loadingMessage">正在取流</div> | 
 |  |  |     <div class="container2"> | 
 |  |  |         <div class="button-container"> | 
 |  |  |             <button class="toggle-button" onclick="closeAllVideo()">关闭</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(1, 1)">1x1</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(2, 2)">2x2</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(3, 3)">3x3</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(4, 4)">4x4</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(5, 5)">5x5</button> | 
 |  |  |             <button class="toggle-button" onclick="changeGrid(6, 6)">6x6</button> | 
 |  |  |             <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"/> | 
 |  |  |         </div> | 
 |  |  |     </div> | 
 |  |  | </div> | 
 |  |  |  | 
 |  |  | <script> | 
 |  |  |     function calculateAspectRatio(videoWidth, videoHeight) { | 
 |  |  |         return videoWidth / videoHeight; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     function adjustGridItemSize(gridItem, videoWidth, videoHeight) { | 
 |  |  |         const aspectRatio = calculateAspectRatio(videoWidth, videoHeight); | 
 |  |  |         gridItem.style.aspectRatio = aspectRatio; /* 设置宽高比 */ | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     function changeGrid(rows, cols) { | 
 |  |  |         closeAllVideo(); | 
 |  |  |         const gridContainer = document.getElementById('gridContainer'); | 
 |  |  |         gridContainer.innerHTML = ''; | 
 |  |  |  | 
 |  |  |         for (let i = 1; i <= rows * cols; i++) { | 
 |  |  |             const gridItem = document.createElement('div'); | 
 |  |  |             gridItem.className = 'grid-item'; | 
 |  |  |             const videoContainer = document.createElement('div'); | 
 |  |  |             videoContainer.className = 'video-container'; | 
 |  |  |             const video = document.createElement('video'); | 
 |  |  |             video.id = "video" + i; | 
 |  |  |             video.controls = true; | 
 |  |  |             video.autoplay = true; | 
 |  |  |             video.muted = true; | 
 |  |  |             video.loop= true; | 
 |  |  |  | 
 |  |  |             videoContainer.appendChild(video); | 
 |  |  |             gridItem.appendChild(videoContainer); | 
 |  |  |             gridContainer.appendChild(gridItem); | 
 |  |  |  | 
 |  |  |             video.addEventListener('loadedmetadata', function () { | 
 |  |  |                 adjustGridItemSize(gridItem, gridItem.videoWidth, gridItem.videoHeight); | 
 |  |  |             }); | 
 |  |  |             video.addEventListener("click", function () { | 
 |  |  |                 loadingMessage.style.display = "block"; | 
 |  |  |                 video.play().then(function () { | 
 |  |  |                     loadingMessage.style.display = "none"; | 
 |  |  |                 }).catch(function (error) { | 
 |  |  |                     console.error("Error playing the video:", error); | 
 |  |  |                     loadingMessage.style.display = "none"; | 
 |  |  |                 }); | 
 |  |  |             }); | 
 |  |  |             console.log(video.id) | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     let mediamtxHost = "192.168.1.227" | 
 |  |  |     var chanMap = new Map(); | 
 |  |  |     window.onload = function () { | 
 |  |  |         changeGrid(2, 2); | 
 |  |  |         chanMap.set("video1", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video2", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video3", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video4", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video5", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video6", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video7", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video8", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video9", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video10", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video11", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video12", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video13", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video14", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video15", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |         chanMap.set("video16", "http://" + mediamtxHost + ":8889/164/"); | 
 |  |  |  | 
 |  |  |         console.log(chanMap); | 
 |  |  |     } | 
 |  |  |     //whep操作方法 | 
 |  |  |     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'); | 
 |  |  |                     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', | 
 |  |  | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     let videoMap = new Map(); | 
 |  |  |     //初始化加载 | 
 |  |  |     var chanMap = new Map(); | 
 |  |  |     window.onload = function () { | 
 |  |  |         let videoUrl = $("#videoUrl").val(); | 
 |  |  |         for (let i = 1; i < 82; i++) { | 
 |  |  |             chanMap.set("video" + i, videoUrl); | 
 |  |  |         } | 
 |  |  |         console.log(chanMap); | 
 |  |  |         changeGrid(2, 2); | 
 |  |  |     } | 
 |  |  |     //绑定点击事件 | 
 |  |  |     let playMap = new Map(); | 
 |  |  |     $(document).on('click', 'video', function () { | 
 |  |  |         let ID = this.id;//获取当前点击事件的元素 | 
 |  |  |         console.log(ID); | 
 |  |  |         console.log(videoMap); | 
 |  |  |         if (videoMap.get(ID) != null) { | 
 |  |  |         console.log(playMap); | 
 |  |  |         if (playMap.get(ID) != null) { | 
 |  |  |             closeVideo(ID); | 
 |  |  |         } else { | 
 |  |  |             let stream = chanMap.get(ID); | 
 |  |  |             let client = new WHEPClient(stream, ID); | 
 |  |  |             videoMap.set(ID, client); | 
 |  |  |             playMap.set(ID, client); | 
 |  |  |         } | 
 |  |  |     }); | 
 |  |  |  | 
 |  |  |     //关闭一个video | 
 |  |  |     function closeVideo(id) { | 
 |  |  |         console.log("关闭" + id) | 
 |  |  |         let client = videoMap.get(id); | 
 |  |  |         let client = playMap.get(id); | 
 |  |  |         client.stop(id); | 
 |  |  |         videoMap.delete(id); | 
 |  |  |         playMap.delete(id); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     //关闭所有video | 
 |  |  |     function closeAllVideo() { | 
 |  |  |         videoMap.forEach((val, key) => { | 
 |  |  |             console.log(val, key); | 
 |  |  |         playMap.forEach((val, key) => { | 
 |  |  |             val.stop(key); | 
 |  |  |             videoMap.delete(key); | 
 |  |  |             closeVideo(key); | 
 |  |  |             playMap.delete(key); | 
 |  |  |         }) | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     //动态改变grid | 
 |  |  |     function calculateAspectRatio(videoWidth, videoHeight) { | 
 |  |  |         return videoWidth / videoHeight; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     function adjustGridItemSize(gridItem, videoWidth, videoHeight) { | 
 |  |  |         const aspectRatio = calculateAspectRatio(videoWidth, videoHeight); | 
 |  |  |         gridItem.style.aspectRatio = aspectRatio; /* 设置宽高比 */ | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     function changeGrid(rows, cols) { | 
 |  |  |         closeAllVideo(); | 
 |  |  |         let num = rows * cols; | 
 |  |  |         let videoUrl = $("#videoUrl").val(); | 
 |  |  |         for (let i = 1; i < num + 1; i++) { | 
 |  |  |             chanMap.set("video" + i, videoUrl); | 
 |  |  |         } | 
 |  |  |         const gridContainer = document.getElementById('gridContainer'); | 
 |  |  |         gridContainer.innerHTML = ''; | 
 |  |  |  | 
 |  |  |         for (let i = 1; i <= rows * cols; i++) { | 
 |  |  |             const gridItem = document.createElement('div'); | 
 |  |  |             gridItem.className = 'grid-item'; | 
 |  |  |             const videoContainer = document.createElement('div'); | 
 |  |  |             videoContainer.className = 'video-container'; | 
 |  |  |             const video = document.createElement('video'); | 
 |  |  |             video.id = "video" + i; | 
 |  |  |             video.controls = true; | 
 |  |  |             video.autoplay = true; | 
 |  |  |             video.muted = true; | 
 |  |  |             video.loop = true; | 
 |  |  |  | 
 |  |  |             videoContainer.appendChild(video); | 
 |  |  |             gridItem.appendChild(videoContainer); | 
 |  |  |             gridContainer.appendChild(gridItem); | 
 |  |  |  | 
 |  |  |             video.addEventListener('loadedmetadata', function () { | 
 |  |  |                 adjustGridItemSize(gridItem, gridItem.videoWidth, gridItem.videoHeight); | 
 |  |  |             }); | 
 |  |  |             video.addEventListener("click", function () { | 
 |  |  |                 loadingMessage.style.display = "block"; | 
 |  |  |                 video.play().then(function () { | 
 |  |  |                     loadingMessage.style.display = "none"; | 
 |  |  |                 }).catch(function (error) { | 
 |  |  |                     console.error("Error playing the video:", error); | 
 |  |  |                     loadingMessage.style.display = "none"; | 
 |  |  |                 }); | 
 |  |  |             }); | 
 |  |  |             console.log(video.id) | 
 |  |  |             let stream = chanMap.get(video.id); | 
 |  |  |             let client = new WHEPClient(stream, video.id); | 
 |  |  |             playMap.set(video.id, client); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; | 
 |  |  |     } | 
 |  |  | </script> | 
 |  |  | </body> | 
 |  |  | </html> |