ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
@@ -1,17 +1,15 @@ package com.ruoyi.media.controller; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.media.domain.MediaInfo; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.media.domain.StreamInfo; import com.ruoyi.media.service.IMediaService; import com.ruoyi.media.service.impl.MediaService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -24,39 +22,45 @@ **/ @RestController @Api(tags = "流媒体接口") @Anonymous public class MediaController { @RequestMapping("/media/stream") public class MediaController extends BaseController { @Resource private IMediaService mediaService; @PostMapping("/add") @ApiOperation("增加转码") public AjaxResult addPath(@RequestBody MediaInfo mediaInfo) { String rtsp = mediaService.addPath(mediaInfo.getCameraId(), mediaInfo.getRtspPath()); @ApiOperationSupport(includeParameters = {"streamInfo.name","streamInfo.rtspSource"}) public AjaxResult addPath(@RequestBody StreamInfo streamInfo) { String rtsp = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource()); return AjaxResult.success(rtsp); } @PostMapping("/remove") @ApiOperation("移除转码") @ApiOperationSupport(includeParameters={"mediaInfo.cameraId"}) public AjaxResult removePath(@RequestBody MediaInfo mediaInfo) { mediaService.removePath(mediaInfo.getCameraId()); @ApiOperationSupport(includeParameters = {"streamInfo.name"}) public AjaxResult removePath(@RequestBody StreamInfo streamInfo) { mediaService.removePath(streamInfo.getName()); return AjaxResult.success(); } @GetMapping("/getPaths") @ApiOperation("获取当前转码列表") public AjaxResult getPaths() { return AjaxResult.success(mediaService.paths()); } @GetMapping("/getRtspSessions") @ApiOperation("获取rtsp会话列表") public AjaxResult getRtspSessions() { return AjaxResult.success(mediaService.rtspsessions()); } @GetMapping("/getRtspConns") @ApiOperation("获取rtsp连接列表") public AjaxResult getRtspConns() { return AjaxResult.success(mediaService.rtspconns()); } @GetMapping("/getRtspSessionById") @ApiOperation("按ID查询会话") public AjaxResult getRtspSessionById(String sessionId) { @@ -68,9 +72,53 @@ public AjaxResult getPushStreams() { return AjaxResult.success(mediaService.getPushStreams()); } @GetMapping("/getPullStreams") @ApiOperation("获取拉流信息") public AjaxResult getPullStreams() { return AjaxResult.success(mediaService.getPullStreams()); } @PreAuthorize("@ss.hasPermi('media:stream:list')") @GetMapping("/pushList") @ApiOperation("获取推流列表") public TableDataInfo getPushStreamList() { startPage(); return getDataTable(mediaService.getPushStreamList()); } /** * 新增流媒体推流 */ @PreAuthorize("@ss.hasPermi('media:stream:add')") @PostMapping @ApiOperationSupport(includeParameters = {"streamInfo.name"}) public AjaxResult add(@RequestBody StreamInfo streamInfo) throws InterruptedException { String rtsp = mediaService.addPath(streamInfo.getName(), streamInfo.getRtspSource()); return AjaxResult.success(rtsp); } /** * 删除流媒体推流 */ @PreAuthorize("@ss.hasPermi('media:stream:remove')") @DeleteMapping("/path/{name}") public AjaxResult remove(@PathVariable String name) { mediaService.removePath(name); return AjaxResult.success(); } /** * 删除流媒体拉流 */ @PreAuthorize("@ss.hasPermi('media:stream:remove')") @DeleteMapping("/{id}") public AjaxResult removePullStreamSession(@PathVariable String id) { return AjaxResult.success(mediaService.kickRtspSession(id)); } @PreAuthorize("@ss.hasPermi('media:stream:list')") @GetMapping("/pullList") @ApiOperation("获取拉流列表") public TableDataInfo getPullStreamList() { startPage(); return getDataTable(mediaService.getPullStreamList()); } } ard-work/src/main/java/com/ruoyi/media/domain/MediaInfo.java
File was deleted ard-work/src/main/java/com/ruoyi/media/domain/RtspSession.java
@@ -1,5 +1,6 @@ package com.ruoyi.media.domain; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; @@ -13,8 +14,9 @@ */ @Data public class RtspSession { private String cameraId; private String name; private String id; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date created; private String remoteAddr; private String state; ard-work/src/main/java/com/ruoyi/media/domain/StreamInfo.java
New file @@ -0,0 +1,60 @@ package com.ruoyi.media.domain; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import java.util.Date; /** * @Description:流信息 * @ClassName: PushStreamInfo * @Author: 刘苏义 * @Date: 2023年07月15日8:40:37 * @Version: 1.0 **/ @Data @JsonInclude(value = JsonInclude.Include.NON_NULL) public class StreamInfo { /** * 会话ID */ String id; /** * 路径名称 */ String name; /** * 源rtsp地址 */ String rtspSource; /** * rtsp播放地址 */ String rtspUrl; /** * 传输方式 */ String protocol; /** * 上行流量 */ String upTraffic; /** * 下行流量 */ String downTraffic; /** * 拉流数量 */ Integer num; /** * 开始时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date beginTime; /** * 远程主机 */ String remoteAddr; } ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
@@ -1,6 +1,7 @@ package com.ruoyi.media.service; import com.ruoyi.media.domain.Items; import com.ruoyi.media.domain.RtspSession; import com.ruoyi.media.domain.StreamInfo; import java.util.List; @@ -11,7 +12,15 @@ public List<Items>paths(); public List<Items> rtspconns(); public List<Items> rtspsessions(); RtspSession getRtspSessionById(String sessionId); List<RtspSession>getPushStreams(); List<RtspSession>getPullStreams(); List<StreamInfo>getPushStreamList(); List<StreamInfo>getPullStreamList(); Boolean kickRtspSession(String sessionId); } ard-work/src/main/java/com/ruoyi/media/service/impl/MediaService.java
@@ -4,12 +4,15 @@ import com.ruoyi.media.domain.*; import com.ruoyi.media.service.IMediaService; import com.ruoyi.utils.forest.MediaClient; import com.ruoyi.utils.tools.ArdTool; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @Description: @@ -42,9 +45,11 @@ //CPU软解码编码 String cmd = "ffmpeg -rtsp_transport tcp -i \"" + rtspPath + "\" -vcodec libx264 -preset:v ultrafast -r 25 -threads 4 -b:v 4096k -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; //GPU硬解码编码 -hwaccel cuvid -c:v h264_cuvid 使用cuda解码 -c:v h264_nvenc 使用cuda编码 //String cmd = "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport udp -i \"" + rtspPath + "\" -c:v h264_nvenc -r 25 -threads 4 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; mediaInfo.setRunoninit(cmd); mediaInfo.setRunoninitrestart(true); //String cmd = "ffmpeg -hwaccel cuvid -c:v h264_cuvid -rtsp_transport udp -i \"" + rtspPath + "\" -c:v h264_nvenc -r 25 -threads 4 -b:v 4096k -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; // mediaInfo.setRunoninit(cmd); // mediaInfo.setRunoninitrestart(true); mediaInfo.setRunondemand(cmd); mediaInfo.setRunondemandrestart(true); mediaClient.addPath(apiUrl, name, mediaInfo); return rtspUrl + name; } @@ -94,11 +99,10 @@ String list = mediaClient.paths(apiUrl); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List<Items> items = jsonsRoot.getItems(); for(Items item:items) { for (Items item : items) { Source source = item.getSource(); RtspSession rtspSession = getRtspSessionById(source.getId()); rtspSession.setCameraId(item.getName()); rtspSession.setName(item.getName()); rtspSessions.add(rtspSession); } return rtspSessions; @@ -111,15 +115,132 @@ String list = mediaClient.paths(apiUrl); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List<Items> items = jsonsRoot.getItems(); for(Items item:items) { for (Items item : items) { List<Readers> readers = item.getReaders(); for(Readers reader:readers) { RtspSession rtspSession = getRtspSessionById(reader.getId()); rtspSession.setCameraId(item.getName()); rtspSession.setName(item.getName()); rtspSessions.add(rtspSession); } } return rtspSessions; } @Override public List<StreamInfo> getPushStreamList() { List<StreamInfo> PushStreamInfoList = new ArrayList<>(); String apiUrl = "http://" + mediamtxHost + ":9997/v2"; String list = mediaClient.paths(apiUrl); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List<Items> items = jsonsRoot.getItems(); for (Items item : items) { StreamInfo info = new StreamInfo(); //ID String name = item.getName(); info.setName(name); //RTSP播放地址 String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name; info.setRtspUrl(rtspUrl); Source source = item.getSource(); if(source==null) { continue; } RtspSession rtspSession = getRtspSessionById(source.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); //RTSP源地址 //String runoninit = item.getConf().getRunoninit(); String runoninit = item.getConf().getRunondemand(); String regex = "rtsp://[^\\s\"]+"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(runoninit); if (matcher.find()) { info.setRtspSource(matcher.group()); } //传输协议 regex = "-rtsp_transport\\s+(\\w+)"; pattern = Pattern.compile(regex); matcher = pattern.matcher(runoninit); if (matcher.find()) { info.setProtocol(matcher.group(1)); } //拉流数量 List<Readers> readers = item.getReaders(); info.setNum(readers.size()); //推流服务器 info.setRemoteAddr(rtspSession.getRemoteAddr()); PushStreamInfoList.add(info); } return PushStreamInfoList; } @Override public List<StreamInfo> getPullStreamList() { List<StreamInfo> PullStreamInfoList = new ArrayList<>(); String apiUrl = "http://" + mediamtxHost + ":9997/v2"; String list = mediaClient.paths(apiUrl); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List<Items> items = jsonsRoot.getItems(); for (Items item : items) { StreamInfo info = new StreamInfo(); //ID String name = item.getName(); info.setName(name); //RTSP播放地址 String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name; info.setRtspUrl(rtspUrl); List<Readers> readers = item.getReaders(); for (Readers reader : readers) { 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.setRemoteAddr(rtspSession.getRemoteAddr()); PullStreamInfoList.add(info); } } return PullStreamInfoList; } @Override public Boolean kickRtspSession(String sessionId) { try { String apiUrl = "http://" + mediamtxHost + ":9997/v2"; mediaClient.kick(apiUrl, sessionId); return true; } catch (Exception ex) { return false; } } } ard-work/src/main/java/com/ruoyi/rongcloud/controller/RongCloudController.java
New file @@ -0,0 +1,34 @@ package com.ruoyi.rongcloud.controller; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.rongcloud.service.RongCloudService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @Description: * @ClassName: RongCloudController * @Author: 刘苏义 * @Date: 2023年07月15日16:57:56 * @Version: 1.0 **/ @RestController @Api(tags = "融云接口") @Anonymous public class RongCloudController { @Resource RongCloudService rongCloudService; @GetMapping("getToken") @ApiOperation("获取token") public AjaxResult getToken(String userId,String name) { String token = rongCloudService.getToken(userId, name); return AjaxResult.success(token); } } ard-work/src/main/java/com/ruoyi/rongcloud/domain/TokenParam.java
New file @@ -0,0 +1,16 @@ package com.ruoyi.rongcloud.domain; import lombok.Data; /** * @Description: * @ClassName: TokenParam * @Author: 刘苏义 * @Date: 2023年07月15日16:54:39 * @Version: 1.0 **/ @Data public class TokenParam { String userId; String name; } ard-work/src/main/java/com/ruoyi/rongcloud/service/Impl/RongCloudServiceImpl.java
New file @@ -0,0 +1,47 @@ package com.ruoyi.rongcloud.service.Impl; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.rongcloud.domain.TokenParam; import com.ruoyi.rongcloud.service.RongCloudService; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.utils.forest.RongCloudClient; import okhttp3.FormBody; import okhttp3.RequestBody; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Date; /** * @Description: * @ClassName: RongCloudServiceImpl * @Author: 刘苏义 * @Date: 2023年07月15日16:49:12 * @Version: 1.0 **/ @Service public class RongCloudServiceImpl implements RongCloudService { @Resource RongCloudClient rongCloudClient; @Resource ISysConfigService iSysConfigService; @Override public String getToken(String userId,String name) { // String appKey="6tnym11s1h717"; // String appSecret="IwcGk2flZr"; String rongyunApiUrl = iSysConfigService.selectConfigByKey("rongyun"); String appKey = iSysConfigService.selectConfigByKey("RongCloudAppKey"); String appSecret = iSysConfigService.selectConfigByKey("RongCloudAppSecret"); TokenParam param=new TokenParam(); param.setUserId(userId); param.setName(name); String Nonce = String.valueOf(Math.random()).split("\\.")[1]; String Timestamp = String.valueOf(new Date().getTime()); String Signature = DigestUtils.sha1Hex(appSecret + Nonce + Timestamp);//App Secret + Nonce + Timestamp String token = rongCloudClient.getToken(rongyunApiUrl,appKey, Nonce, Timestamp, Signature, param); return token; } } ard-work/src/main/java/com/ruoyi/rongcloud/service/RongCloudService.java
New file @@ -0,0 +1,13 @@ package com.ruoyi.rongcloud.service; /** * @Description: * @ClassName: RongCloudService * @Author: 刘苏义 * @Date: 2023年07月15日16:48:36 * @Version: 1.0 **/ public interface RongCloudService { String getToken(String userId,String name); } ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
@@ -45,8 +45,15 @@ String rtspconns(@Var("apiUrl") String apiUrl); /** * 按id查询rtsp会话 * 按sessionId查询rtsp会话 */ @Get("{apiUrl}/rtspsessions/get/{sessionId}") String getRtspsessionById(@Var("apiUrl") String apiUrl, @Var("sessionId") String sessionId); /** * 按sessionId删除rtsp会话 */ @Post("{apiUrl}/rtspsessions/kick/{sessionId}") String kick(@Var("apiUrl") String apiUrl, @Var("sessionId") String sessionId); } ard-work/src/main/java/com/ruoyi/utils/forest/RongCloudClient.java
New file @@ -0,0 +1,23 @@ package com.ruoyi.utils.forest; import com.dtflys.forest.annotation.BaseRequest; import com.dtflys.forest.annotation.Body; import com.dtflys.forest.annotation.Post; import com.dtflys.forest.annotation.Var; import com.ruoyi.rongcloud.domain.TokenParam; @BaseRequest( baseURL = "{rongyunApiUrl}", sslProtocol = "TLS" ) public interface RongCloudClient { @Post(url = "/user/getToken.json", headers = { "App-Key:{AppKey}", "Nonce:{Nonce}", "Timestamp:{Timestamp}", "Signature:{Signature}", }) public String getToken(@Var("rongyunApiUrl") String rongyunApiUrl,@Var("AppKey") String AppKey, @Var("Nonce") String Nonce, @Var("Timestamp") String Timestamp, @Var("Signature") String Signature, @Body TokenParam param); } ard-work/src/main/java/com/ruoyi/utils/tools/ArdTool.java
@@ -5,6 +5,7 @@ import javax.annotation.Resource; import java.lang.reflect.Field; import java.text.DecimalFormat; import java.time.LocalTime; import java.util.*; @@ -87,4 +88,37 @@ return 1; } } /** * 文件大小智能转换 * 会将文件大小转换为最大满足单位 * @param size(文件大小,单位为B) * @return 文件大小 */ public static String formatFileSize(Long size) { String sizeName = null; if (1024 * 1024 > size && size >= 1024) { sizeName = String.format("%.2f", size.doubleValue() / 1024) + "KB"; } else if (1024 * 1024 * 1024 > size && size >= 1024 * 1024) { sizeName = String.format("%.2f", size.doubleValue() / (1024 * 1024)) + "MB"; } else if (size >= 1024 * 1024 * 1024) { sizeName = String.format("%.2f", size.doubleValue() / (1024 * 1024 * 1024)) + "GB"; } else { sizeName = size.toString() + "B"; } return sizeName; } /** * 文件大小智能转换 * 会将文件大小转换为最大满足单位 * @param size(文件大小,单位为B) * @return 文件大小 */ public static String readableFileSize(long size) { if (size <= 0) { return "0"; } final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); return new DecimalFormat("#,###.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; } }