From 49bedcce2ac42fa34da012ee3fc8e9230886064a Mon Sep 17 00:00:00 2001 From: liusuyi <1951119284@qq.com> Date: 星期五, 12 七月 2024 15:28:24 +0800 Subject: [PATCH] 优化:流媒体 --- ard-work/src/main/java/com/ruoyi/media/domain/RtmpSessions.java | 21 ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/controller/HikSdkController.java | 1 ard-work/src/main/resources/templates/test.html | 550 ++++---------- ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java | 93 +- ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSessions.java | 20 ard-work/src/main/java/com/ruoyi/media/domain/RtspSessions.java | 21 ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java | 7 ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java | 4 ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java | 2 ard-work/src/main/resources/templates/preview.html | 3 ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java | 9 /dev/null | 1161 --------------------------------- ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java | 17 ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java | 167 ++-- ard-work/src/main/java/com/ruoyi/media/domain/RtspSession.java | 7 server/mediamtx/mediamtx.yml | 2 16 files changed, 390 insertions(+), 1,695 deletions(-) diff --git a/ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java b/ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java index e1e3ec5..5ca32d2 100644 --- a/ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java +++ b/ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java @@ -23,13 +23,11 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * @Description: 鐩告満閫氱敤SDK鎺ュ彛 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 6d89b58..c5d6bba 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 @@ -2,16 +2,14 @@ import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.uuid.IdUtils; -import com.ruoyi.media.domain.Config; -import com.ruoyi.media.domain.Paths; -import com.ruoyi.media.domain.StreamInfo; -import com.ruoyi.media.domain.Vtdu; +import com.ruoyi.media.domain.*; import com.ruoyi.media.service.IMediaService; import com.ruoyi.media.service.IVtduService; import io.swagger.annotations.Api; @@ -107,33 +105,21 @@ } /** - * 绉婚櫎鎷夋祦 + * 绉婚櫎webrtc鎷夋祦 */ - @ApiOperation("绉婚櫎鎷夋祦") - @ApiOperationSupport(order =4 ) + @ApiOperation("绉婚櫎webrtc鎷夋祦") + @ApiOperationSupport(order = 4) @PreAuthorize("@ss.hasPermi('media:stream:remove')") @DeleteMapping("/{sessionId}") - public AjaxResult removePullStreamSession(@PathVariable String sessionId) { - List<StreamInfo> pullStreamList = mediaService.getPullStreamList(1,1000); - StreamInfo streamInfo = pullStreamList.stream() - .filter(object -> object.getId().equals(sessionId)) - .collect(Collectors.toList()).get(0); - switch (streamInfo.getSessionType()) { - case "rtsp": - return AjaxResult.success(mediaService.kickRtspSession(sessionId)); - case "webrtc": - return AjaxResult.success(mediaService.kickWebrtcSession(sessionId)); - case "rtmp": - return AjaxResult.success(mediaService.kickRtmpSession(sessionId)); - } - return AjaxResult.error(); + public AjaxResult removeWebrtcPullStreamSession(@PathVariable String sessionId) { + return AjaxResult.success(mediaService.kickWebrtcSession(sessionId)); } /** * 鑾峰彇閫氶亾璇︾粏淇℃伅 */ @ApiOperation("鑾峰彇閫氶亾璇︾粏淇℃伅") - @ApiOperationSupport(order =4 ) + @ApiOperationSupport(order = 4) @GetMapping(value = "/{name}") public AjaxResult getInfo(@PathVariable("name") String name) { return success(mediaService.getPathInfo(name)); @@ -145,30 +131,18 @@ @GetMapping("/path/list") @ApiOperation("鑾峰彇褰撳墠閫氶亾鍒楄〃") @ApiOperationSupport(order = 5) - public TableDataInfo getPaths(Integer pageNum,Integer pageSize) { + public TableDataInfo getPaths(Integer pageNum, Integer pageSize) { startPage(); - return getDataTable(mediaService.paths(pageNum,pageSize)); + return getDataTable(mediaService.paths(pageNum, pageSize)); } /** * 鎸塈D鏌ヨ鎷夋祦璇︽儏 */ - @GetMapping("/getRtspSessionById") - @ApiOperation("鎸塈D鏌ヨ鎷夋祦璇︽儏") - public AjaxResult getRtspSessionById(String sessionId) { - List<StreamInfo> pullStreamList = mediaService.getPullStreamList(1,1000); - StreamInfo streamInfo = pullStreamList.stream() - .filter(object -> object.getId().equals(sessionId)) - .collect(Collectors.toList()).get(0); - switch (streamInfo.getSessionType()) { - case "rtsp": - return AjaxResult.success(mediaService.getRtspSessionById(sessionId)); - case "rtmp": - return AjaxResult.success(mediaService.getRtmpSessionById(sessionId)); - case "webrtc": - return AjaxResult.success(mediaService.getWebrtcSessionById(sessionId)); - } - return AjaxResult.error(); + @GetMapping("/getWebrtcSessionById") + @ApiOperation("鎸塈D鏌ヨwebrtc鎷夋祦璇︽儏") + public AjaxResult getWebrtcSessionById(String sessionId) { + return AjaxResult.success(mediaService.getWebrtcSessionById(sessionId)); } /** @@ -178,21 +152,42 @@ @GetMapping("/pushList") @ApiOperation("鑾峰彇鎺ㄦ祦鍒楄〃") @ApiOperationSupport(order = 6) - public TableDataInfo getPushStreamList(Integer pageNum,Integer pageSize) { - startPage(); - return getDataTable(mediaService.getPushStreamList(pageNum,pageSize)); + public TableDataInfo getPushStreamList(Integer pageNum, Integer pageSize) { + TableDataInfo tableDataInfo = mediaService.getPushStreamList(pageNum, pageSize); + return tableDataInfo; } /** - * 鑾峰彇鎷夋祦鍒楄〃 + * 鑾峰彇WebRtc鎷夋祦鍒楄〃 */ @PreAuthorize("@ss.hasPermi('media:stream:list')") - @GetMapping("/pullList") - @ApiOperation("鑾峰彇鎷夋祦鍒楄〃") + @GetMapping("/getPullWebrtcStreamList") + @ApiOperation("鑾峰彇WebRtc鎷夋祦鍒楄〃") @ApiOperationSupport(order = 7) - public TableDataInfo getPullStreamList(Integer pageNum,Integer pageSize) { - startPage(); - return getDataTable(mediaService.getPullStreamList(pageNum,pageSize)); + public TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize) { + return mediaService.getPullWebrtcStreamList(pageNum, pageSize); } + /** + * 鑾峰彇rtsp鎷夋祦鍒楄〃 + */ + @PreAuthorize("@ss.hasPermi('media:stream:list')") + @GetMapping("/getPullRtspStreamList") + @ApiOperation("鑾峰彇rtsp鎷夋祦鍒楄〃") + @ApiOperationSupport(order = 8) + public TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize) { + return mediaService.getPullRtspStreamList(pageNum, pageSize); + + } + + /** + * 鑾峰彇rtmp鎷夋祦鍒楄〃 + */ + @PreAuthorize("@ss.hasPermi('media:stream:list')") + @GetMapping("/getPullRtmpStreamList") + @ApiOperation("鑾峰彇rtmp鎷夋祦鍒楄〃") + @ApiOperationSupport(order = 9) + public TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize) { + return mediaService.getPullRtmpStreamList(pageNum, pageSize); + } } 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 index c9906d3..e32642e 100644 --- a/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java +++ b/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSession.java @@ -20,4 +20,8 @@ private String state; private long bytesReceived; private long bytesSent; + private String upStream; + private String downStream; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date beginTime; } diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSessions.java b/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSessions.java new file mode 100644 index 0000000..06cb51c --- /dev/null +++ b/ard-work/src/main/java/com/ruoyi/media/domain/RtmpSessions.java @@ -0,0 +1,21 @@ +package com.ruoyi.media.domain; + +import lombok.Data; + +import java.util.List; + +/** + * @ClassName:Sessions + * @Description: + * @Author:ard + * @Date:2024骞�07鏈�12鏃�13:34 + * @Version:1.0 + **/ +@Data +public class RtmpSessions { + private int itemCount; + + private int pageCount; + + private List<RtmpSession> items; +} diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/RtspSession.java b/ard-work/src/main/java/com/ruoyi/media/domain/RtspSession.java index 629baac..4697b86 100644 --- a/ard-work/src/main/java/com/ruoyi/media/domain/RtspSession.java +++ b/ard-work/src/main/java/com/ruoyi/media/domain/RtspSession.java @@ -14,11 +14,16 @@ */ @Data public class RtspSession { - private String name; private String id; + private String path; private Date created; private String remoteAddr; private String state; + private String transport; private long bytesReceived; private long bytesSent; + private String upStream; + private String downStream; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date beginTime; } diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/RtspSessions.java b/ard-work/src/main/java/com/ruoyi/media/domain/RtspSessions.java new file mode 100644 index 0000000..2cccc3c --- /dev/null +++ b/ard-work/src/main/java/com/ruoyi/media/domain/RtspSessions.java @@ -0,0 +1,21 @@ +package com.ruoyi.media.domain; + +import lombok.Data; + +import java.util.List; + +/** + * @ClassName:Sessions + * @Description: + * @Author:ard + * @Date:2024骞�07鏈�12鏃�13:34 + * @Version:1.0 + **/ +@Data +public class RtspSessions { + private int itemCount; + + private int pageCount; + + private List<RtspSession> items; +} 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 index b66dbbe..5451888 100644 --- a/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java +++ b/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSession.java @@ -13,11 +13,18 @@ **/ @Data public class WebrtcSession { - private String name; private String id; + private String path; private Date created; private String remoteAddr; + private String peerConnectionEstablished; + private String localCandidate; + private String remoteCandidate; private String state; private long bytesReceived; private long bytesSent; + private String upStream; + private String downStream; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date beginTime; } diff --git a/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSessions.java b/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSessions.java new file mode 100644 index 0000000..8873a72 --- /dev/null +++ b/ard-work/src/main/java/com/ruoyi/media/domain/WebrtcSessions.java @@ -0,0 +1,20 @@ +package com.ruoyi.media.domain; + +import lombok.Data; +import java.util.List; + +/** + * @ClassName:Sessions + * @Description: + * @Author:ard + * @Date:2024骞�07鏈�12鏃�13:34 + * @Version:1.0 + **/ +@Data +public class WebrtcSessions { + private int itemCount; + + private int pageCount; + + private List<WebrtcSession> items; +} 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 9c0602e..087c11b 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,5 +1,6 @@ package com.ruoyi.media.service; +import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.media.domain.*; import java.util.List; import java.util.Map; @@ -45,9 +46,11 @@ RtmpSession getRtmpSessionById(String sessionId); - List<StreamInfo> getPushStreamList(Integer pageNum,Integer pageSize); + TableDataInfo getPushStreamList(Integer pageNum,Integer pageSize); - List<StreamInfo> getPullStreamList(Integer pageNum,Integer pageSize); + TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize); + TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize); + TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize); Boolean kickRtspSession(String sessionId); 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 0079687..3e3a269 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 @@ -3,6 +3,8 @@ import com.alibaba.fastjson2.JSONObject; import com.dtflys.forest.exceptions.ForestNetworkException; import com.dtflys.forest.exceptions.ForestRuntimeException; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.media.domain.*; import com.ruoyi.media.service.IMediaService; @@ -10,6 +12,7 @@ import com.ruoyi.utils.tools.ArdTool; import io.swagger.models.auth.In; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.formula.functions.T; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -243,9 +246,12 @@ * 2023/8/29 9:37:05 */ @Override - public List<StreamInfo> getPushStreamList(Integer pageNum, Integer pageSize) { + public TableDataInfo getPushStreamList(Integer pageNum, Integer pageSize) { + TableDataInfo tableDataInfo = new TableDataInfo(); List<StreamInfo> PushStreamInfoList = new ArrayList<>(); Paths paths = mediaClient.paths(pageNum - 1, pageSize); + int itemCount = paths.getItemCount(); + tableDataInfo.setTotal(itemCount); List<Items> items = paths.getItems(); for (Items item : items) { StreamInfo info = new StreamInfo(); @@ -309,105 +315,82 @@ PushStreamInfoList.add(info); } - return PushStreamInfoList; + tableDataInfo.setRows(PushStreamInfoList); + tableDataInfo.setCode(HttpStatus.SUCCESS); + tableDataInfo.setMsg("鏌ヨ鎴愬姛"); + return tableDataInfo; } /** - * 鑾峰彇鎷夋祦鍒楄〃 + * 鑾峰彇webrtc鎷夋祦鍒楄〃 * 鍒樿嫃涔� * 2023/8/29 9:37:05 */ @Override - public List<StreamInfo> getPullStreamList(Integer pageNum, Integer pageSize) { - List<StreamInfo> PullStreamInfoList = new ArrayList<>(); - Paths paths = mediaClient.paths(pageNum - 1, pageSize); - List<Items> items = paths.getItems(); - for (Items item : items) { - List<Readers> readers = item.getReaders(); - for (Readers reader : readers) { - StreamInfo info = new StreamInfo(); - //ID - String name = item.getName(); - info.setName(name); - Conf conf = mediaClient.getPathInfo(name); - //浼犺緭鍗忚 - info.setProtocol(conf.getSourceProtocol()); - - 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": - 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; + public TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize) { + TableDataInfo tableDataInfo = new TableDataInfo(); + WebrtcSessions WebrtcSessions = mediaClient.webrtcsessions(pageNum-1, pageSize); + List<WebrtcSession> webrtcsessions = WebrtcSessions.getItems(); + webrtcsessions.stream().forEach( + webrtcSession -> { + webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived())); + webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent())); + webrtcSession.setBeginTime(webrtcSession.getCreated()); } - } - } - Comparator<StreamInfo> comparator = Comparator.comparing(streamInfo -> streamInfo.getBeginTime()); // 浣跨敤Collections.sort鏂规硶杩涜鎺掑簭 Collections.sort(personList, comparator); - Collections.sort(PullStreamInfoList, comparator.reversed()); - return PullStreamInfoList; + ); + tableDataInfo.setTotal(WebrtcSessions.getItemCount()); + tableDataInfo.setRows(webrtcsessions); + tableDataInfo.setCode(HttpStatus.SUCCESS); + tableDataInfo.setMsg("鏌ヨ鎴愬姛"); + return tableDataInfo; + } + + /** + * 鑾峰彇rtmp鎷夋祦鍒楄〃 + * 鍒樿嫃涔� + * 2023/8/29 9:37:05 + */ + @Override + public TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize) { + TableDataInfo tableDataInfo = new TableDataInfo(); + RtmpSessions rtmpSessions = mediaClient.rtmpsessions(pageNum-1, pageSize); + List<RtmpSession> webrtcsessions = rtmpSessions.getItems(); + webrtcsessions.stream().forEach( + webrtcSession -> { + webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived())); + webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent())); + webrtcSession.setBeginTime(webrtcSession.getCreated()); + } + ); + tableDataInfo.setTotal(rtmpSessions.getItemCount()); + tableDataInfo.setRows(webrtcsessions); + tableDataInfo.setCode(HttpStatus.SUCCESS); + tableDataInfo.setMsg("鏌ヨ鎴愬姛"); + return tableDataInfo; + } + + /** + * 鑾峰彇rtsp鎷夋祦鍒楄〃 + * 鍒樿嫃涔� + * 2023/8/29 9:37:05 + */ + @Override + public TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize) { + TableDataInfo tableDataInfo = new TableDataInfo(); + RtspSessions rtspSessions = mediaClient.rtspsessions(pageNum-1, pageSize); + List<RtspSession> webrtcsessions = rtspSessions.getItems(); + webrtcsessions.stream().forEach( + webrtcSession -> { + webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived())); + webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent())); + webrtcSession.setBeginTime(webrtcSession.getCreated()); + } + ); + tableDataInfo.setTotal(rtspSessions.getItemCount()); + tableDataInfo.setRows(webrtcsessions); + tableDataInfo.setCode(HttpStatus.SUCCESS); + tableDataInfo.setMsg("鏌ヨ鎴愬姛"); + return tableDataInfo; } /** 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 e441bc0..1f37783 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 @@ -4,6 +4,7 @@ import com.ruoyi.media.domain.*; import java.nio.file.Path; +import java.util.List; /** * @Description: mediamtx娴佸獟浣撳鎴风 @@ -47,8 +48,20 @@ /** * 鏌ヨ鎵�鏈塺tsp浼氳瘽 */ - @Get("/rtspsessions/list") - public String rtspsessions(); + @Get("/rtspsessions/list?page={pageNum}&itemsPerPage={pageSize}") + public RtspSessions rtspsessions(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize); + + /** + * 鏌ヨ鎵�鏈墂ebrt浼氳瘽 + */ + @Get("/webrtcsessions/list?page={pageNum}&itemsPerPage={pageSize}") + public WebrtcSessions webrtcsessions(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize); + + /** + * 鏌ヨ鎵�鏈塺tmp浼氳瘽 + */ + @Get("/rtmpsessions/list?page={pageNum}&itemsPerPage={pageSize}") + public RtmpSessions rtmpsessions(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize); /** * 鎸塻essionId鏌ヨrtsp浼氳瘽 diff --git a/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/controller/HikSdkController.java b/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/controller/HikSdkController.java index e90b089..e70ba37 100644 --- a/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/controller/HikSdkController.java +++ b/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/controller/HikSdkController.java @@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.*; import springfox.documentation.annotations.ApiIgnore; import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; diff --git a/ard-work/src/main/resources/templates/preview.html b/ard-work/src/main/resources/templates/preview.html index 273683e..003d22d 100644 --- a/ard-work/src/main/resources/templates/preview.html +++ b/ard-work/src/main/resources/templates/preview.html @@ -101,7 +101,7 @@ <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.227:8889/164/" style="width: 250px"/> + <input id="videoUrl" type="text" value="http://192.168.1.227:8889/0d1c9f80a7b4480c8b401ba6b140b581_1/" style="width: 250px"/> </div> </div> </div> @@ -242,6 +242,7 @@ } // this.eTag = res.headers.get('ETag'); this.eTag = res.headers.get("ETag") || res.headers.get('E-Tag'); + this.wurl = new URL(res.headers.get('location'), this.wurl.origin).toString(); return res.text(); }) .then((sdp) => this.onRemoteAnswer(new RTCSessionDescription({ diff --git a/ard-work/src/main/resources/templates/test.html b/ard-work/src/main/resources/templates/test.html index 4c4dc19..6284aaf 100644 --- a/ard-work/src/main/resources/templates/test.html +++ b/ard-work/src/main/resources/templates/test.html @@ -10,7 +10,6 @@ .top-buffer { margin-top: 10px; } - .container { border: 2px solid #1b6d85; padding: 15px; @@ -143,7 +142,7 @@ </div> </div> <script th:inline="javascript"> - var cameraId, chanNo, opt, optOpen, optClose, token; + var cameraId, chanNo,opt, optOpen, optClose, token; window.onload = function () { console.log(RTCRtpReceiver.getCapabilities('video').codecs) opt = {"username": "admin", "password": "admin123"}; @@ -879,7 +878,7 @@ function commondMethod(url, code, enable) { cameraId = $('#selectDev option:selected').val(); chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "enable": enable, "code": code}; + opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 4, "enable": enable, "code": code}; $.ajax({ headers: { 'Accept': 'application/json', @@ -910,30 +909,17 @@ type: "get", dataType: "json", success: function (data) { - realView(data.data.webrtcUrl + "/"); + realView(data.data.webrtcUrl + "/", e.target.id); } }) }); let webrtcClient; //whep鎿嶄綔鏂规硶 - const retryPause = 2000; - - const video = document.getElementById('video'); - - let nonAdvertisedCodecs = []; - let pc = null; - let restartTimeout = null; - let sessionUrl = ''; - let offerData = ''; - let queuedCandidates = []; - let defaultControls = false; - let whepUrl = ''; - + const restartPause = 2000; const unquoteCredential = (v) => ( JSON.parse(`"${v}"`) ); - const linkToIceServers = (links) => ( (links !== null) ? links.split(', ').map((link) => { const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i); @@ -944,21 +930,20 @@ if (m[3] !== undefined) { ret.username = unquoteCredential(m[3]); ret.credential = unquoteCredential(m[4]); - ret.credentialType = 'password'; + ret.credentialType = "password"; } return ret; }) : [] ); - - const parseOffer = (sdp) => { + const parseOffer = (offer) => { const ret = { iceUfrag: '', icePwd: '', medias: [], }; - for (const line of sdp.split('\r\n')) { + 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:')) { @@ -970,131 +955,7 @@ return ret; }; - - const enableStereoPcmau = (section) => { - let lines = section.split('\r\n'); - - lines[0] += ' 118'; - lines.splice(lines.length - 1, 0, 'a=rtpmap:118 PCMU/8000/2'); - lines.splice(lines.length - 1, 0, 'a=rtcp-fb:118 transport-cc'); - - lines[0] += ' 119'; - lines.splice(lines.length - 1, 0, 'a=rtpmap:119 PCMA/8000/2'); - lines.splice(lines.length - 1, 0, 'a=rtcp-fb:119 transport-cc'); - - return lines.join('\r\n'); - }; - - const enableMultichannelOpus = (section) => { - let lines = section.split('\r\n'); - - lines[0] += " 112"; - lines.splice(lines.length - 1, 0, "a=rtpmap:112 multiopus/48000/3"); - lines.splice(lines.length - 1, 0, "a=fmtp:112 channel_mapping=0,2,1;num_streams=2;coupled_streams=1"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:112 transport-cc"); - - lines[0] += " 113"; - lines.splice(lines.length - 1, 0, "a=rtpmap:113 multiopus/48000/4"); - lines.splice(lines.length - 1, 0, "a=fmtp:113 channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:113 transport-cc"); - - lines[0] += " 114"; - lines.splice(lines.length - 1, 0, "a=rtpmap:114 multiopus/48000/5"); - lines.splice(lines.length - 1, 0, "a=fmtp:114 channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:114 transport-cc"); - - lines[0] += " 115"; - lines.splice(lines.length - 1, 0, "a=rtpmap:115 multiopus/48000/6"); - lines.splice(lines.length - 1, 0, "a=fmtp:115 channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:115 transport-cc"); - - lines[0] += " 116"; - lines.splice(lines.length - 1, 0, "a=rtpmap:116 multiopus/48000/7"); - lines.splice(lines.length - 1, 0, "a=fmtp:116 channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:116 transport-cc"); - - lines[0] += " 117"; - lines.splice(lines.length - 1, 0, "a=rtpmap:117 multiopus/48000/8"); - lines.splice(lines.length - 1, 0, "a=fmtp:117 channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:117 transport-cc"); - - return lines.join('\r\n'); - }; - - const enableL16 = (section) => { - let lines = section.split('\r\n'); - - lines[0] += " 120"; - lines.splice(lines.length - 1, 0, "a=rtpmap:120 L16/8000/2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:120 transport-cc"); - - lines[0] += " 121"; - lines.splice(lines.length - 1, 0, "a=rtpmap:121 L16/16000/2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:121 transport-cc"); - - lines[0] += " 122"; - lines.splice(lines.length - 1, 0, "a=rtpmap:122 L16/48000/2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:122 transport-cc"); - - return lines.join('\r\n'); - }; - - const enableStereoOpus = (section) => { - let opusPayloadFormat = ''; - let lines = section.split('\r\n'); - - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith('a=rtpmap:') && lines[i].toLowerCase().includes('opus/')) { - opusPayloadFormat = lines[i].slice('a=rtpmap:'.length).split(' ')[0]; - break; - } - } - - if (opusPayloadFormat === '') { - return section; - } - - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith('a=fmtp:' + opusPayloadFormat + ' ')) { - if (!lines[i].includes('stereo')) { - lines[i] += ';stereo=1'; - } - if (!lines[i].includes('sprop-stereo')) { - lines[i] += ';sprop-stereo=1'; - } - } - } - - return lines.join('\r\n'); - }; - - const editOffer = (sdp) => { - const sections = sdp.split('m='); - - for (let i = 0; i < sections.length; i++) { - if (sections[i].startsWith('audio')) { - sections[i] = enableStereoOpus(sections[i]); - - if (nonAdvertisedCodecs.includes('pcma/8000/2')) { - sections[i] = enableStereoPcmau(sections[i]); - } - - if (nonAdvertisedCodecs.includes('multiopus/48000/6')) { - sections[i] = enableMultichannelOpus(sections[i]); - } - - if (nonAdvertisedCodecs.includes('L16/48000/2')) { - sections[i] = enableL16(sections[i]); - } - - break; - } - } - - return sections.join('m='); - }; - - const generateSdpFragment = (od, candidates) => { + const generateSdpFragment = (offerData, candidates) => { const candidatesByMedia = {}; for (const candidate of candidates) { const mid = candidate.sdpMLineIndex; @@ -1104,12 +965,12 @@ candidatesByMedia[mid].push(candidate); } - let frag = 'a=ice-ufrag:' + od.iceUfrag + '\r\n' - + 'a=ice-pwd:' + od.icePwd + '\r\n'; + let frag = 'a=ice-ufrag:' + offerData.iceUfrag + '\r\n' + + 'a=ice-pwd:' + offerData.icePwd + '\r\n'; let mid = 0; - for (const media of od.medias) { + for (const media of offerData.medias) { if (candidatesByMedia[mid] !== undefined) { frag += 'm=' + media + '\r\n' + 'a=mid:' + mid + '\r\n'; @@ -1122,256 +983,181 @@ } return frag; - }; + } - const loadStream = () => { - console.log('loadStream'); - requestICEServers(); - }; + class WHEPClient { + constructor(whepUrl, videoId) { + this.video = videoId; + this.wurl = new URL('whep', whepUrl); + this.pc = null; + this.restartTimeout = null; + this.eTag = ''; + this.queuedCandidates = []; + this.start(); + } - const supportsNonAdvertisedCodec = (codec, fmtp) => ( - new Promise((resolve, reject) => { - const pc = new RTCPeerConnection({iceServers: []}); - pc.addTransceiver('audio', {direction: 'recvonly'}); - pc.createOffer() - .then((offer) => { - if (offer.sdp.includes(' ' + codec)) { // codec is advertised, there's no need to add it manually - resolve(false); - return; + start() { + console.log("requesting ICE servers"); + fetch(this.wurl, { + 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"); + console.log(this.wurl); + fetch(this.wurl, { + method: 'POST', + headers: { + 'Content-Type': 'application/sdp', + }, + body: offer.sdp, + }) + .then((res) => { + if (res.status !== 201) { + throw new Error('bad status code'); } - const sections = offer.sdp.split('m=audio'); - const lines = sections[1].split('\r\n'); - lines[0] += ' 118'; - lines.splice(lines.length - 1, 0, 'a=rtpmap:118 ' + codec); - if (fmtp !== undefined) { - lines.splice(lines.length - 1, 0, 'a=fmtp:118 ' + fmtp); + // this.eTag = res.headers.get('ETag'); + this.eTag = res.headers.get("ETag") || res.headers.get('E-Tag'); + this.wurl = new URL(res.headers.get('location'), this.wurl.origin).toString(); + 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 === '') { + console.log("222222222222222222222") + this.queuedCandidates.push(evt.candidate); + } else { + console.log("333333333333333333333") + this.sendLocalCandidates([evt.candidate]) + } + } + } + + sendLocalCandidates(candidates) { + fetch(this.wurl, { + 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'); } - sections[1] = lines.join('\r\n'); - offer.sdp = sections.join('m=audio'); - return pc.setLocalDescription(offer); - }) - .then(() => { - return pc.setRemoteDescription(new RTCSessionDescription({ - type: 'answer', - sdp: 'v=0\r\n' - + 'o=- 6539324223450680508 0 IN IP4 0.0.0.0\r\n' - + 's=-\r\n' - + 't=0 0\r\n' - + 'a=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\n' - + 'm=audio 9 UDP/TLS/RTP/SAVPF 118\r\n' - + 'c=IN IP4 0.0.0.0\r\n' - + 'a=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\n' - + 'a=ice-ufrag:29e036dc\r\n' - + 'a=sendonly\r\n' - + 'a=rtcp-mux\r\n' - + 'a=rtpmap:118 ' + codec + '\r\n' - + ((fmtp !== undefined) ? 'a=fmtp:118 ' + fmtp + '\r\n' : ''), - })); - }) - .then(() => { - resolve(true); }) .catch((err) => { - resolve(false); - }) - .finally(() => { - pc.close(); + console.log('error: ' + err); + this.scheduleRestart(); }); - }) - ); + } - const getNonAdvertisedCodecs = () => { - Promise.all([ - ['pcma/8000/2'], - ['multiopus/48000/6', 'channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'], - ['L16/48000/2'] - ].map((c) => supportsNonAdvertisedCodec(c[0], c[1]).then((r) => (r) ? c[0] : false))) - .then((c) => c.filter((e) => e !== false)) - .then((codecs) => { - nonAdvertisedCodecs = codecs; - loadStream(); - }); - }; - - const onError = (err) => { - if (restartTimeout === null) { - - if (pc !== null) { - pc.close(); - pc = null; + scheduleRestart() { + if (this.restartTimeout !== null) { + return; } - restartTimeout = window.setTimeout(() => { - restartTimeout = null; - loadStream(); - }, retryPause); - - if (this.sessionUrl) { - fetch(this.sessionUrl, { - method: 'DELETE', - }); + if (this.pc !== null) { + this.pc.close(); + this.pc = null; } - this.sessionUrl = ''; - queuedCandidates = []; + this.restartTimeout = window.setTimeout(() => { + this.restartTimeout = null; + this.start(); + }, restartPause); + + this.eTag = ''; + this.queuedCandidates = []; } - }; - const sendLocalCandidates = (candidates) => { - fetch(new URL('whep', this.whepUrl), { - method: 'PATCH', - headers: { - 'Content-Type': 'application/trickle-ice-sdpfrag', - 'If-Match': '*', - }, - body: generateSdpFragment(offerData, candidates), - }) - .then((res) => { - switch (res.status) { - case 204: - break; - case 404: - throw new Error('stream not found'); - default: - throw new Error(`bad status code ${res.status}`); + stop() { + if (this.pc) { + try { + this.pc.close(); + } catch (e) { + console.log("Failure close peer connection:" + e); } - }) - .catch((err) => { - onError(err.toString()); - }); - }; - - const onLocalCandidate = (evt) => { - if (restartTimeout !== null) { - return; - } - - if (evt.candidate !== null) { - if (this.sessionUrl === '') { - queuedCandidates.push(evt.candidate); - } else { - //sendLocalCandidates([evt.candidate]) + this.pc = null; } } - }; + } - const onRemoteAnswer = (sdp) => { - if (restartTimeout !== null) { - return; - } - pc.setRemoteDescription(new RTCSessionDescription({ - type: 'answer', - sdp, - })) - .then(() => { - if (queuedCandidates.length !== 0) { - sendLocalCandidates(queuedCandidates); - queuedCandidates = []; - } - }) - .catch((err) => { - onError(err.toString()); - }); - }; - - const sendOffer = (offer) => { - fetch(new URL('whep', this.whepUrl), { - method: 'POST', - headers: { - 'Content-Type': 'application/sdp', - }, - body: offer.sdp, - }) - .then((res) => { - switch (res.status) { - case 201: - break; - case 404: - throw new Error('stream not found'); - case 400: - return res.json().then((e) => { - throw new Error(e.error); - }); - default: - throw new Error(`bad status code ${res.status}`); - } - - this.sessionUrl = new URL(res.headers.get('location'), new URL(this.whepUrl).origin).toString(); - return res.text() - .then((sdp) => onRemoteAnswer(sdp)); - }) - .catch((err) => { - onError(err.toString()); - }); - }; - - const createOffer = () => { - pc.createOffer() - .then((offer) => { - offer.sdp = editOffer(offer.sdp); - offerData = parseOffer(offer.sdp); - pc.setLocalDescription(offer) - .then(() => { - sendOffer(offer); - }) - .catch((err) => { - onError(err.toString()); - }); - }) - .catch((err) => { - onError(err.toString()); - }); - }; - - const onConnectionState = () => { - if (restartTimeout !== null) { - return; - } - - if (pc.iceConnectionState === 'disconnected') { - onError('peer connection closed'); - } - }; - - const onTrack = (evt) => { - - video.srcObject = evt.streams[0]; - }; - - const requestICEServers = () => { - fetch(new URL('whep', this.whepUrl), { - method: 'OPTIONS', - }) - .then((res) => { - pc = new RTCPeerConnection({ - iceServers: linkToIceServers(res.headers.get('Link')), - // https://webrtc.org/getting-started/unified-plan-transition-guide - sdpSemantics: 'unified-plan', - }); - - const direction = 'sendrecv'; - pc.addTransceiver('video', {direction}); - pc.addTransceiver('audio', {direction}); - - pc.onicecandidate = (evt) => onLocalCandidate(evt); - pc.oniceconnectionstatechange = () => onConnectionState(); - pc.ontrack = (evt) => onTrack(evt); - - createOffer(); - }) - .catch((err) => { - onError(err.toString()); - }); - }; - - const init = () => { - getNonAdvertisedCodecs(); - }; - - function realView(whepUrl) { - console.log(whepUrl) - this.whepUrl = whepUrl - init(); + function realView(whepUrl, videoId) { + webrtcClient = new WHEPClient(whepUrl, videoId); } </script> </body> diff --git a/ard-work/src/main/resources/templates/test1.html b/ard-work/src/main/resources/templates/test1.html deleted file mode 100644 index 5623b4b..0000000 --- a/ard-work/src/main/resources/templates/test1.html +++ /dev/null @@ -1,1161 +0,0 @@ -<!DOCTYPE html> -<html lang="en" xmlns:th="http://www.thymeleaf.org"> -<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> - .top-buffer { - margin-top: 10px; - } - .container { - border: 2px solid #1b6d85; - padding: 15px; - } - </style> -<body> -<div class="container"> - <div class="row"> - <div class="col-md-5"> - <div class="row top-buffer"> - 璁惧锛�<select id="selectDev" style="width: 330px;"> - </select> - </div> - <div class="row top-buffer"> - 閫氶亾锛�<select id="selectChn" style="width: 330px;"> - </select> - </div> - <div class="row top-buffer"> - <div class="col-md-1 col-md-offset-1"> - <button id="up" type="button" class="btn btn-primary">涓�</button> - </div> - <div class="col-md-6 col-md-offset-2"> - <div class="btn-group" role="group"> - <button id="controlZoomIn" type="button" class="btn btn-primary">璋冪劍-</button> - <button id="controlZoomOut" type="button" class="btn btn-primary">璋冪劍+</button> - </div> - </div> - </div> - <div class="row "> - <div class="col-md-1"> - <button id="left" type="button" class="btn btn-primary">宸�</button> - </div> - <div class="col-md-1 col-md-offset-1"> - <button id="right" type="button" class="btn btn-primary">鍙�</button> - </div> - <div class="col-md-6 col-md-offset-1"> - <div class="btn-group" role="group"> - <button id="controlFocusNear" type="button" class="btn btn-primary">鑱氱劍-</button> - <button id="controlFocusFar" type="button" class="btn btn-primary">鑱氱劍+</button> - </div> - </div> - </div> - <div class="row "> - <div class="col-md-1 col-md-offset-1"> - <button id="down" type="button" class="btn btn-primary">涓�</button> - </div> - <div class="col-md-6 col-md-offset-2"> - <div class="btn-group" role="group"> - <button id="controlIrisOpen" type="button" class="btn btn-primary">鍏夊湀-</button> - <button id="controlIrisClose" type="button" class="btn btn-primary">鍏夊湀+</button> - </div> - </div> - </div> - <div class="row "> - <div class="col-md-10"> - <div class="row top-buffer"> - <div class="input-group"> - <span class="input-group-addon">鐩殑鍧愭爣鍊硷細</span> - <input id="targetPostion" class="form-control" placeholder="鐩殑鍧愭爣"/> - <button id="setTargetPostion" type="button" class="btn btn-default">鎸囧悜鍧愭爣</button> - </div> - <div class="input-group"> - <span class="input-group-addon">P鍊硷細</span> - <input id="p" class="form-control" placeholder="璇疯緭鍏鍊�"/> - </div> - <div class="input-group"> - <span class="input-group-addon">T鍊硷細</span> - <input id="t" class="form-control" placeholder="璇疯緭鍏鍊�"/> - </div> - <div class="input-group"> - <span class="input-group-addon">Z鍊硷細</span> - <input id="z" class="form-control" placeholder="璇疯緭鍏鍊�"/> - </div> - </div> - <div class="row top-buffer"> - <div class="btn-group" role="group"> - <button id="getPTZ" type="button" class="btn btn-default">鑾峰彇ptz</button> - <button id="setPTZ" type="button" class="btn btn-default">璁剧疆ptz</button> - <button id="setPreset" type="button" class="btn btn-default">璁鹃缃偣</button> - <button id="gotoPreset" type="button" class="btn btn-default">璋冮缃偣</button> - <button id="setZeroPTZ" type="button" class="btn btn-default">璁剧疆闆舵柟浣嶈</button> - </div> - </div> - <div class="row top-buffer"> - <div class="btn-group" role="group"> - <button id="FocusMode" type="button" class="btn btn-default">鎵嬪姩鑱氱劍</button> - <div id="focusDiv" class="input-group"> - <span class="input-group-addon">鑱氱劍鍊硷細</span> - <input id="focus" class="form-control" placeholder="鑱氱劍鍊�"/> - </div> - <button id="getFocusPos" type="button" class="btn btn-default">鑾峰彇鑱氱劍鍊�</button> - <button id="setFocusPos" type="button" class="btn btn-default">璁剧疆鑱氱劍鍊�</button> - </div> - </div> - <div class="row top-buffer"> - <div class="btn-group" role="group"> - <button id="WiperPwron" type="button" class="btn btn-default">寮�鍚洦鍒�</button> - <button id="Defogcfg" type="button" class="btn btn-default">寮�鍚�忛浘</button> - <button id="Infrarecfg" type="button" class="btn btn-default">寮�鍚孩澶�</button> - <button id="HeateRpwron" type="button" class="btn btn-default">寮�鍚簯鍙板姞鐑�</button> - <button id="CameraDeicing" type="button" class="btn btn-default">寮�鍚暅澶村姞鐑�</button> - </div> - </div> - <div class="row top-buffer"> - <div class="btn-group" role="group"> - <button id="voice" type="button" class="btn btn-default">寮�濮嬭闊冲璁�</button> - <button id="record" type="button" class="btn btn-default">寮�濮嬪綍鍍�</button> - <button id="saveCutPic" type="button" class="btn btn-default">瀛樺偍鎶撳浘</button> - </div> - </div> - </div> - </div> - </div> - <div class="col-md-6"> - <div class="row"> - <div class="row top-buffer"> - <button id="preview" type="button" class="btn btn-primary">棰勮</button> - <button id="previewStop" type="button" class="btn btn-primary">鍋滄</button> - </div> - <div class="row top-buffer"> - <video id="video" muted autoplay loop controls - style="width: 100%; height: 360px; object-fit: fill; border: 2px solid #3498db;"/> - </div> - <div class="row"> - <img class="thumbnail" id="imgContainer" - style="width: 100%; height: 360px; border: 2px solid #3498db;"/> - </div> - </div> - </div> - </div> -</div> -<script th:inline="javascript"> - var cameraId, chanNo,opt, optOpen, optClose, token; - window.onload = function () { - console.log(RTCRtpReceiver.getCapabilities('video').codecs) - opt = {"username": "admin", "password": "admin123"}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - url: "../login", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - token = data.token; - getDeviceList();// 鑾峰彇璁惧鍒楄〃 - } - }) - // 鍒濆鍖栧唴瀹� - console.log(cameraMap); - } - - //鑾峰彇璁惧 - function getDeviceList() { - $.ajax({ - url: "../cameraSdk/list", - type: "get", - success: function (data) { - console.log(data); - var arr = data.data; - for (var i = 0; i < arr.length; i++) { - console.log(arr[i].id); - console.log(arr[i].name) - var camera = { - name: arr[i].name, - factory: arr[i].factory, - ipaddr: arr[i].ip, - username: arr[i].username, - password: arr[i].password, - port: arr[i].rtspPort, - longitude: arr[i].longitude, - latitude: arr[i].latitude, - altitude: arr[i].altitude - }; - cameraMap.set(arr[i].id, camera); - //鍏堝垱寤哄ソselect閲岄潰鐨刼ption鍏冪礌 - var option = $("<option>"); - //缁檕ption鐨則ext璧嬪��,杩欏氨鏄綘鐐瑰紑涓嬫媺妗嗚兘澶熺湅鍒扮殑涓滆タ - $(option).val(arr[i].id); - $(option).text(arr[i].name); - //鑾峰彇select 涓嬫媺妗嗗璞�,骞跺皢option娣诲姞杩泂elect - $('#selectDev').append(option); - } - $("#selectDev").trigger("change"); - } - }) - } - - //閫夋嫨璁惧 - $("#selectDev").change(function () { - // 鍦ㄨ繖閲屽鐞嗛�夋嫨浜嬩欢 - var cameraId = $(this).find("option:selected").val(); - var name = $(this).find("option:selected").text(); - getChannelList(cameraId); - console.log("閫夋嫨浜嗭細" + cameraId + "---" + name); - }); - - //鑾峰彇閫氶亾 - function getChannelList(cameraId) { - console.log(cameraId) - var myEntity = { - deviceId: cameraId, - pageNum: 1, - pageSize: 64 - } - var queryString = $.param(myEntity); - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../device/channel/list?" + queryString, - type: "get", - success: function (data) { - console.log(data); - var arr = data.rows; - $('#selectChn').empty(); - for (var i = 0; i < arr.length; i++) { - console.log(arr[i].chanNo); - console.log(arr[i].name); - //鍏堝垱寤哄ソselect閲岄潰鐨刼ption鍏冪礌 - var option = document.createElement("option"); - //缁檕ption鐨則ext璧嬪��,杩欏氨鏄綘鐐瑰紑涓嬫媺妗嗚兘澶熺湅鍒扮殑涓滆タ - $(option).text(arr[i].name); - $(option).val(arr[i].chanNo); - //鑾峰彇select 涓嬫媺妗嗗璞�,骞跺皢option娣诲姞杩泂elect - $('#selectChn').append(option); - } - } - }) - } - - //棰勮 - $('#preview').click(() => { - var cameraId = $('#selectDev option:selected').val(); - var chanNo = $('#selectChn option:selected').val(); - console.log(cameraId + " " + chanNo) - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../vtdu/media/" + cameraId + "_" + chanNo, - type: "get", - dataType: "json", - success: function (data) { - realView(data.data.webrtcUrl + "/", "video"); - } - }) - }); - //鍋滄 - $('#previewStop').click(() => { - webrtcClient.stop(); - }); - //浜戝彴涓婁笅宸﹀彸 - $("#up").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 2; - var enable = true; - commondMethod(url, code, enable); - - }) - $("#up").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 2; - var enable = false; - commondMethod(url, code, enable); - }) - $("#down").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 8; - var enable = true; - commondMethod(url, code, enable); - }) - $("#down").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 8; - var enable = false; - commondMethod(url, code, enable); - }) - $("#left").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 4; - var enable = true; - commondMethod(url, code, enable); - }) - $("#left").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 4; - var enable = false; - commondMethod(url, code, enable); - }) - $("#right").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 6; - var enable = true; - commondMethod(url, code, enable); - }) - $("#right").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 6; - var enable = false; - commondMethod(url, code, enable); - }) - //鍙樺�� - $("#controlZoomIn").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 10; - var enable = true; - commondMethod(url, code, enable); - }) - $("#controlZoomIn").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 10; - var enable = false; - commondMethod(url, code, enable); - }) - $("#controlZoomOut").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 11; - var enable = true; - commondMethod(url, code, enable); - }) - $("#controlZoomOut").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 11; - var enable = false; - commondMethod(url, code, enable); - }) - //鍙樼劍 - $("#controlFocusNear").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 12; - var enable = true; - commondMethod(url, code, enable); - }) - $("#controlFocusNear").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 12; - var enable = false; - commondMethod(url, code, enable); - }) - $("#controlFocusFar").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 13; - var enable = true; - commondMethod(url, code, enable); - }) - $("#controlFocusFar").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 13; - var enable = false; - commondMethod(url, code, enable); - }) - //鍏夊湀 - $("#controlIrisOpen").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 14; - var enable = true; - commondMethod(url, code, enable); - }) - $("#controlIrisOpen").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 14; - var enable = false; - commondMethod(url, code, enable); - }) - $("#controlIrisClose").mousedown(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 15; - var enable = true; - commondMethod(url, code, enable); - }) - $("#controlIrisClose").mouseup(function () { - var url = "../cameraSdk/PTZControlWithSpeed"; - var code = 15; - var enable = false; - commondMethod(url, code, enable); - }) - - $("#setPreset").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "presetIndex": 1}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/setPreset", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data); - } - }) - }) - $("#gotoPreset").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "presetIndex": 1}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/gotoPreset", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data); - } - }) - }) - $("#getPTZ").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/getPTZ", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (datas) { - console.log(datas); - $("#p").val(datas.data.p); - $("#t").val(datas.data.t); - $("#z").val(datas.data.z); - } - }) - }) - $("#setPTZ").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - var p = $('#p').val(); - var t = $('#t').val(); - var z = $('#z').val(); - //瀹氫箟涓�涓甫鏈塎ap瀛楁鐨勫疄浣撳璞� - var myEntity = { - chanNo: chanNo, - cameraId: cameraId, - ptzMap: { - p: p, - t: t, - z: z - } - }; - console.log(opt) - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/setPTZ", - type: "post", - dataType: "json", - data: JSON.stringify(myEntity), - success: function (data) { - console.log(data); - } - }) - }) - $("#setTargetPostion").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - var camera = cameraMap.get(cameraId); - var camP = camera.longitude + ',' + camera.latitude + ',' + camera.altitude; - var targetP = $('#targetPostion').val(); - var arr = targetP.split(","); - arr = arr.map(item => parseFloat(item)); - //瀹氫箟涓�涓甫鏈塎ap瀛楁鐨勫疄浣撳璞� - var myEntity = { - chanNo: chanNo, - cameraId: cameraId, - targetPosition: arr - }; - console.log(myEntity) - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/setTargetPosition", - type: "post", - dataType: "json", - data: JSON.stringify(myEntity), - success: function (data) { - console.log(data); - } - }) - }) - $("#setZeroPTZ").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/setZeroPTZ", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data); - } - }) - }) - $("#WiperPwron").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "enable": true, "code": 16}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/PTZControlWithSpeed", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data); - } - }) - }) - var defogflag = true; - $("#Defogcfg").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; - optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; - if (defogflag) { - $(this).text("鍏抽棴閫忛浘"); - defogflag = false; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/defogcfg", - type: "post", - dataType: "json", - data: JSON.stringify(optOpen), - success: function (data) { - console.log(data); - } - }) - } else { - $(this).text("寮�鍚�忛浘"); - defogflag = true; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/defogcfg", - type: "post", - dataType: "json", - data: JSON.stringify(optClose), - success: function (data) { - console.log(data); - } - }) - } - - }) - var infrareflag = true; - $("#Infrarecfg").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; - optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; - if (infrareflag) { - $(this).text("鍏抽棴绾㈠"); - infrareflag = false; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/infrarecfg", - type: "post", - dataType: "json", - data: JSON.stringify(optOpen), - success: function (data) { - console.log(data); - } - }) - } else { - - $(this).text("寮�鍚孩澶�"); - infrareflag = true; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/infrarecfg", - type: "post", - dataType: "json", - data: JSON.stringify(optClose), - success: function (data) { - console.log(data); - } - }) - } - }) - var focusModeflag = true; - $("#FocusMode").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; - optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; - if (focusModeflag) { - $(this).text("鑷姩鑱氱劍"); - focusModeflag = false; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/focusMode", - type: "post", - dataType: "json", - data: JSON.stringify(optOpen), - success: function (data) { - console.log(data); - } - }) - } else { - $(this).text("鎵嬪姩鑱氱劍"); - focusModeflag = true; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/focusMode", - type: "post", - dataType: "json", - data: JSON.stringify(optClose), - success: function (data) { - console.log(data); - } - }) - } - }) - $("#getFocusPos").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/getFocusPos", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (datas) { - console.log(datas); - $("#focus").val(datas.data); - } - }) - }) - var heateRpwronflag = true; - $("#HeateRpwron").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; - optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; - if (heateRpwronflag) { - $(this).text("鍏抽棴浜戝彴鍔犵儹"); - heateRpwronflag = false; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/heateRpwron", - type: "post", - dataType: "json", - data: JSON.stringify(optOpen), - success: function (data) { - console.log(data); - } - }) - } else { - $(this).text("寮�鍚簯鍙板姞鐑�"); - heateRpwronflag = true; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/heateRpwron", - type: "post", - dataType: "json", - data: JSON.stringify(optClose), - success: function (data) { - console.log(data); - } - }) - } - }) - var CameraDeicingflag = true; - $("#CameraDeicing").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; - optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; - if (CameraDeicingflag) { - $(this).text("鍏抽棴闀滃ご鍔犵儹"); - CameraDeicingflag = false; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/cameraDeicing", - type: "post", - dataType: "json", - data: JSON.stringify(optOpen), - success: function (data) { - console.log(data); - } - }) - } else { - $(this).text("寮�鍚暅澶村姞鐑�"); - CameraDeicingflag = true; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/cameraDeicing", - type: "post", - dataType: "json", - data: JSON.stringify(optClose), - success: function (data) { - console.log(data); - } - }) - } - }) - $("#realCutPic").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/captureJPEGPicture", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data.data); - $("#imgContainer").attr("src", "data:image/png;base64," + data.data); - } - }) - }) - $("#saveCutPic").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/picCutCate", - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data.data); - setTimeout(() => { - $('#imgContainer').attr('src', data.data); - }, 1000) - - } - }) - }) - var recordflag = true; - $("#record").click(function () { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; - optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; - if (recordflag) { - $(this).text("鍋滄褰曞儚"); - recordflag = false; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/record", - type: "post", - dataType: "json", - data: JSON.stringify(optOpen), - success: function (data) { - console.log(data); - } - }) - } else { - $(this).text("寮�濮嬪綍鍍�"); - recordflag = true; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../cameraSdk/record", - dataType: "json", - data: JSON.stringify(optClose), - type: "post", - success: function (data) { - console.log(data); - } - }) - } - }) - - var cameraMap = new Map(); - - /*浜戝彴鍏叡鏂规硶*/ - function commondMethod(url, code, enable) { - cameraId = $('#selectDev option:selected').val(); - chanNo = $('#selectChn option:selected').val(); - opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 4, "enable": enable, "code": code}; - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: url, - type: "post", - dataType: "json", - data: JSON.stringify(opt), - success: function (data) { - console.log(data); - } - }) - } - - $('video').click(function (e) { - var cameraId = $('#selectDev option:selected').val(); - var chanNo = $('#selectChn option:selected').val(); - console.log(cameraId + " " + chanNo) - $.ajax({ - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token - }, - url: "../vtdu/media/" + cameraId + "_" + chanNo, - type: "get", - dataType: "json", - success: function (data) { - realView(data.data.webrtcUrl + "/", e.target.id); - } - }) - }); - - let webrtcClient; - //whep鎿嶄綔鏂规硶 - const restartPause = 2000; - const unquoteCredential = (v) => ( - JSON.parse(`"${v}"`) - ); - 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(whepUrl, videoId) { - this.video = videoId; - this.wurl = new URL('whep', whepUrl); - this.pc = null; - this.restartTimeout = null; - this.eTag = ''; - this.queuedCandidates = []; - this.start(); - } - - start() { - console.log("requesting ICE servers"); - fetch(this.wurl, { - 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"); - console.log(this.wurl); - fetch(this.wurl, { - 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('ETag'); - this.eTag = res.headers.get("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.wurl, { - 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; - } - } - } - - function realView(whepUrl, videoId) { - console.log(whepUrl) - webrtcClient = new WHEPClient(whepUrl, videoId); - } -</script> -</body> -</html> diff --git a/server/mediamtx/mediamtx.yml b/server/mediamtx/mediamtx.yml index 81ca38d..9427049 100644 --- a/server/mediamtx/mediamtx.yml +++ b/server/mediamtx/mediamtx.yml @@ -222,7 +222,7 @@ password: '' # List of interfaces that will be used to gather IPs to send # to the counterpart to establish a connection. -webrtcICEInterfaces: [] +webrtcICEInterfaces: [192.168.1.1] # List of public IP addresses that are to be used as a host. # This is used typically for servers that are behind 1:1 D-NAT. webrtcICEHostNAT1To1IPs: [192.168.1.227] -- Gitblit v1.9.3