From 8a6e4993680d87deee0e69417abf0ffcd014448a Mon Sep 17 00:00:00 2001 From: 15683799673 Date: Sun, 10 Aug 2025 19:04:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=81=E5=AA=92=E4=BD=93=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/sis/config/ZLMediaKitConfig.java | 2 + .../controller/SisDeviceManageController.java | 1 + .../zkmedia/ZKLmediaController.java | 118 ++--- .../sis/sdk/zkmedia/ZLMediaKitService.java | 156 ++----- .../sdk/zkmedia/ZLMediaKitServiceImpl.java | 423 ++++++++++-------- .../sis/sdk/zkmedia/model/AddStreamProxy.java | 47 +- .../sis/sdk/zkmedia/model/StreamPlay.java | 42 ++ .../sis/service/ISisDeviceChannelService.java | 10 +- .../impl/SisDeviceChannelServiceImpl.java | 10 + 9 files changed, 395 insertions(+), 414 deletions(-) create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/StreamPlay.java diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/ZLMediaKitConfig.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/ZLMediaKitConfig.java index d6289ff2..f99d4898 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/ZLMediaKitConfig.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/ZLMediaKitConfig.java @@ -21,4 +21,6 @@ public class ZLMediaKitConfig { private String vhost; + private String pushStreamUrl; + } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/SisDeviceManageController.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/SisDeviceManageController.java index b3547588..f31ed593 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/SisDeviceManageController.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/SisDeviceManageController.java @@ -108,6 +108,7 @@ public class SisDeviceManageController extends BaseController { return toAjax(sisDeviceManageService.deleteWithValidByIds(List.of(ids), true)); } + @GetMapping("/tree") public R>> tree() { return R.ok(sisDeviceManageService.tree()); diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/zkmedia/ZKLmediaController.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/zkmedia/ZKLmediaController.java index db73d170..fc214650 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/zkmedia/ZKLmediaController.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/controller/zkmedia/ZKLmediaController.java @@ -1,14 +1,11 @@ package org.dromara.sis.controller.zkmedia; -import cn.hutool.core.util.IdUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; -import org.dromara.sis.api.enums.FactoryNoEnum; -import org.dromara.sis.sdk.zkmedia.MediaServerUtils; import org.dromara.sis.sdk.zkmedia.ZLMediaKitService; import org.dromara.sis.sdk.zkmedia.model.AddStreamProxy; import org.dromara.sis.sdk.zkmedia.model.AddStreamProxyResp; -import org.dromara.sis.sdk.zkmedia.model.StartStreamProxy; +import org.dromara.sis.sdk.zkmedia.model.StreamPlay; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -31,35 +28,15 @@ public class ZKLmediaController { @Resource private ZLMediaKitService zlMediaKitService; - - private static final String HIK_REALTIME_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/Streaming/Channels/%s"; - private static final String DAHUA_REALTIME_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/cam/realmonitor?channel=%s&subtype=0"; - - private static final String HIK_HISTORY_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/Streaming/tracks/%s?starttime=%s&endtime=%s"; - private static final String DAHUA_HISTORY_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/cam/playback?channel=%s&subtype=0&starttime=%s&endtime=%s"; - /** * 创建拉流任务,返回null代表创建拉流任务失败 * * @param data 创建拉流设备信息(如果外网不建议使用这种方式) - * @return 返回拉流任务信息 + * @return 返回播放地址 */ @PostMapping("/realtime/add") - public R alarm(@RequestBody @Validated AddStreamProxy data) { - StartStreamProxy proxy = new StartStreamProxy(); - proxy.setApp("realtime"); - // 实时流不用每次都去拉流,流不存在的情况下在拉取 - String streanStr = data.getVideoIp() + "_" + data.getChannelId(); -// proxy.setStream(SecureUtil.md5(streanStr)); - proxy.setStream(IdUtil.fastSimpleUUID()); - if (FactoryNoEnum.HIK.getCode().equals(data.getFactoryNo())) { - proxy.setUrl(String.format(HIK_REALTIME_RTSP_TEMPLATE, data.getAccount(), data.getPwd(), data.getVideoIp(), data.getVideoPort(), data.getChannelId())); - } else if (FactoryNoEnum.DAHUA.getCode().equals(data.getFactoryNo())) { - proxy.setUrl(String.format(DAHUA_REALTIME_RTSP_TEMPLATE, data.getAccount(), data.getPwd(), data.getVideoIp(), data.getVideoPort(), data.getChannelId())); - } else { - throw new RuntimeException("未知的设备类型!"); - } - AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addStreamProxy(proxy); + public R addStreamProxy(@RequestBody @Validated AddStreamProxy data) { + AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addStreamProxy(data); if (addStreamProxyResp != null) { return R.ok(addStreamProxyResp); } @@ -67,23 +44,29 @@ public class ZKLmediaController { } /** - * 创建拉流任务,返回null代表创建拉流任务失败 + * 新增ffmpeg拉流代理 * * @param data 创建拉流设备信息(如果外网不建议使用这种方式) - * @return 返回拉流任务信息 + * @return 返回播放地址 */ - @PostMapping("/realtime/addFfmpeg") - public R addFfmpegTask(@RequestBody @Validated AddStreamProxy data) { - String sourceUrl = ""; - if (FactoryNoEnum.HIK.getCode().equals(data.getFactoryNo())) { - sourceUrl = String.format(HIK_REALTIME_RTSP_TEMPLATE, data.getAccount(), data.getPwd(), data.getVideoIp(), data.getVideoPort(), data.getChannelId()); - } else if (FactoryNoEnum.DAHUA.getCode().equals(data.getFactoryNo())) { - sourceUrl = String.format(DAHUA_REALTIME_RTSP_TEMPLATE, data.getAccount(), data.getPwd(), data.getVideoIp(), data.getVideoPort(), data.getChannelId()); - } else { - throw new RuntimeException("未知的设备类型!"); + @PostMapping("/f/proxy") + public R addFfmpegStreamProxy(@RequestBody @Validated AddStreamProxy data) { + AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addFfmpegStreamProxy(data); + if (addStreamProxyResp != null) { + return R.ok(addStreamProxyResp); } + return R.fail(); + } - AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addFFmpegSource(sourceUrl); + /** + * 通过是设备ip和通道新增拉流代理 + * + * @param streamPlay 拉流参数 + * @return 返回播放地址 + */ + @PostMapping("/proxy") + public R addStreamProxy(@RequestBody @Validated StreamPlay streamPlay) { + AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addStreamProxy(streamPlay); if (addStreamProxyResp != null) { return R.ok(addStreamProxyResp); } @@ -92,62 +75,17 @@ public class ZKLmediaController { /** - * 创建历史回放拉流任务,返回null代表创建拉流任务失败 + * 通过是设备ip和通道新增ffmpeg拉流代理 * - * @param data 创建拉流设备信息(如果外网不建议使用这种方式) - * @return 返回拉流任务信息 + * @param data 拉流参数 + * @return 返回播放地址 */ - @PostMapping("/history/add") - public R history(@RequestBody @Validated AddStreamProxy data) throws InterruptedException { - StartStreamProxy proxy = new StartStreamProxy(); - proxy.setApp("history"); - String s = IdUtil.fastSimpleUUID(); - proxy.setStream(s); - if ("DS1010".equals(data.getFactoryNo())) { - String pattern = "yyyyMMdd'T'HHmmss'Z'"; - String startTime = MediaServerUtils.formatTimestamp(data.getStartTime(), "yyyyMMdd'T'HHmmss'Z'"); - String endTime = MediaServerUtils.formatTimestamp(data.getEndTime(), "yyyyMMdd'T'HHmmss'Z'"); - proxy.setUrl(String.format(HIK_HISTORY_RTSP_TEMPLATE, data.getAccount(), data.getPwd(), data.getVideoIp(), data.getVideoPort(), data.getChannelId(), startTime, endTime)); - } else if ("DS1014".equals(data.getFactoryNo())) { - String startTime = MediaServerUtils.formatTimestamp(data.getStartTime(), "yyyy_MM_dd_HH_mm_ss"); - String endTime = MediaServerUtils.formatTimestamp(data.getEndTime(), "yyyy_MM_dd_HH_mm_ss"); - proxy.setUrl(String.format(DAHUA_HISTORY_RTSP_TEMPLATE, data.getAccount(), data.getPwd(), data.getVideoIp(), data.getVideoPort(), data.getChannelId(), startTime, endTime)); - } else { - throw new RuntimeException("未知的设备类型!"); - } - AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addStreamProxy(proxy); + @PostMapping("/f/proxy") + public R addFfmpegStreamProxy(@RequestBody @Validated StreamPlay data) { + AddStreamProxyResp addStreamProxyResp = zlMediaKitService.addFfmpegStreamProxy(data); if (addStreamProxyResp != null) { return R.ok(addStreamProxyResp); } return R.fail(); } - - /** - * 查询设备的信息 - * - * @param data 设备ip - * @return - */ - /*@PostMapping("/queryMediaInfo") - public R queryMediaInfo(@RequestBody VideoConfigTreeDTO data) { - if (StringUtils.isEmpty(data.getVideoIp())) { - throw new BizException(ErrorType.VIDEOIP_FAIL, "视频IP不能为空"); - } - logger.info("查询视频信息,参数:{}", JSONObject.toJSON(data.getVideoIp())); - TpEqpAcquisitionDTO result = tpEqpAcquisitionService.queryConfigByEqpNo(data.getVideoIp()); - logger.info("查询视频信息,返回信息:{}", JSONObject.toJSON(result)); - return BizResultVO.success(result); - } - - @PostMapping("/history/delete/{stream}") - public BizResultVO delete(@PathVariable("stream") String stream) throws InterruptedException { - StartStreamProxy proxy = new StartStreamProxy(); - proxy.setApp("history"); - proxy.setStream(stream); - String ss = zlMediaKitService.delStreamProxy(proxy); - if (ss != null) { - return BizResultVO.success(ss); - } - return BizResultVO.fail(ErrorType.ADD_STREAMPROXY_FAIL, null); - }*/ } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitService.java index 9e744d2a..3e922beb 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitService.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitService.java @@ -1,64 +1,46 @@ package org.dromara.sis.sdk.zkmedia; +import org.dromara.sis.sdk.zkmedia.model.AddStreamProxy; import org.dromara.sis.sdk.zkmedia.model.AddStreamProxyResp; -import org.dromara.sis.sdk.zkmedia.model.R; import org.dromara.sis.sdk.zkmedia.model.StartStreamProxy; -import org.dromara.sis.sdk.zkmedia.model.ThreadsLoadDelay; - -import java.util.List; +import org.dromara.sis.sdk.zkmedia.model.StreamPlay; +/** + * 拉流服务 + * @author lxj + */ public interface ZLMediaKitService { /** - * 获取各后台 epoll(或 select)线程负载以及延时 + * 创建视频流拉流代理 + * + * @param addStreamProxy 拉流参数 + * @return 视频播放地址 */ - R> getWorkThreadsLoad(); + AddStreamProxyResp addStreamProxy(AddStreamProxy addStreamProxy); /** - * 获取ZLMediaKit服务器配置信息 + * 通过 fork FFmpeg 进程的方式拉流代理,支持任意协议 */ - Object getServerConfig(); + AddStreamProxyResp addFfmpegStreamProxy(AddStreamProxy proxy); + /** - * 设置服务器配置 + * 创建视频流拉流代理 + * + * @param streamPlay 拉流参数 + * @return 视频播放地址 */ - Object setServerConfig(); + AddStreamProxyResp addStreamProxy(StreamPlay streamPlay); /** - * 重启服务器,只有 Daemon 方式才能重启,否则是直接关闭! + * 增加FFmpeg 拉流代理 + * + * @param streamPlay 拉流参数 + * @return 视频流播放地址 */ - Object restartServer(); - - /** - * 获取流列表,可选筛选参数 - */ - Object getMediaList(); - - /** - * 关闭流(目前所有类型的流都支持关闭) - */ - Object closeStreams(); - - /** - * 获取所有 TcpSession 列表(获取所有 tcp 客户端相关信息) - */ - Object getAllSession(); - - /** - * 断开 tcp 连接,比如说可以断开 rtsp、rtmp 播放器等 - */ - Object kickSession(); - - /** - * 断开 tcp 连接,比如说可以断开 rtsp、rtmp 播放器等 - */ - Object kickSessions(); - - /** - * 动态添加 rtsp/rtmp/hls/http-ts/http-flv 拉流代理(只支持 H264/H265/aac/G711/opus 负载) - */ - AddStreamProxyResp addStreamProxy(StartStreamProxy startStreamProxy); + AddStreamProxyResp addFfmpegStreamProxy(StreamPlay streamPlay); /** * (流注册成功后,也可以使用close_streams接口替代) @@ -66,94 +48,14 @@ public interface ZLMediaKitService { */ String delStreamProxy(StartStreamProxy startStreamProxy); - /** - * 通过 fork FFmpeg 进程的方式拉流代理,支持任意协议 - */ - AddStreamProxyResp addFFmpegSource(String src_url); /** - * 流注册成功后,也可以使用close_streams接口替代 + * 删除ffmpeg 拉流任务 + * + * @param key 流id + * @return */ - Boolean delFFmpegSource(String key); + Boolean delFfmpegSource(String key); - /** - * 获取 rtp 代理时的某路 ssrc rtp 信息 - */ - Object getRtpInfo(); - - /** - * 搜索文件系统,获取流对应的录像文件列表或日期文件夹列表 - */ - Object getMp4RecordFile(); - - /** - * 开始录制 hls 或 MP4 - */ - Object startRecord(); - - /** - * 停止录制流 - */ - Object stopRecord(); - - /** - * 获取流录制状态 - */ - Object isRecording(); - - /** - * 获取截图或生成实时截图并返回 - */ - Object getSnap(); - - /** - * 创建 GB28181 RTP 接收端口,如果该端口接收数据超时,则会自动被回收(不用调用 closeRtpServer 接口) - */ - Object openRtpServer(); - - /** - * 关闭 GB28181 RTP 接收端口 - */ - Object closeRtpServer(); - - /** - * 获取 openRtpServer 接口创建的所有 RTP 服务器 - */ - Object listRtpServer(); - - /** - * 作为 GB28181 客户端,启动 ps-rtp 推流,支持 rtp/udp 方式;该接口支持 rtsp/rtmp 等协议转 ps-rtp 推流。第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试。 - */ - Object startSendRtp(); - - /** - * 停止 GB28181 ps-rtp 推流 - */ - Object stopSendRtp(); - - /** - * 获取主要对象个数统计,主要用于分析内存性能 - */ - Object getStatistic(); - - /** - * 添加 rtsp/rtmp 主动推流(把本服务器的直播流推送到其他服务器去) - */ - Object addStreamPusherProxy(); - - /** - * 关闭推流,可以使用close_streams接口关闭源直播流也可以停止推流) - */ - Object delStreamPusherProxy(); - - /** - * 获取版本信息,如分支,commit id, 编译时间 - */ - Object getVersion(); - - /** - * 获取某个流观看者列表 - */ - Object getMediaPlayerList(); } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitServiceImpl.java index 77775311..c04a8249 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitServiceImpl.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/ZLMediaKitServiceImpl.java @@ -1,18 +1,18 @@ package org.dromara.sis.sdk.zkmedia; import cn.hutool.core.util.IdUtil; -import com.alibaba.fastjson2.TypeReference; +import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; +import org.dromara.sis.api.enums.FactoryNoEnum; import org.dromara.sis.config.ZLMediaKitConfig; -import org.dromara.sis.sdk.zkmedia.model.AddStreamProxyResp; -import org.dromara.sis.sdk.zkmedia.model.R; -import org.dromara.sis.sdk.zkmedia.model.StartStreamProxy; -import org.dromara.sis.sdk.zkmedia.model.ThreadsLoadDelay; +import org.dromara.sis.domain.SisDeviceChannel; +import org.dromara.sis.sdk.zkmedia.model.*; +import org.dromara.sis.service.ISisDeviceChannelService; +import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -26,13 +26,41 @@ public class ZLMediaKitServiceImpl implements ZLMediaKitService { @Resource private ZLMediaKitConfig zlmConfig; + @Resource + private ISisDeviceChannelService deviceChannelService; + + // 海康实时流取流模板 + private static final String HIK_REALTIME_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/Streaming/Channels/%s"; + // 大华实时流取流模板 + private static final String DAHUA_REALTIME_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/cam/realmonitor?channel=%s&subtype=0"; + // 海康历史流取流模板 + private static final String HIK_HISTORY_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/Streaming/tracks/%s?starttime=%s&endtime=%s"; + // 大华历史流取流模板 + private static final String DAHUA_HISTORY_RTSP_TEMPLATE = "rtsp://%s:%s@%s:%s/cam/playback?channel=%s&subtype=0&starttime=%s&endtime=%s"; + //流媒体请求模板 + private static final String STREAM_REQUEST_TEMLATE = "http://%s:%d/index/api/"; + // RTMP 视频流播放模板 + private static final String RTMP_PLAY_URL = "rtmp://%s:%d/%s/%s"; + // RTSP 视频流播放模板 + private static final String RTSP_PLAY_URL = "rtsp://%s:%d/%s/%s"; + // HTTP-FLV 视频流播放模板 + private static final String HTTP_FLV_PLAY_URL = "http://%s:%d/%s/%s.live.flv"; + // WS-FLV 视频流播放模板 + private static final String WS_FLV_PLAY_URL = "ws://%s:%d/%s/%s.live.flv"; + // HLS 视频流播放模板 + private static final String HLS_FLV_PLAY_URL = "http://%s:%d/%s/%s/hls.m3u8"; + // MP4 视频流播放模板 + private static final String MP4_FLV_PLAY_URL = "http://%s:%d/%s/%s.live.mp4"; + // 推流地址 + + private static volatile String ZLM_REQUEST_PREFIX = null; public String getRequestUrl(String uri) { if (ZLM_REQUEST_PREFIX == null) { synchronized (ZLMediaKitServiceImpl.class) { if (ZLM_REQUEST_PREFIX == null) { - ZLM_REQUEST_PREFIX = String.format("http://%s:%d/index/api/", zlmConfig.getIp(), zlmConfig.getHttpPort()); + ZLM_REQUEST_PREFIX = String.format(STREAM_REQUEST_TEMLATE, zlmConfig.getIp(), zlmConfig.getHttpPort()); } } } @@ -45,84 +73,96 @@ public class ZLMediaKitServiceImpl implements ZLMediaKitService { return params; } - @Override - public R> getWorkThreadsLoad() { - String url = getRequestUrl("getThreadsLoad"); - Map commonParams = getCommonParams(); - return HttpClientUtil.get(url, commonParams, new TypeReference>>() { - }); - } - - @Override - public Object getServerConfig() { - return null; - } - - @Override - public Object setServerConfig() { - return null; - } - - @Override - public Object restartServer() { - return null; - } - - @Override - public Object getMediaList() { - return null; - } - - @Override - public Object closeStreams() { - return null; - } - - @Override - public Object getAllSession() { - return null; - } - - @Override - public Object kickSession() { - return null; - } - - @Override - public Object kickSessions() { - return null; - } - /** - * 获取拉流地址 + * 设置返回参数的视频播放地址 */ private AddStreamProxyResp setPlayerUrl(String app, String streamId, AddStreamProxyResp resp) { if (resp == null) { resp = new AddStreamProxyResp(); } // RTMP 播放地址 - resp.setRtmp(String.format("rtmp://%s:%d/%s/%s", zlmConfig.getIp(), zlmConfig.getRtmpPort(), app, streamId)); + resp.setRtmp(String.format(RTMP_PLAY_URL, zlmConfig.getIp(), zlmConfig.getRtmpPort(), app, streamId)); // RTSP 播放地址 - resp.setRtsp(String.format("rtsp://%s:%d/%s/%s", zlmConfig.getIp(), zlmConfig.getRtspPort(), app, streamId)); + resp.setRtsp(String.format(RTSP_PLAY_URL, zlmConfig.getIp(), zlmConfig.getRtspPort(), app, streamId)); // HTTP-FLV 播放地址 - resp.setFlv(String.format("http://%s:%d/%s/%s.live.flv", zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); - resp.setWsFlv(String.format("ws://%s:%d/%s/%s.live.flv", zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); + resp.setFlv(String.format(HTTP_FLV_PLAY_URL, zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); + resp.setWsFlv(String.format(WS_FLV_PLAY_URL, zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); // HLS 播放地址 - resp.setHls(String.format("http://%s:%d/%s/%s/hls.m3u8", zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); + resp.setHls(String.format(HLS_FLV_PLAY_URL, zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); // MP4 播放地址 - resp.setMp4(String.format("http://%s:%d/%s/%s.live.mp4", zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); + resp.setMp4(String.format(MP4_FLV_PLAY_URL, zlmConfig.getIp(), zlmConfig.getHttpPort(), app, streamId)); return resp; } + /** + * 生成视频流地址 + * + * @param factoryNo 厂商 + * @param account 账号 + * @param pwd 密码 + * @param ip ip + * @param port 端口 + * @param channel 通道 + * @return 返回视频流播放地址 + */ + private String getRealTimeStreamUrl(String factoryNo, String account, String pwd, String ip, Integer port, String channel) { + if (FactoryNoEnum.HIK.getCode().equals(factoryNo)) { + return String.format(HIK_REALTIME_RTSP_TEMPLATE, account, pwd, ip, port, channel); + } else if (FactoryNoEnum.DAHUA.getCode().equals(factoryNo)) { + return String.format(DAHUA_REALTIME_RTSP_TEMPLATE, account, pwd, ip, port, channel); + } else { + throw new RuntimeException("未知的设备类型!"); + } + } - @Override - public AddStreamProxyResp addStreamProxy(StartStreamProxy startStreamProxy) { + private String getPlayBackStreamUrl(String factoryNo, String account, String pwd, String ip, Integer port, String channel, String startTime, String endTime) { + if (FactoryNoEnum.HIK.getCode().equals(factoryNo)) { + String pattern = "yyyyMMdd'T'HHmmss'Z'"; + String st = MediaServerUtils.formatTimestamp(startTime, pattern); + String et = MediaServerUtils.formatTimestamp(endTime, pattern); + return String.format(HIK_HISTORY_RTSP_TEMPLATE, account, pwd, ip, port, channel, st, et); + } else if (FactoryNoEnum.DAHUA.getCode().equals(factoryNo)) { + String pattern = "yyyy_MM_dd_HH_mm_ss"; + String st = MediaServerUtils.formatTimestamp(startTime, pattern); + String et = MediaServerUtils.formatTimestamp(endTime, pattern); + return String.format(DAHUA_HISTORY_RTSP_TEMPLATE, account, pwd, ip, port, channel, st, et); + } else { + throw new RuntimeException("未知的设备类型!"); + } + } + + /** + * 获取拉流地址 + * streamType=1 是实时流, streamType=2 是历史流 + * factoryNo = 1 是海康视频流, factoryNo= 2 是大华视频流 + * + * @param factoryNo 厂商编码 + * @param streamType 流类型 + * @param account 账号 + * @param pwd 密码 + * @param ip ip + * @param port 端口 + * @param channel 通道 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return + */ + private String getPullStreamUrl(String factoryNo, Integer streamType, String account, String pwd, String ip, Integer port, String channel, String startTime, String endTime) { + if (streamType == 1) { + return getRealTimeStreamUrl(factoryNo, account, pwd, ip, port, channel); + } else { + return getPlayBackStreamUrl(factoryNo, account, pwd, ip, port, channel, startTime, endTime); + } + } + + + private AddStreamProxyResp addStreamProxy(String app, String stream, String url) { Map commonParams = getCommonParams(); commonParams.put("vhost", zlmConfig.getVhost()); - commonParams.put("app", startStreamProxy.getApp()); - commonParams.put("stream", startStreamProxy.getStream()); - commonParams.put("url", startStreamProxy.getUrl()); - commonParams.put("rtp_type", startStreamProxy.getRtpType()); + commonParams.put("app", app); + commonParams.put("stream", stream); + commonParams.put("url", url); + commonParams.put("rtp_type", 1); R result = HttpClientUtil.get(getRequestUrl("addStreamProxy"), commonParams, AddStreamProxyResp.class); if (result != null) { if (result.getCode() == 0) { @@ -132,11 +172,132 @@ public class ZLMediaKitServiceImpl implements ZLMediaKitService { if (result.getCode() == -1) { log.info("拉流任务已存在,返回播放地址。"); } - return setPlayerUrl(startStreamProxy.getApp(), startStreamProxy.getStream(), result.getData()); + return setPlayerUrl(app, stream, result.getData()); } return null; } + /** + * 查询设备通道信息 + * + * @param deviceIp 设备ip + * @param channelId 设备通道id + * @return 返回通道信息 + */ + private SisDeviceChannel getDeviceChannel(String deviceIp, String channelId) { + SisDeviceChannel channel = deviceChannelService.queryChannels(deviceIp, channelId); + if (channel == null) { + throw new RuntimeException("设备通道不存在!"); + } + return channel; + } + + @Override + public AddStreamProxyResp addStreamProxy(AddStreamProxy proxy) { + // 实时流 + String app = proxy.getStreamType() == 1 ? "realtime" : "history"; + String url = getPullStreamUrl(proxy.getFactoryNo(), proxy.getStreamType(), proxy.getAccount(), proxy.getPwd(), + proxy.getVideoIp(), proxy.getVideoPort(), proxy.getChannelId(), proxy.getStartTime(), proxy.getEndTime()); + String stream = IdUtil.fastSimpleUUID(); + return addStreamProxy(app, stream, url); + } + + @Override + public AddStreamProxyResp addStreamProxy(StreamPlay streamPlay) { + // 查询设备通道信息 + SisDeviceChannel channel = getDeviceChannel(streamPlay.getDeviceIp(), streamPlay.getChannelNo()); + // 构建拉流地址 + String app = null; + String url = null; + // 判断是走录像机拉流还是设备直接拉流 + if (streamPlay.getStreamType() == 1) { + app = "realtime"; + // 当前如果配置了录像机会默认走录像机拉流 + if (StrUtil.isNotEmpty(channel.getNvrIp())) { + url = getRealTimeStreamUrl(channel.getNvrFactoryNo(), channel.getNvrAccount(), channel.getNvrPwd(), channel.getNvrIp(), channel.getNvrPort(), channel.getNvrChannelNo()); + } else { + url = getRealTimeStreamUrl(channel.getFactoryNo(), channel.getDeviceAccount(), channel.getDevicePwd(), channel.getDeviceIp(), channel.getDevicePort(), channel.getChannelNo()); + } + } else { + app = "history"; + // 校验通道是否配置了nvr和cvr + if (StrUtil.isNotEmpty(channel.getNvrIp())) { + url = getPlayBackStreamUrl(channel.getNvrFactoryNo(), channel.getNvrAccount(), channel.getNvrPwd(), channel.getNvrIp(), channel.getNvrPort(), channel.getNvrChannelNo() + , streamPlay.getStartTime(), streamPlay.getEndTime()); + } else { + throw new RuntimeException("设备机未配置存储设备,无法拉取视频流。"); + } + } + String stream = IdUtil.fastSimpleUUID(); + return addStreamProxy(app, stream, url); + } + + @Nullable + private AddStreamProxyResp getAddStreamProxyResp(String url) { + // 生成拉流任务key + String taskKey = IdUtil.fastSimpleUUID(); +// String targetUrl = "rtmp://127.0.0.1/live/" + taskKey; + // ffmpeg 推流地址 + String targetUrl = zlmConfig.getPushStreamUrl() + taskKey; + Map commonParams = getCommonParams(); + commonParams.put("src_url", url); + commonParams.put("dst_url", targetUrl); + commonParams.put("timeout_ms", 10000); + commonParams.put("enable_hls", false); + commonParams.put("enable_mp4", false); + R result = HttpClientUtil.get(getRequestUrl("addFFmpegSource"), commonParams, AddStreamProxyResp.class); + if (result != null) { + if (result.getCode() == 0) { + log.info("创建FFMPEG拉流任务成功."); + } + // 此处代表拉流任务已存在 + if (result.getCode() == -1) { + log.info("FFMPEG拉流任务已存在,返回播放地址。"); + } + if (result.getData() != null) { + + return setPlayerUrl("live", taskKey, result.getData()); + } + } + return null; + } + + + @Override + public AddStreamProxyResp addFfmpegStreamProxy(AddStreamProxy proxy) { + String url = ""; + if (proxy.getStreamType() == 1) { + url = getRealTimeStreamUrl(proxy.getFactoryNo(), proxy.getAccount(), proxy.getPwd(), proxy.getVideoIp(), proxy.getVideoPort(), proxy.getChannelId()); + } else { + url = getPlayBackStreamUrl(proxy.getFactoryNo(), proxy.getAccount(), proxy.getPwd(), proxy.getVideoIp(), proxy.getVideoPort(), proxy.getChannelId(), proxy.getStartTime(), proxy.getEndTime()); + } + return getAddStreamProxyResp(url); + } + + @Override + public AddStreamProxyResp addFfmpegStreamProxy(StreamPlay streamPlay) { + // 查询设备通道信息 + SisDeviceChannel channel = getDeviceChannel(streamPlay.getDeviceIp(), streamPlay.getChannelNo()); + String url = null; + if (streamPlay.getStreamType() == 1) { + // 当前如果配置了录像机会默认走录像机拉流 + if (StrUtil.isNotEmpty(channel.getNvrIp())) { + url = getRealTimeStreamUrl(channel.getNvrFactoryNo(), channel.getNvrAccount(), channel.getNvrPwd(), channel.getNvrIp(), channel.getNvrPort(), channel.getNvrChannelNo()); + } else { + url = getRealTimeStreamUrl(channel.getFactoryNo(), channel.getDeviceAccount(), channel.getDevicePwd(), channel.getDeviceIp(), channel.getDevicePort(), channel.getChannelNo()); + } + } else { + // 校验通道是否配置了nvr和cvr + if (StrUtil.isNotEmpty(channel.getNvrIp())) { + url = getPlayBackStreamUrl(channel.getNvrFactoryNo(), channel.getNvrAccount(), channel.getNvrPwd(), channel.getNvrIp(), channel.getNvrPort(), channel.getNvrChannelNo() + , streamPlay.getStartTime(), streamPlay.getEndTime()); + } else { + throw new RuntimeException("设备机未配置存储设备,无法拉取视频流。"); + } + } + return getAddStreamProxyResp(url); + } + @Override public String delStreamProxy(StartStreamProxy startStreamProxy) { Map commonParams = getCommonParams(); @@ -161,132 +322,12 @@ public class ZLMediaKitServiceImpl implements ZLMediaKitService { return null; } - @Override - public AddStreamProxyResp addFFmpegSource(String src_url) { - - // 生成拉流任务key - String taskKey = IdUtil.fastSimpleUUID(); - String targetUrl = "rtmp://127.0.0.1/live/" + taskKey; - Map commonParams = getCommonParams(); - commonParams.put("src_url", src_url); - commonParams.put("dst_url", targetUrl); - commonParams.put("timeout_ms", 10000); - commonParams.put("enable_hls", false); - commonParams.put("enable_mp4", false); - R result = HttpClientUtil.get(getRequestUrl("addFFmpegSource"), commonParams, AddStreamProxyResp.class); - if (result != null) { - if (result.getCode() == 0) { - log.info("创建FFMPEG拉流任务成功."); - } - // 此处代表拉流任务已存在 - if (result.getCode() == -1) { - log.info("FFMPEG拉流任务已存在,返回播放地址。"); - } - - // RTMP 播放地址 - result.getData().setRtmp(String.format("rtmp://%s:%d/live/%s", zlmConfig.getIp(), zlmConfig.getRtmpPort(), taskKey)); - // RTSP 播放地址 - result.getData().setRtsp(String.format("rtsp://%s:%d/live/%s", zlmConfig.getIp(), zlmConfig.getRtspPort(), taskKey)); - // HTTP-FLV 播放地址 - result.getData().setFlv(String.format("http://%s:%d/live/%s.live.flv", zlmConfig.getIp(), zlmConfig.getHttpPort(), taskKey)); - result.getData().setWsFlv(String.format("ws://%s:%d/live/%s.live.flv", zlmConfig.getIp(), zlmConfig.getHttpPort(), taskKey)); - // HLS 播放地址 - result.getData().setHls(String.format("http://%s:%d/live/%s/hls.m3u8", zlmConfig.getIp(), zlmConfig.getHttpPort(), taskKey)); - // MP4 播放地址 - result.getData().setMp4(String.format("http://%s:%d/live/%s.live.mp4", zlmConfig.getIp(), zlmConfig.getHttpPort(), taskKey)); - return result.getData(); - } - return null; - } @Override - public Boolean delFFmpegSource(String key) { + public Boolean delFfmpegSource(String key) { Map commonParams = getCommonParams(); R result = HttpClientUtil.get(getRequestUrl("addFFmpegSource?key=" + key), commonParams, String.class); - if (result != null && result.getCode() == 0) { - return true; - } - return false; - } - - @Override - public Object getRtpInfo() { - return null; - } - - @Override - public Object getMp4RecordFile() { - return null; - } - - @Override - public Object startRecord() { - return null; - } - - @Override - public Object stopRecord() { - return null; - } - - @Override - public Object isRecording() { - return null; - } - - @Override - public Object getSnap() { - return null; - } - - @Override - public Object openRtpServer() { - return null; - } - - @Override - public Object closeRtpServer() { - return null; - } - - @Override - public Object listRtpServer() { - return null; - } - - @Override - public Object startSendRtp() { - return null; - } - - @Override - public Object stopSendRtp() { - return null; - } - - @Override - public Object getStatistic() { - return null; - } - - @Override - public Object addStreamPusherProxy() { - return null; - } - - @Override - public Object delStreamPusherProxy() { - return null; - } - - @Override - public Object getVersion() { - return null; - } - - @Override - public Object getMediaPlayerList() { - return null; + return result != null && result.getCode() == 0; } } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/AddStreamProxy.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/AddStreamProxy.java index afd1c406..c69dd878 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/AddStreamProxy.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/AddStreamProxy.java @@ -5,31 +5,68 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; +/** + * 增加拉流代理 + * + * @author lxj + */ @Data public class AddStreamProxy { + /** + * 设备ip + */ @NotBlank private String videoIp; + /** + * 设备端口 + */ private Integer videoPort; + /** + * 厂商 + */ @NotBlank private String factoryNo; + /** + * 账号 + */ @NotBlank private String account; + /** + * 设备密码 + */ @NotBlank private String pwd; + /** + * 通道id + */ @NotNull private String channelId; - - private String startTime; - - private String endTime; - + /** + * 流应用名称 + */ private String stream; + /** + * 流类型1:实时流,2:历史流 + */ + private Integer streamType = 1; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/StreamPlay.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/StreamPlay.java new file mode 100644 index 00000000..97d110a8 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/zkmedia/model/StreamPlay.java @@ -0,0 +1,42 @@ +package org.dromara.sis.sdk.zkmedia.model; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** + * 实时视频流播放请求参数 + * + * @author lxj + */ +@Data +public class StreamPlay { + + /** + * 设备编号 + */ + @NotEmpty + private String deviceIp; + + /** + * 设备通道号 + */ + @NotEmpty + private String channelNo; + + /** + * 流类型1:实时流,2:历史流 + */ + private Integer streamType = 1; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java index 76130014..8cf5edb7 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java @@ -1,5 +1,6 @@ package org.dromara.sis.service; +import jakarta.validation.constraints.NotEmpty; import org.dromara.common.core.domain.TreeNode; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -129,5 +130,12 @@ public interface ISisDeviceChannelService { */ Boolean updateDeviceChannelState(String deviceIp, Integer onLineState); - + /** + * 通过设备ip和通道编码查询设备通道信息 + * + * @param deviceIp 设备ip + * @param channelNo 设备通道号 + * @return 返回通道信息 + */ + SisDeviceChannel queryChannels(@NotEmpty String deviceIp, @NotEmpty String channelNo); } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java index e24d642e..6099d031 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java @@ -328,4 +328,14 @@ public class SisDeviceChannelServiceImpl implements ISisDeviceChannelService { lqw.eq(SisDeviceChannel::getDeviceIp, deviceIp); return baseMapper.update(lqw) > 0; } + + + @Override + public SisDeviceChannel queryChannels(String deviceIp, String channelNo) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(SisDeviceChannel::getDeviceIp, deviceIp); + lqw.eq(SisDeviceChannel::getChannelNo, channelNo) + .or().eq(SisDeviceChannel::getNvrChannelNo, channelNo); + return baseMapper.selectOne(lqw); + } }