From 8db95778fffe41dc9f562a02d1e42dbe2582a18a Mon Sep 17 00:00:00 2001
From: ‘liusuyi’ <1951119284@qq.com>
Date: Thu, 14 Sep 2023 11:44:19 +0800
Subject: [PATCH] 优化流媒体测试页
---
ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java | 7
ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java | 6
ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java | 64 ++++++
ard-work/src/main/resources/templates/mediaMTX.html | 335 +++------------------------------
ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java | 12 +
lib/mediamtx/mediamtx.yml | 2
ard-work/src/main/resources/templates/preview.html | 169 ++++++++--------
7 files changed, 201 insertions(+), 394 deletions(-)
diff --git a/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java b/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
index a4e65f3..48490b4 100644
--- a/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
+++ b/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
@@ -22,7 +22,7 @@
import java.util.stream.Collectors;
/**
- * @Description:
+ * @Description: 流媒体业务
* @ClassName: controller
* @Author: 刘苏义
* @Date: 2023年07月13日9:26
@@ -75,9 +75,7 @@
@PreAuthorize("@ss.hasPermi('media:stream:edit')")
@PutMapping
public AjaxResult edit(@RequestBody StreamInfo streamInfo) {
- mediaService.removePath(new String[]{streamInfo.getName()});
- vtduService.deleteVtduByName(streamInfo.getName());
- Map<String, String> map = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource(), streamInfo.getMode(), streamInfo.getIsCode());
+ Map<String, String> map = mediaService.editPath(streamInfo.getName(), streamInfo.getRtspSource(), streamInfo.getMode(), streamInfo.getIsCode());
Vtdu vtdu = new Vtdu();
vtdu.setName(streamInfo.getName());
vtdu.setSourceUrl(streamInfo.getRtspSource());
@@ -86,6 +84,7 @@
vtdu.setRtspUrl(map.get("rtspUrl"));
vtdu.setRtmpUrl(map.get("rtmpUrl"));
vtdu.setWebrtcUrl(map.get("webrtcUrl"));
+ vtduService.updateVtdu(vtdu);
return AjaxResult.success(map);
}
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java b/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
index 195ab1a..436fe72 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
@@ -16,7 +16,17 @@
* 刘苏义
* 2023/8/12 13:56:52
*/
- Map<String,String> addPath(String name, String rtspPath, String mode, String isCode);
+ Map<String,String> addPath(String name, String sourceUrl, String mode, String isCode);
+ /**
+ * 修改路径
+ * name 名称
+ * rtspPath rtsp地址
+ * mode 模式:实时/按需
+ * isCode 是否转码
+ * 刘苏义
+ * 2023/8/12 13:56:52
+ */
+ Map<String,String> editPath(String name, String sourceUrl, String mode, String isCode);
StreamInfo getPathInfo(String name);
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
index 32fc297..2471f51 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
@@ -131,18 +131,17 @@
//GPU硬解码编码 -hwaccel cuvid -c:v h264_cuvid 使用cuda解码 -c:v h264_nvenc 使用cuda编码
//String cmd = rootPath + "/lib/mediamtx/" + "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport udp -i " + rtspPath + " -c:v h264_nvenc -r 25 -threads 4 -b:v 2048k -bf 0 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
if (isCode.equals("1")) {
- String cmd = rootPath + "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 25 -sc_threshold 0 -threads 6 -b:v 4096k -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+ String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 25 -sc_threshold 0 -threads 6 -b:v 2048k -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
if (!softwareDecoding) {
- cmd = rootPath + "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -threads 6 -b:v 4096k -bf 0 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+ cmd = "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -threads 6 -b:v 2048k -bf 0 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
}
if (mode.equals("0")) {
mediaInfo.setRunondemand(cmd);
mediaInfo.setRunondemandrestart(true);
+ mediaInfo.setRunondemandcloseafter("5s");
} else {
mediaInfo.setRunoninit(cmd);
mediaInfo.setRunoninitrestart(true);
- //mediaInfo.setRunonready(cmd);
- //mediaInfo.setRunonreadyrestart(true);
}
} else {
mediaInfo.setSource(sourceUrl);
@@ -158,6 +157,63 @@
}
@Override
+ public Map<String, String> editPath(String name, String sourceUrl, String mode, String isCode) {
+ String rtspUrl = "rtsp://" + mediamtxHost + ":7554/" + name;
+ String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
+ String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
+
+ Conf mediaInfo = new Conf();
+ String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
+ //-vcodec libx264 //指定视频编码器为 libx264,使用 H.264 编码格式进行视频压缩
+ //-preset ultrafast //--preset的参数主要调节编码速度和质量的平衡,有ultrafast(转码速度最快,视频往往也最模糊)、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项,从快到慢
+ //-r 25 //设置输出视频的帧率为 25 帧/秒
+ //-g 20 //关键帧间隔20
+ //-sc_threshold 0 //将其设置为0(-sc_threshold 0)禁用场景变化检测
+ //-rtsp_transport tcp //这个选项告诉 FFmpeg 使用 TCP 作为 RTSP 的传输协议
+ //-threads 4: 指定要使用的线程数为 4。//这允许 FFmpeg 在多核处理器上使用多个线程来进行视频编码,以加快速度。
+ // -i //用于指定输入媒体文件或输入流的地址
+ // -bf 0 禁用B帧,因为webrtc在网页调用时控制台一直输出 WebRTC doesn’t support H264 streams with B-frames
+ //-f rtsp //这个选项告诉 FFmpeg 输出为 RTSP 格式。
+ //CPU软解码编码
+ //String cmd = rootPath + "/lib/mediamtx/" +"ffmpeg -rtsp_transport tcp -i " + rtspPath + " -vcodec libx264 -preset:v ultrafast -r 25 -threads 4 -b:v 2048k -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+ //GPU硬解码编码 -hwaccel cuvid -c:v h264_cuvid 使用cuda解码 -c:v h264_nvenc 使用cuda编码
+ //String cmd = rootPath + "/lib/mediamtx/" + "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport udp -i " + rtspPath + " -c:v h264_nvenc -r 25 -threads 4 -b:v 2048k -bf 0 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+ if (isCode.equals("1")) {
+ mediaInfo.setSource("publisher");
+ String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 25 -sc_threshold 0 -threads 6 -b:v 2048k -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+ if (!softwareDecoding) {
+ cmd = "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -threads 6 -b:v 2048k -bf 0 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+ }
+ if (mode.equals("0")) {
+ mediaInfo.setRunondemand(cmd);
+ mediaInfo.setRunondemandcloseafter("5s");
+ mediaInfo.setRunondemandrestart(true);
+ mediaInfo.setRunoninit("");
+ mediaInfo.setRunoninitrestart(false);
+ } else {
+ mediaInfo.setRunoninit(cmd);
+ mediaInfo.setRunoninitrestart(true);
+ mediaInfo.setRunondemand("");
+ mediaInfo.setRunondemandrestart(false);
+ }
+ } else {
+ mediaInfo.setSource(sourceUrl);
+ mediaInfo.setRunondemand("");
+ mediaInfo.setRunondemandrestart(false);
+ mediaInfo.setRunoninit("");
+ mediaInfo.setRunoninitrestart(false);
+ }
+ mediaInfo.setMaxReaders(100);
+ mediaInfo.setSourceprotocol("tcp");
+ mediaClient.editPath(name, mediaInfo);
+ Map<String,String> map=new HashMap<>();
+ map.put("rtspUrl",rtspUrl);
+ map.put("rtmpUrl",rtmpUrl);
+ map.put("webrtcUrl",webrtcUrl);
+ return map;
+ }
+
+ @Override
public StreamInfo getPathInfo(String name) {
Items item = mediaClient.getPathInfo(name);
StreamInfo info = new StreamInfo();
diff --git a/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java b/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
index 7103be5..0e39f82 100644
--- a/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
+++ b/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
@@ -18,7 +18,11 @@
*/
@Post("/config/paths/add/{name}")
String addPath( @Var("name") String name, @JSONBody Conf body);
-
+ /**
+ * 修改路径
+ */
+ @Post("/config/paths/edit/{name}")
+ String editPath( @Var("name") String name, @JSONBody Conf body);
/**
* 移除路径
*/
diff --git a/ard-work/src/main/resources/templates/mediaMTX.html b/ard-work/src/main/resources/templates/mediaMTX.html
index 90c51b4..7b1680a 100644
--- a/ard-work/src/main/resources/templates/mediaMTX.html
+++ b/ard-work/src/main/resources/templates/mediaMTX.html
@@ -1,307 +1,40 @@
<!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<html>
<head>
<meta charset="UTF-8">
- <title>测试页</title>
- <script th:src="@{/js/jquery-3.6.4.min.js}"></script>
- <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>
- <script th:src="@{/js/bootstrap.js}"></script>
- <style>
- .video-container {
- display: inline-block;
- vertical-align: top;
- width: 33%; /* 3个视频平均分配一行的宽度 */
- /*padding: 2px; !* 可以根据需要调整内边距 *!*/
- box-sizing: border-box;
- }
-
- .video-container video {
- width: 100%;
- height: 100%;
- /*object-fit: cover; !* 视频填充容器,保持宽高比 *!*/
- }
- </style>
+ <title>video.js播放rtmp流</title>
+ <!--引入播放器样式-->
+ <link href="http://vjs.zencdn.net/5.19/video-js.min.css" rel="stylesheet">
+ <!--引入播放器js-->
+ <script src="http://vjs.zencdn.net/5.19/video.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/npm/videojs-flash@2/dist/videojs-flash.min.js"></script>
+</head>
<body>
-<div>
- <div class="row">
- <div class="video-container">
- <video id="video1" muted autoplay loop controls></video>
- </div>
- <div class="video-container">
- <video id="video2" muted autoplay loop controls></video>
- </div>
- <div class="video-container">
- <video id="video3" muted autoplay loop controls></video>
- </div>
- <div class="video-container">
- <video id="video4" muted autoplay loop controls></video>
- </div>
- <div class="video-container">
- <video id="video5" muted autoplay loop controls></video>
- </div>
- </div>
-</div>
-<script th:inline="javascript">
- var chanMap = new Map();
- window.onload = function () {
-
- 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();
- $('video').click(function (e) {
- let ID = e.target.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);
- }
+<!--vjs-big-play-centered 播放按钮居中-->
+<!--poster默认的显示界面,就是还没点播放,给你显示的界面-->
+<!--controls 规定浏览器应该为视频提供播放控件-->
+<!--preload="auto" 是否提前加载-->
+<!--autoplay 自动播放-->
+<!--loop=true 自动循环-->
+<!--data-setup='{"example_option":true}' 可以把一些属性写到这个里面来,如data-setup={"autoplay":true}-->
+<video id="my-player"
+ class="video-js vjs-default-skin vjs-big-play-centered" controls
+ preload="auto" autoplay="autoplay"
+ poster="images/logo.png" width="500" height="400"
+ data-setup='{}'>
+ <!--src: 规定媒体文件的 URL type:规定媒体资源的类型-->
+ <source src='rtmp://192.168.1.194:1935/164' type='rtmp/flv' />
+</video>
+<script type="text/javascript">
+ // 设置flash路径,用于在videojs发现浏览器不支持HTML5播放器的时候自动唤起flash播放器
+ videojs.options.flash.swf = 'https://cdn.bootcss.com/videojs-swf/5.4.1/video-js.swf';
+ //my-player为页面video元素的id
+ var player = videojs('my-player');
+ //播放
+ player.play();
+ // 1. 播放 player.play()
+ // 2. 停止 player.pause()
+ // 3. 暂停 player.pause()
</script>
</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/ard-work/src/main/resources/templates/preview.html b/ard-work/src/main/resources/templates/preview.html
index 63871f9..fd773b0 100644
--- a/ard-work/src/main/resources/templates/preview.html
+++ b/ard-work/src/main/resources/templates/preview.html
@@ -20,7 +20,7 @@
.container {
background-color: #151414; /* 将网格项目的颜色设置为红色背景 */
- flex: 9;
+ flex: 30;
border: 10px solid;
box-sizing: border-box;
display: grid;
@@ -68,6 +68,7 @@
height: 100%;
object-fit: fill;
}
+
#loadingMessage {
position: absolute;
top: 50%;
@@ -90,87 +91,23 @@
<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.11: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);
@@ -401,36 +338,104 @@
}
}
- 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>
diff --git a/lib/mediamtx/mediamtx.yml b/lib/mediamtx/mediamtx.yml
index df30797..9dfc5b5 100644
--- a/lib/mediamtx/mediamtx.yml
+++ b/lib/mediamtx/mediamtx.yml
@@ -15,7 +15,7 @@
writeTimeout: 10s
# Size of the queue of outgoing packets.
# A higher value allows to increase throughput, a lower value allows to save RAM.
-writeQueueSize: 8192
+writeQueueSize: 512
# Maximum size of outgoing UDP packets.
# This can be decreased to avoid fragmentation on networks with a low UDP MTU.
udpMaxPayloadSize: 1472
--
Gitblit v1.9.3