From 76817b8c752b12030ab285bcb5b2effebfa9a248 Mon Sep 17 00:00:00 2001
From: ‘liusuyi’ <1951119284@qq.com>
Date: 星期六, 26 八月 2023 17:29:14 +0800
Subject: [PATCH] 流媒体增加webrtc和rtmp协议推拉流

---
 ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java            |   24 +++
 ard-work/src/main/resources/templates/test.html                             |   33 +++
 ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java      |   31 ++-
 ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java              |   24 ++
 ard-work/src/main/resources/static/js/WHEPClient.js                         |   76 +++++++++
 ard-work/src/main/java/com/ruoyi/media/domain/StreamInfo.java               |   12 +
 ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java           |   10 
 ard-work/src/main/resources/static/js/negotiateConnectionWithClientOffer.js |   80 ++++++++++
 ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java              |   24 +++
 lib/mediamtx/mediamtx.yml                                                   |    4 
 ard-work/src/main/java/com/ruoyi/media/service/impl/MediaService.java       |  157 ++++++++++++++-----
 11 files changed, 408 insertions(+), 67 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 1ec2e16..ca50877 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
@@ -17,6 +17,9 @@
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @Description:
@@ -38,17 +41,16 @@
     @PreAuthorize("@ss.hasPermi('media:stream:add')")
     @ApiOperationSupport(includeParameters = {"streamInfo.name", "streamInfo.rtspSource", "streamInfo.mode"})
     public AjaxResult addPath(@RequestBody StreamInfo streamInfo) {
-        if(StringUtils.isEmpty(streamInfo.getName()))
-        {
+        if (StringUtils.isEmpty(streamInfo.getName())) {
             return AjaxResult.error("閫氶亾鍚嶇О涓嶈兘涓虹┖");
         }
-        if(StringUtils.isEmpty(streamInfo.getRtspSource()))
-        {
+        if (StringUtils.isEmpty(streamInfo.getRtspSource())) {
             return AjaxResult.error("rtsp鍦板潃涓嶈兘涓虹┖");
         }
-        String rtsp = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource(), streamInfo.getMode(),streamInfo.getIsCode());
+        String rtsp = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource(), streamInfo.getMode(), streamInfo.getIsCode());
         return AjaxResult.success(rtsp);
     }
+
     /**
      * 鑾峰彇杞爜璇︾粏淇℃伅
      */
@@ -57,6 +59,7 @@
     public AjaxResult getInfo(@PathVariable("name") String name) {
         return success(mediaService.getPathInfo(name));
     }
+
     /**
      * 淇敼杞爜
      */
@@ -65,14 +68,14 @@
     @PutMapping
     public AjaxResult edit(@RequestBody StreamInfo streamInfo) {
         mediaService.removePath(new String[]{streamInfo.getName()});
-        String rtsp = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource(), streamInfo.getMode(),streamInfo.getIsCode());
+        String rtsp = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource(), streamInfo.getMode(), streamInfo.getIsCode());
         return AjaxResult.success(rtsp);
     }
 
     @DeleteMapping("/path/{names}")
     @PreAuthorize("@ss.hasPermi('media:stream:remove')")
     @ApiOperation("绉婚櫎杞爜")
