| | |
| | | |
| | | .container { |
| | | background-color: #151414; /* 将网格项目的颜色设置为红色背景 */ |
| | | flex: 9; |
| | | border: 1px solid #ccc; |
| | | flex: 30; |
| | | border: 10px solid; |
| | | box-sizing: border-box; |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); /* 默认 2x2 网格 */ |
| | |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | border: 1px solid #ccc; |
| | | border: 2px solid #384551; |
| | | box-sizing: border-box; |
| | | padding: 10px; /* 内边距为 10px */ |
| | | position: relative; /* 添加相对定位 */ |
| | |
| | | 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/0d1c9f80a7b4480c8b401ba6b140b581_1/" 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.12" |
| | | var chanMap = new Map(); |
| | | window.onload = function () { |
| | | changeGrid(2, 2); |
| | | chanMap.set("video1", "http://" + mediamtxHost + ":8889/164/"); |
| | | chanMap.set("video2", "http://" + mediamtxHost + ":8889/165/"); |
| | | chanMap.set("video3", "http://" + mediamtxHost + ":8889/245/"); |
| | | chanMap.set("video4", "http://" + mediamtxHost + ":8889/164/"); |
| | | chanMap.set("video5", "http://" + mediamtxHost + ":8889/165/"); |
| | | chanMap.set("video6", "http://" + mediamtxHost + ":8889/245/"); |
| | | chanMap.set("video7", "http://" + mediamtxHost + ":8889/164/"); |
| | | chanMap.set("video8", "http://" + mediamtxHost + ":8889/165/"); |
| | | chanMap.set("video9", "http://" + mediamtxHost + ":8889/245/"); |
| | | console.log(chanMap); |
| | | } |
| | | 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) => { |
| | |
| | | } |
| | | } |
| | | |
| | | 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> |