From 296bc1c12ed1cff4839a6387757845c98379c273 Mon Sep 17 00:00:00 2001 From: ‘liusuyi’ <1951119284@qq.com> Date: 星期四, 31 八月 2023 16:52:02 +0800 Subject: [PATCH] 流媒体优化 --- ard-work/src/main/java/com/ruoyi/device/hiksdk/controller/SdkController.java | 4 ard-work/src/main/resources/templates/preview.html | 397 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 401 insertions(+), 0 deletions(-) diff --git a/ard-work/src/main/java/com/ruoyi/device/hiksdk/controller/SdkController.java b/ard-work/src/main/java/com/ruoyi/device/hiksdk/controller/SdkController.java index 2053d50..a0f602b 100644 --- a/ard-work/src/main/java/com/ruoyi/device/hiksdk/controller/SdkController.java +++ b/ard-work/src/main/java/com/ruoyi/device/hiksdk/controller/SdkController.java @@ -55,6 +55,10 @@ sdk.loginAll(); } + @RequestMapping("/preview") + private String preview() { + return "preview"; + } @RequestMapping("/index") private String index() { return "test"; diff --git a/ard-work/src/main/resources/templates/preview.html b/ard-work/src/main/resources/templates/preview.html new file mode 100644 index 0000000..9915e66 --- /dev/null +++ b/ard-work/src/main/resources/templates/preview.html @@ -0,0 +1,397 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>鑷�傚簲瀹瑰櫒绀轰緥</title> + <script th:src="@{/js/jquery-3.6.4.min.js}"></script> + <style> + body, html { + margin: 0; + padding: 0; + height: 100%; + } + + .container-wrapper { + display: flex; + flex-direction: column; + height: 100%; + } + + .container { + background-color: #151414; /* 灏嗙綉鏍奸」鐩殑棰滆壊璁剧疆涓虹孩鑹茶儗鏅� */ + flex: 9; + border: 1px solid #ccc; + box-sizing: border-box; + display: grid; + grid-template-columns: repeat(2, 1fr); /* 榛樿 2x2 缃戞牸 */ + grid-gap: 10px; + overflow: hidden; /* 闃叉瑙嗛婧㈠嚭瀹瑰櫒 */ + } + + .grid-item { + background-color: #151414; /* 灏嗙綉鏍奸」鐩殑棰滆壊璁剧疆涓虹孩鑹茶儗鏅� */ + text-align: center; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid #ccc; + box-sizing: border-box; + padding: 10px; /* 鍐呰竟璺濅负 10px */ + position: relative; /* 娣诲姞鐩稿瀹氫綅 */ + } + + .video-container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; /* 闃叉瑙嗛婧㈠嚭瀹瑰櫒 */ + position: absolute; /* 娣诲姞缁濆瀹氫綅 */ + top: 0; + left: 0; + right: 0; + bottom: 0; + } + + .container2 { + flex: 1; + border: 1px solid #ccc; + padding: 10px; + box-sizing: border-box; + } + + /* 淇敼 video 鏍峰紡 */ + video { + width: 100%; + height: 100%; + object-fit: fill; + } + </style> +</head> +<body> +<div class="container-wrapper"> + <div class="container" id="gridContainer"> + <!-- 缃戞牸椤圭洰灏嗙敱 JavaScript 鍔ㄦ�佺敓鎴� --> + </div> + <div class="container2"> + <div class="button-container"> + <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> + </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; + videoContainer.appendChild(video); + gridItem.appendChild(videoContainer); + gridContainer.appendChild(gridItem); + + video.addEventListener('loadedmetadata', function () { + adjustGridItemSize(gridItem, gridItem.videoWidth, gridItem.videoHeight); + }); + console.log(video.id) + } + + gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; + } + + + var chanMap = new Map(); + window.onload = function () { + changeGrid(2, 2); + chanMap.set("video1", "http://127.0.0.1:8889/245/"); + chanMap.set("video2", "http://127.0.0.1:8889/164/"); + chanMap.set("video3", "http://127.0.0.1:8889/164/"); + chanMap.set("video4", "http://127.0.0.1:8889/165/"); + chanMap.set("video5", "http://127.0.0.1:8889/165/"); + console.log(chanMap); + } + const linkToIceServers = (links) => ( + (links !== null) ? links.split(', ').map((link) => { + const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i); + const ret = { + urls: [m[1]], + }; + + if (m[3] !== undefined) { + ret.username = unquoteCredential(m[3]); + ret.credential = unquoteCredential(m[4]); + ret.credentialType = "password"; + } + + return ret; + }) : [] + ); + const parseOffer = (offer) => { + const ret = { + iceUfrag: '', + icePwd: '', + medias: [], + }; + + for (const line of offer.split('\r\n')) { + if (line.startsWith('m=')) { + ret.medias.push(line.slice('m='.length)); + } else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) { + ret.iceUfrag = line.slice('a=ice-ufrag:'.length); + } else if (ret.icePwd === '' && line.startsWith('a=ice-pwd:')) { + ret.icePwd = line.slice('a=ice-pwd:'.length); + } + } + + return ret; + }; + const generateSdpFragment = (offerData, candidates) => { + const candidatesByMedia = {}; + for (const candidate of candidates) { + const mid = candidate.sdpMLineIndex; + if (candidatesByMedia[mid] === undefined) { + candidatesByMedia[mid] = []; + } + candidatesByMedia[mid].push(candidate); + } + + let frag = 'a=ice-ufrag:' + offerData.iceUfrag + '\r\n' + + 'a=ice-pwd:' + offerData.icePwd + '\r\n'; + + let mid = 0; + + for (const media of offerData.medias) { + if (candidatesByMedia[mid] !== undefined) { + frag += 'm=' + media + '\r\n' + + 'a=mid:' + mid + '\r\n'; + + for (const candidate of candidatesByMedia[mid]) { + frag += 'a=' + candidate.candidate + '\r\n'; + } + } + mid++; + } + + return frag; + } + + class WHEPClient { + constructor(wurl, videoId) { + this.video = videoId; + this.url = new URL('whep', wurl); + this.pc = null; + this.restartTimeout = null; + this.eTag = ''; + this.queuedCandidates = []; + this.start(); + } + + start() { + console.log("requesting ICE servers"); + fetch(this.url, { + method: 'OPTIONS', + }) + .then((res) => this.onIceServers(res)) + .catch((err) => { + console.log('error: ' + err); + this.scheduleRestart(); + }); + } + + onIceServers(res) { + this.pc = new RTCPeerConnection({ + iceServers: linkToIceServers(res.headers.get('Link')), + }); + + const direction = "sendrecv"; + this.pc.addTransceiver("video", {direction}); + this.pc.addTransceiver("audio", {direction}); + + this.pc.onicecandidate = (evt) => this.onLocalCandidate(evt); + this.pc.oniceconnectionstatechange = () => this.onConnectionState(); + + this.pc.ontrack = (evt) => { + console.log("new track:", evt.track.kind); + document.getElementById(this.video).srcObject = evt.streams[0]; + }; + + this.pc.createOffer() + .then((offer) => this.onLocalOffer(offer)); + } + + onLocalOffer(offer) { + this.offerData = parseOffer(offer.sdp); + this.pc.setLocalDescription(offer); + + console.log("sending offer"); + + fetch(this.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/sdp', + }, + body: offer.sdp, + }) + .then((res) => { + if (res.status !== 201) { + throw new Error('bad status code'); + } + this.eTag = res.headers.get('E-Tag'); + return res.text(); + }) + .then((sdp) => this.onRemoteAnswer(new RTCSessionDescription({ + type: 'answer', + sdp, + }))) + .catch((err) => { + console.log('error: ' + err); + this.scheduleRestart(); + }); + } + + onConnectionState() { + if (this.restartTimeout !== null) { + return; + } + + console.log("peer connection state:", this.pc.iceConnectionState); + + switch (this.pc.iceConnectionState) { + case "disconnected": + this.scheduleRestart(); + } + } + + onRemoteAnswer(answer) { + if (this.restartTimeout !== null) { + return; + } + + this.pc.setRemoteDescription(new RTCSessionDescription(answer)); + + if (this.queuedCandidates.length !== 0) { + this.sendLocalCandidates(this.queuedCandidates); + this.queuedCandidates = []; + } + } + + onLocalCandidate(evt) { + if (this.restartTimeout !== null) { + return; + } + + if (evt.candidate !== null) { + if (this.eTag === '') { + this.queuedCandidates.push(evt.candidate); + } else { + this.sendLocalCandidates([evt.candidate]) + } + } + } + + sendLocalCandidates(candidates) { + fetch(this.url, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/trickle-ice-sdpfrag', + 'If-Match': this.eTag, + }, + body: generateSdpFragment(this.offerData, candidates), + }) + .then((res) => { + if (res.status !== 204) { + throw new Error('bad status code'); + } + }) + .catch((err) => { + console.log('error: ' + err); + this.scheduleRestart(); + }); + } + + scheduleRestart() { + if (this.restartTimeout !== null) { + return; + } + + if (this.pc !== null) { + this.pc.close(); + this.pc = null; + } + + this.restartTimeout = window.setTimeout(() => { + this.restartTimeout = null; + this.start(); + }, restartPause); + + this.eTag = ''; + this.queuedCandidates = []; + } + + stop() { + if (this.pc) { + try { + this.pc.close(); + } catch (e) { + console.log("Failure close peer connection:" + e); + } + this.pc = null; + } + } + } + + let videoMap = new Map(); + $(document).on('click', 'video', function() { + let ID = this.id;//鑾峰彇褰撳墠鐐瑰嚮浜嬩欢鐨勫厓绱� + console.log(ID); + console.log(videoMap); + if (videoMap.get(ID) != null) { + closeVideo(ID); + } else { + let stream = chanMap.get(ID); + let client = new WHEPClient(stream, ID); + videoMap.set(ID, client); + } + }); + + function closeVideo(id) { + console.log("鍏抽棴" + id) + let client = videoMap.get(id); + client.stop(id); + videoMap.delete(id); + } + function closeAllVideo(){ + videoMap.forEach((val,key) => { + console.log(val,key); + val.stop(key); + videoMap.delete(key); + }) + } + +</script> +</body> +</html> -- Gitblit v1.9.3