-    public AjaxResult removePath( @PathVariable String[] names) {
+    public AjaxResult removePath(@PathVariable String[] names) {
         mediaService.removePath(names);
         return AjaxResult.success();
     }
@@ -128,7 +131,19 @@
     @PreAuthorize("@ss.hasPermi('media:stream:remove')")
     @DeleteMapping("/{id}")
     public AjaxResult removePullStreamSession(@PathVariable String id) {
-        return AjaxResult.success(mediaService.kickRtspSession(id));
+        List<StreamInfo> pullStreamList = mediaService.getPullStreamList();
+        StreamInfo streamInfo = pullStreamList.stream()
+                .filter(object -> object.getId().equals(id))
+                .collect(Collectors.toList()).get(0);
+        switch (streamInfo.getSessionType()) {
+            case "rtsp":
+                return AjaxResult.success(mediaService.kickRtspSession(id));
+            case "webrtc":
+                return AjaxResult.success(mediaService.kickWebrtcSession(id));
+            case "rtmp":
+                return AjaxResult.success(mediaService.kickRtmpSession(id));
+        }
+        return AjaxResult.error();
     }
 
     @PreAuthorize("@ss.hasPermi('media:stream:list')")
diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java b/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java
new file mode 100644
index 0000000..8969464
--- /dev/null
+++ b/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java
@@ -0,0 +1,24 @@
+package com.ruoyi.media.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @Description:
+ * @ClassName: WebrtcSession
+ * @Author: 鍒樿嫃涔�
+ * @Date: 2023骞�08鏈�26鏃�10:16:21
+ **/
+@Data
+public class RtmpSession {
+    private String name;
+    private String id;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date created;
+    private String remoteAddr;
+    private String state;
+    private long bytesReceived;
+    private long bytesSent;
+}
diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/StreamInfo.java b/ard-work/src/main/java/com/ruoyi/media/domain/StreamInfo.java
index b705a5c..c605658 100644
--- a/ard-work/src/main/java/com/ruoyi/media/domain/StreamInfo.java
+++ b/ard-work/src/main/java/com/ruoyi/media/domain/StreamInfo.java
@@ -33,6 +33,14 @@
      */
     String rtspUrl;
     /**
+     * rtmp鎾斁鍦板潃
+     */
+    String rtmpUrl;
+    /**
+     * webrtc鎾斁鍦板潃
+     */
+    String webrtcUrl;
+    /**
      * 浼犺緭鏂瑰紡
      */
     String protocol;
@@ -67,5 +75,9 @@
      * 鏄惁杞爜
      */
     String isCode;
+    /**
+     * 濯掍綋绫诲瀷锛坵ebRTCSession/rtspSession锛�
+     */
+    String sessionType;
 
 }
diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java b/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java
new file mode 100644
index 0000000..5e27c27
--- /dev/null
+++ b/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java
@@ -0,0 +1,24 @@
+package com.ruoyi.media.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @Description:
+ * @ClassName: WebrtcSession
+ * @Author: 鍒樿嫃涔�
+ * @Date: 2023骞�08鏈�26鏃�10:16:21
+ **/
+@Data
+public class WebrtcSession {
+    private String name;
+    private String id;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date created;
+    private String remoteAddr;
+    private String state;
+    private long bytesReceived;
+    private long bytesSent;
+}
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 28b5780..bd99555 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
@@ -1,8 +1,6 @@
 package com.ruoyi.media.service;
 
-import com.ruoyi.media.domain.Items;
-import com.ruoyi.media.domain.RtspSession;
-import com.ruoyi.media.domain.StreamInfo;
+import com.ruoyi.media.domain.*;
 
 import java.util.List;
 
@@ -29,7 +27,8 @@
     public List<Items> rtspsessions();
 
     RtspSession getRtspSessionById(String sessionId);
-
+    WebrtcSession getWebrtcSessionById(String sessionId);
+    RtmpSession getRtmpSessionById(String sessionId);
     List<RtspSession> getPushStreams();
 
     List<RtspSession> getPullStreams();
@@ -39,5 +38,6 @@
     List<StreamInfo> getPullStreamList();
 
     Boolean kickRtspSession(String sessionId);
-
+    Boolean kickRtmpSession(String sessionId);
+    Boolean kickWebrtcSession(String sessionId);
 }
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaService.java b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaService.java
index 578291c..090961e 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaService.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaService.java
@@ -130,9 +130,7 @@
         if (matcher.find()) {
             info.setRtspSource(matcher.group());
             info.setIsCode("1");
-        }
-        else
-        {
+        } else {
             info.setRtspSource(item.getConf().getSource());
             info.setIsCode("0");
         }
