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