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