@@ -212,6 +210,20 @@
     }
 
     @Override
+    public WebrtcSession getWebrtcSessionById(String sessionId) {
+        String list = mediaClient.getWebrtcsessionById(sessionId);
+        WebrtcSession webrtcSession = JSONObject.parseObject(list, WebrtcSession.class);
+        return webrtcSession;
+    }
+
+    @Override
+    public RtmpSession getRtmpSessionById(String sessionId) {
+        String list = mediaClient.getRtmpsessionById(sessionId);
+        RtmpSession rtmpSession = JSONObject.parseObject(list, RtmpSession.class);
+        return rtmpSession;
+    }
+
+    @Override
     public List<RtspSession> getPushStreams() {
         List<RtspSession> rtspSessions = new ArrayList<>();
 
@@ -255,11 +267,17 @@
             //ID
             String name = item.getName();
             info.setName(name);
+            //RTMP鎾斁鍦板潃
+            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
+            info.setRtmpUrl(rtmpUrl);
             //RTSP鎾斁鍦板潃
             String rtspUrl = "rtsp://" + mediamtxHost + ":7554/" + name;
             info.setRtspUrl(rtspUrl);
+            //WEBRTC鎾斁鍦板潃
+            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
+            info.setWebrtcUrl(webrtcUrl);
             Source source = item.getSource();
-            if (source==null||source.getId().equals("")) {
+            if (source == null || source.getId().equals("")) {
                 //浼氳瘽ID
                 info.setId("0");
                 //涓婅娴侀噺
@@ -296,18 +314,10 @@
             Matcher matcher = pattern.matcher(runoninit);
             if (matcher.find()) {
                 info.setRtspSource(matcher.group());
-            }
-            else
-            {
+            } else {
                 info.setRtspSource(item.getConf().getSource());
             }
             //浼犺緭鍗忚
-//            regex = "-rtsp_transport\\s+(\\w+)";
-//            pattern = Pattern.compile(regex);
-//            matcher = pattern.matcher(runoninit);
-//            if (matcher.find()) {
-//                info.setProtocol(matcher.group(1));
-//            }
             info.setProtocol(item.getConf().getSourceprotocol());
             //鎷夋祦鏁伴噺
             List<Readers> readers = item.getReaders();
@@ -332,38 +342,81 @@
                 //ID
                 String name = item.getName();
                 info.setName(name);
-                //RTSP鎾斁鍦板潃
-                String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
-                info.setRtspUrl(rtspUrl);
-                RtspSession rtspSession = getRtspSessionById(reader.getId());
-                //浼氳瘽ID
-                info.setId(rtspSession.getId());
-                //寮�濮嬫媺娴佹椂闂�
-                info.setBeginTime(rtspSession.getCreated());
-                //涓婅娴侀噺
-                long bytesReceived = rtspSession.getBytesReceived();
-                String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
-                info.setUpTraffic(formatReceivedSize);
-                //涓嬭娴侀噺
-                long bytesSent = rtspSession.getBytesSent();
-                String formatSentSize = ArdTool.formatFileSize(bytesSent);
-                info.setDownTraffic(formatSentSize);
-
                 //浼犺緭鍗忚
-//                String runoninit = item.getConf().getRunondemand();
-//                String regex = "-rtsp_transport\\s+(\\w+)";
-//                Pattern pattern = Pattern.compile(regex);
-//                Matcher matcher = pattern.matcher(runoninit);
-//                if (matcher.find()) {
-//                    info.setProtocol(matcher.group(1));
-//                }
                 info.setProtocol(item.getConf().getSourceprotocol());
-                //鎷夋祦鏈嶅姟鍣�
-                info.setRemoteAddr(rtspSession.getRemoteAddr());
-                PullStreamInfoList.add(info);
+
+                String type = reader.getType();
+                switch (type) {
+                    case "rtmpConn":
+                        info.setSessionType("rtmp");
+                        //webrtc鎾斁鍦板潃
+                        String url = "rtmp://" + mediamtxHost + ":1935/" + name;
+                        info.setRtspUrl(url);
+                        RtmpSession rtmpSession = getRtmpSessionById(reader.getId());
+                        //浼氳瘽ID
+                        info.setId(rtmpSession.getId());
+                        //寮�濮嬫媺娴佹椂闂�
+                        info.setBeginTime(rtmpSession.getCreated());
+                        //涓婅娴侀噺
+                        long bytesReceived = rtmpSession.getBytesReceived();
+                        String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
+                        info.setUpTraffic(formatReceivedSize);
+                        //涓嬭娴侀噺
+                        long bytesSent = rtmpSession.getBytesSent();
+                        String formatSentSize = ArdTool.formatFileSize(bytesSent);
+                        info.setDownTraffic(formatSentSize);
+                        //鎷夋祦鏈嶅姟鍣�
+                        info.setRemoteAddr(rtmpSession.getRemoteAddr());
+                        PullStreamInfoList.add(info);
+                        break;
+                    case "webRTCSession":
+                        info.setSessionType("webrtc");
+                        //webrtc鎾斁鍦板潃
+                        url = "http://" + mediamtxHost + ":8889/" + name;
+                        info.setRtspUrl(url);
+                        WebrtcSession webrtcSession = getWebrtcSessionById(reader.getId());
+                        //浼氳瘽ID
+                        info.setId(webrtcSession.getId());
+                        //寮�濮嬫媺娴佹椂闂�
+                        info.setBeginTime(webrtcSession.getCreated());
+                        //涓婅娴侀噺
+                         bytesReceived = webrtcSession.getBytesReceived();
+                         formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
+                        info.setUpTraffic(formatReceivedSize);
+                        //涓嬭娴侀噺
+                         bytesSent = webrtcSession.getBytesSent();
+                         formatSentSize = ArdTool.formatFileSize(bytesSent);
+                        info.setDownTraffic(formatSentSize);
+                        //鎷夋祦鏈嶅姟鍣�
+                        info.setRemoteAddr(webrtcSession.getRemoteAddr());
+                        PullStreamInfoList.add(info);
+                        break;
+                    case "rtspSession":
+                        info.setSessionType("rtsp");
+                        //RTSP鎾斁鍦板潃
+                        String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
+                        info.setRtspUrl(rtspUrl);
+                        RtspSession rtspSession = getRtspSessionById(reader.getId());
+                        //浼氳瘽ID
+                        info.setId(rtspSession.getId());
+                        //寮�濮嬫媺娴佹椂闂�
+                        info.setBeginTime(rtspSession.getCreated());
+                        //涓婅娴侀噺
+                        bytesReceived = rtspSession.getBytesReceived();
+                        formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
+                        info.setUpTraffic(formatReceivedSize);
+                        //涓嬭娴侀噺
+                        bytesSent = rtspSession.getBytesSent();
+                        formatSentSize = ArdTool.formatFileSize(bytesSent);
+                        info.setDownTraffic(formatSentSize);
+                        //鎷夋祦鏈嶅姟鍣�
+                        info.setRemoteAddr(rtspSession.getRemoteAddr());
+                        PullStreamInfoList.add(info);
+                        break;
+                }
             }
         }
-        Comparator<StreamInfo> comparator = Comparator.comparing(streamInfo ->streamInfo.getBeginTime() ); // 浣跨敤Collections.sort鏂规硶杩涜鎺掑簭 Collections.sort(personList, comparator);
+        Comparator<StreamInfo> comparator = Comparator.comparing(streamInfo -> streamInfo.getBeginTime()); // 浣跨敤Collections.sort鏂规硶杩涜鎺掑簭 Collections.sort(personList, comparator);
         Collections.sort(PullStreamInfoList, comparator.reversed());
         return PullStreamInfoList;
     }
@@ -371,7 +424,27 @@
     @Override
     public Boolean kickRtspSession(String sessionId) {
         try {
-            mediaClient.kick(sessionId);
+            mediaClient.kickRtspSessions(sessionId);
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public Boolean kickRtmpSession(String sessionId) {
+        try {
+            mediaClient.kickRtmpSessions(sessionId);
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public Boolean kickWebrtcSession(String sessionId) {
+        try {
+            mediaClient.kickWebrtcSessions(sessionId);
             return true;
         } catch (Exception ex) {
             return false;
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 e87f76c..7103be5 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
@@ -53,11 +53,29 @@
      */
     @Get("/rtspsessions/get/{sessionId}")
     String getRtspsessionById(@Var("sessionId") String sessionId);
-
+    /**
+     * 鎸塻essionId鏌ヨwebrtc浼氳瘽
+     */
+    @Get("/webrtcsessions/get/{sessionId}")
+    String getWebrtcsessionById(@Var("sessionId") String sessionId);
+    /**
+     * 鎸塻essionId鏌ヨrtmp浼氳瘽
+     */
+    @Get("/rtmpconns/get/{sessionId}")
+    String getRtmpsessionById(@Var("sessionId") String sessionId);
     /**
      * 鎸塻essionId鍒犻櫎rtsp浼氳瘽
      */
     @Post("/rtspsessions/kick/{sessionId}")
-    String kick(@Var("sessionId") String sessionId);
-
+    String kickRtspSessions(@Var("sessionId") String sessionId);
+    /**
+     * 鎸塻essionId鍒犻櫎rtmp杩炴帴
+     */
+    @Post("/rtmpconns/kick/{sessionId}")
+    String kickRtmpSessions(@Var("sessionId") String sessionId);
+    /**
+     * 鎸塻essionId鍒犻櫎webrtc浼氳瘽
+     */
+    @Post("/webrtcsessions/kick/{sessionId}")
+    String kickWebrtcSessions(@Var("sessionId") String sessionId);
 }
diff --git a/ard-work/src/main/resources/static/js/WHEPClient.js b/ard-work/src/main/resources/static/js/WHEPClient.js
new file mode 100644
index 0000000..f84398b
--- /dev/null
+++ b/ard-work/src/main/resources/static/js/WHEPClient.js
@@ -0,0 +1,76 @@
+import negotiateConnectionWithClientOffer from "./negotiateConnectionWithClientOffer.js";
+/**
+ * Example implementation of a client that uses WHEP to playback video over WebRTC
+ *
+ * https://www.ietf.org/id/draft-murillo-whep-00.html
+ */
+export default class WHEPClient {
+	constructor(endpoint, videoElement) {
+		this.endpoint = endpoint;
+		this.videoElement = videoElement;
+		this.stream = new MediaStream();
+		/**
+		 * Create a new WebRTC connection, using public STUN servers with ICE,
+		 * allowing the client to disover its own IP address.
+		 * https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols#ice
+		 */
+		this.peerConnection = new RTCPeerConnection({
+			iceServers: [
+				{
+					urls: "stun:stun.cloudflare.com:3478",
+				},
+			],
+			bundlePolicy: "max-bundle",
+		});
+		/** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver */
+		this.peerConnection.addTransceiver("video", {
+			direction: "recvonly",
+		});
+		this.peerConnection.addTransceiver("audio", {
+			direction: "recvonly",
+		});
+		/**
+		 * When new tracks are received in the connection, store local references,
+		 * so that they can be added to a MediaStream, and to the <video> element.
+		 *
+		 * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/track_event
+		 */
+		this.peerConnection.ontrack = (event) => {
+			const track = event.track;
+			const currentTracks = this.stream.getTracks();
+			const streamAlreadyHasVideoTrack = currentTracks.some(
+				(track) => track.kind === "video"
+			);
+			const streamAlreadyHasAudioTrack = currentTracks.some(
+				(track) => track.kind === "audio"
+			);
+			switch (track.kind) {
+				case "video":
+					if (streamAlreadyHasVideoTrack) {
+						break;
+					}
+					this.stream.addTrack(track);
+					break;
+				case "audio":
+					if (streamAlreadyHasAudioTrack) {
+						break;
+					}
+					this.stream.addTrack(track);
+					break;
+				default:
+					console.log("got unknown track " + track);
+			}
+		};
+		this.peerConnection.addEventListener("connectionstatechange", (ev) => {
+			if (this.peerConnection.connectionState !== "connected") {
+				return;
+			}
+			if (!this.videoElement.srcObject) {
+				this.videoElement.srcObject = this.stream;
+			}
+		});
+		this.peerConnection.addEventListener("negotiationneeded", (ev) => {
+			negotiateConnectionWithClientOffer(this.peerConnection, this.endpoint);
+		});
+	}
+}
diff --git a/ard-work/src/main/resources/static/js/negotiateConnectionWithClientOffer.js b/ard-work/src/main/resources/static/js/negotiateConnectionWithClientOffer.js
new file mode 100644
index 0000000..26c70b6
--- /dev/null
+++ b/ard-work/src/main/resources/static/js/negotiateConnectionWithClientOffer.js
@@ -0,0 +1,80 @@
+/**
+ * Performs the actual SDP exchange.
+ *
+ * 1. Constructs the client's SDP offer
+ * 2. Sends the SDP offer to the server,
+ * 3. Awaits the server's offer.
+ *
+ * SDP describes what kind of media we can send and how the server and client communicate.
+ *
+ * https://developer.mozilla.org/en-US/docs/Glossary/SDP
+ * https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#name-protocol-operation
+ */
+export default async function negotiateConnectionWithClientOffer(
+	peerConnection,
+	endpoint
+) {
+	/** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer */
+	const offer = await peerConnection.createOffer();
+	/** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription */
+	await peerConnection.setLocalDescription(offer);
+	/** Wait for ICE gathering to complete */
+	let ofr = await waitToCompleteICEGathering(peerConnection);
+	if (!ofr) {
+		throw Error("failed to gather ICE candidates for offer");
+	}
+	/**
+	 * As long as the connection is open, attempt to...
+	 */
+	while (peerConnection.connectionState !== "closed") {
+		/**
+		 * This response contains the server's SDP offer.
+		 * This specifies how the client should communicate,
+		 * and what kind of media client and server have negotiated to exchange.
+		 */
+		let response = await postSDPOffer(endpoint, ofr.sdp);
+		if (response.status === 201) {
+			let answerSDP = await response.text();
+			await peerConnection.setRemoteDescription(
+				new RTCSessionDescription({ type: "answer", sdp: answerSDP })
+			);
+			return response.headers.get("Location");
+		} else if (response.status === 405) {
+			console.error("Update the URL passed into the WHIP or WHEP client");
+		} else {
+			const errorMessage = await response.text();
+			console.error(errorMessage);
+		}
+		/** Limit reconnection attempts to at-most once every 5 seconds */
+		await new Promise((r) => setTimeout(r, 5000));
+	}
+}
+async function postSDPOffer(endpoint, data) {
+	return await fetch(endpoint, {
+		method: "POST",
+		mode: "cors",
+		headers: {
+			"content-type": "application/sdp",
+		},
+		body: data,
+	});
+}
+/**
+ * Receives an RTCPeerConnection and waits until
+ * the connection is initialized or a timeout passes.
+ *
+ * https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#section-4.1
+ * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceGatheringState
+ * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/icegatheringstatechange_event
+ */
+async function waitToCompleteICEGathering(peerConnection) {
+	return new Promise((resolve) => {
+		/** Wait at most 1 second for ICE gathering. */
+		setTimeout(function () {
+			resolve(peerConnection.localDescription);
+		}, 1000);
+		peerConnection.onicegatheringstatechange = (ev) =>
+			peerConnection.iceGatheringState === "complete" &&
+			resolve(peerConnection.localDescription);
+	});
+}
diff --git a/ard-work/src/main/resources/templates/test.html b/ard-work/src/main/resources/templates/test.html
index 15e9981..a55bf5e 100644
--- a/ard-work/src/main/resources/templates/test.html
+++ b/ard-work/src/main/resources/templates/test.html
@@ -13,6 +13,20 @@
             margin-top: 10px;
         }
     </style>
+    <!-- 瀵煎叆 ECMAScript 妯″潡 -->
+    <script th:type="module" th:src="@{/js/WHEPClient.js}"></script>
+    <!-- 鍐呰仈鑴氭湰鍧楋紝鍙互浣跨敤妯″潡涓殑鍐呭 -->
+    <script th:inline="javascript">
+        /*<![CDATA[*/
+        // 鍦ㄨ繖閲屼娇鐢� WHEPClient 妯″潡涓殑鍐呭
+        $("#play").click(function webrtcClient(){
+            const  url = "http://127.0.0.1:8889/165"; // add the webRTC URL from your live input here
+            console.log(url)
+            const  videoElement = document.getElementById("remote-video");
+            const  client = new WHEPClient(url, videoElement);
+        })
+        /*]]>*/
+    </script>
 <body>
 <div class="container">
     <div class="row ">
@@ -127,11 +141,15 @@
             <div class="row top-buffer">
                 <video id="video" muted autoplay loop controls style="width: 800px; height: 100%; object-fit: fill;"/>
             </div>
+            <div class="row top-buffer">
+                <button id="play" type="button" class="btn btn-default" >鎾斁</button>
+            <video id="remote-video" controls autoplay muted></video>
+            </div>
         </div>
     </div>
 </div>
 <script th:inline="javascript">
-    var cameraId, opt, token;
+    var cameraId, opt,optOpen,optClose, token;
     window.onload = function () {
         $.ajax({
             url: "../hik/list",
@@ -461,8 +479,8 @@
     var defogflag = true;
     $("#Defogcfg").click(function () {
         cameraId = $('#select option:selected').val();
-        optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true};
-        optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false};
+         optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true};
+         optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false};
         if (defogflag) {
             $(this).text("鍏抽棴閫忛浘");
             defogflag = false;
@@ -503,8 +521,8 @@
     var infrareflag = true;
     $("#Infrarecfg").click(function () {
         cameraId = $('#select option:selected').val();
-        optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true};
-        optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false};
+         optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true};
+         optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false};
         if (infrareflag) {
             $(this).text("鍏抽棴绾㈠");
             infrareflag = false;
@@ -545,8 +563,8 @@
     var focusModeflag = true;
     $("#FocusMode").click(function () {
         cameraId = $('#select option:selected').val();
-        optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true};
-        optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false};
+         optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true};
+         optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false};
         if (focusModeflag) {
             $(this).text("鑷姩鑱氱劍");
             focusModeflag = false;
@@ -828,5 +846,6 @@
         webRtcServer.disconnect();
     }
 </script>
+
 </body>
 </html>
\ No newline at end of file
diff --git a/lib/mediamtx/mediamtx.yml b/lib/mediamtx/mediamtx.yml
index ae9757b..c26403a 100644
--- a/lib/mediamtx/mediamtx.yml
+++ b/lib/mediamtx/mediamtx.yml
@@ -105,7 +105,7 @@
 # RTMP parameters
 
 # Disable support for the RTMP protocol.
-rtmpDisable: yes
+rtmpDisable: no
 # Address of the RTMP listener. This is needed only when encryption is "no" or "optional".
 rtmpAddress: :1935
 # Encrypt connections with TLS (RTMPS).
@@ -181,7 +181,7 @@
 # WebRTC parameters
 
 # Disable support for the WebRTC protocol.
-webrtcDisable: yes
+webrtcDisable: no
 # Address of the WebRTC listener.
 webrtcAddress: :8889
 # Enable TLS/HTTPS on the WebRTC server.

--
Gitblit v1.9.3