diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/DeleteRecordDirectory.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/DeleteRecordDirectory.java new file mode 100644 index 0000000..95498c2 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/DeleteRecordDirectory.java @@ -0,0 +1,41 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@Data +public class DeleteRecordDirectory { + /** + * 添加的流的虚拟主机,例如__defaultVhost__ + */ + @Builder.Default + private String vhost = "__defaultVhost__"; + + /** + * 添加的流的应用名,例如live + */ + private String app; + + /** + * 添加的流的id名 + */ + private String stream; + + /** + * 流的录像日期,格式为 2020-02-01,如果不是完整的日期,那么是搜索录像文件夹列表,否则搜索对应日期下的 mp4 文件列表 + */ + private String period; + + /** + * 自定义搜索路径,与 startRecord 方法中的 customized_path 一样,默认为配置文件的路径 + */ + private String customizedPath; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/DeleteRecordDirectoryResp.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/DeleteRecordDirectoryResp.java new file mode 100644 index 0000000..8656991 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/DeleteRecordDirectoryResp.java @@ -0,0 +1,19 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import cn.skcks.docking.gb28181.media.dto.status.ResponseStatus; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@Data +public class DeleteRecordDirectoryResp { + private ResponseStatus code; + private String path; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFile.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFile.java new file mode 100644 index 0000000..422387a --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFile.java @@ -0,0 +1,41 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@Data +public class GetMp4RecordFile { + /** + * 添加的流的虚拟主机,例如__defaultVhost__ + */ + @Builder.Default + private String vhost = "__defaultVhost__"; + + /** + * 添加的流的应用名,例如live + */ + private String app; + + /** + * 添加的流的id名 + */ + private String stream; + + /** + * 流的录像日期,格式为 2020-02-01,如果不是完整的日期,那么是搜索录像文件夹列表,否则搜索对应日期下的 mp4 文件列表 + */ + private String period; + + /** + * 自定义搜索路径,与 startRecord 方法中的 customized_path 一样,默认为配置文件的路径 + */ + private String customizedPath; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFileResp.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFileResp.java new file mode 100644 index 0000000..0d1d769 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFileResp.java @@ -0,0 +1,9 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import cn.skcks.docking.gb28181.media.dto.response.ZlmResponse; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class GetMp4RecordFileResp extends ZlmResponse { + +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFileRespData.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFileRespData.java new file mode 100644 index 0000000..f66daf8 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/GetMp4RecordFileRespData.java @@ -0,0 +1,15 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class GetMp4RecordFileRespData { + private List paths; + private String rootPath; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/IsRecording.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/IsRecording.java new file mode 100644 index 0000000..1157737 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/IsRecording.java @@ -0,0 +1,36 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class IsRecording { + /** + * 添加的流的虚拟主机,例如__defaultVhost__ + */ + @Builder.Default + private String vhost = "__defaultVhost__"; + + /** + * 0 为 hls,1 为 mp4 + */ + @Builder.Default + private Integer type = 1; + + /** + * 添加的流的应用名,例如live + */ + private String app; + /** + * 添加的流的id名 + */ + private String stream; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/IsRecordingResp.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/IsRecordingResp.java new file mode 100644 index 0000000..9ef5eef --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/IsRecordingResp.java @@ -0,0 +1,19 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import cn.skcks.docking.gb28181.media.dto.status.ResponseStatus; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class IsRecordingResp { + private ResponseStatus code; + private Boolean status; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StartRecord.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StartRecord.java new file mode 100644 index 0000000..350699a --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StartRecord.java @@ -0,0 +1,46 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Data +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class StartRecord { + /** + * 添加的流的虚拟主机,例如__defaultVhost__ + */ + @Builder.Default + private String vhost = "__defaultVhost__"; + + /** + * 0 为 hls,1 为 mp4 + */ + @Builder.Default + private Integer type = 1; + + /** + * 添加的流的应用名,例如live + */ + private String app; + /** + * 添加的流的id名 + */ + private String stream; + + /** + * 录像保存目录 + */ + private String customizedPath; + + /** + * mp4 录像切片时间大小,单位秒,置 0 则采用配置项 + */ + private int maxSecond = 0; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StartRecordResp.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StartRecordResp.java new file mode 100644 index 0000000..d00732d --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StartRecordResp.java @@ -0,0 +1,19 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import cn.skcks.docking.gb28181.media.dto.status.ResponseStatus; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class StartRecordResp { + private ResponseStatus code; + private Boolean result; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StopRecord.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StopRecord.java new file mode 100644 index 0000000..a6f50c9 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StopRecord.java @@ -0,0 +1,36 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class StopRecord { + /** + * 添加的流的虚拟主机,例如__defaultVhost__ + */ + @Builder.Default + private String vhost = "__defaultVhost__"; + + /** + * 0 为 hls,1 为 mp4 + */ + @Builder.Default + private Integer type = 1; + + /** + * 添加的流的应用名,例如live + */ + private String app; + /** + * 添加的流的id名 + */ + private String stream; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StopRecordResp.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StopRecordResp.java new file mode 100644 index 0000000..6ed22d1 --- /dev/null +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/dto/record/StopRecordResp.java @@ -0,0 +1,19 @@ +package cn.skcks.docking.gb28181.media.dto.record; + +import cn.skcks.docking.gb28181.media.dto.status.ResponseStatus; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class StopRecordResp { + private ResponseStatus code; + private Boolean result; +} diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaHttpClient.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaHttpClient.java index f5ffcb0..a56c091 100644 --- a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaHttpClient.java +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaHttpClient.java @@ -4,6 +4,7 @@ import cn.skcks.docking.gb28181.media.dto.config.ServerConfig; import cn.skcks.docking.gb28181.media.dto.media.GetMediaList; import cn.skcks.docking.gb28181.media.dto.media.MediaResp; import cn.skcks.docking.gb28181.media.dto.proxy.*; +import cn.skcks.docking.gb28181.media.dto.record.*; import cn.skcks.docking.gb28181.media.dto.response.ZlmResponse; import cn.skcks.docking.gb28181.media.dto.rtp.*; import cn.skcks.docking.gb28181.media.dto.snap.Snap; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; +import java.util.Map; @FeignClient(name="zlmMediaServerProxy", url = "${media.url}", configuration = IgnoreSSLFeignClientConfig.class) public interface ZlmMediaHttpClient { @@ -83,4 +85,18 @@ public interface ZlmMediaHttpClient { @GetMapping("/index/api/delFFmpegSource") ZlmResponse delFFmpegSource(@RequestParam String secret, @RequestParam String key); + @PostMapping("/index/api/startRecord") + StartRecordResp startRecord(@RequestParam String secret, @RequestBody StartRecord params); + + @PostMapping("/index/api/stopRecord") + StopRecordResp stopRecord(@RequestParam String secret, @RequestBody StopRecord params); + + @PostMapping("/index/api/isRecording") + IsRecordingResp isRecording(@RequestParam String secret, @RequestBody IsRecording params); + + @PostMapping("/index/api/getMp4RecordFile") + GetMp4RecordFileResp getMp4RecordFile(@RequestParam String secret, @RequestBody GetMp4RecordFile params); + + @PostMapping("/index/api/deleteRecordDirectory") + DeleteRecordDirectoryResp deleteRecordDirectory(@RequestParam String secret, @RequestBody DeleteRecordDirectory params); } diff --git a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaService.java b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaService.java index c737369..c634e5b 100644 --- a/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaService.java +++ b/zlmediakit-service/src/main/java/cn/skcks/docking/gb28181/media/proxy/ZlmMediaService.java @@ -4,14 +4,19 @@ import cn.skcks.docking.gb28181.media.dto.config.ServerConfig; import cn.skcks.docking.gb28181.media.dto.media.GetMediaList; import cn.skcks.docking.gb28181.media.dto.media.MediaResp; import cn.skcks.docking.gb28181.media.dto.proxy.*; +import cn.skcks.docking.gb28181.media.dto.record.*; import cn.skcks.docking.gb28181.media.dto.response.ZlmResponse; import cn.skcks.docking.gb28181.media.dto.rtp.*; import cn.skcks.docking.gb28181.media.dto.snap.Snap; import cn.skcks.docking.gb28181.media.dto.version.VersionResp; import lombok.Builder; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import java.util.List; +import java.util.Map; @Builder @SuppressWarnings("unused") @@ -173,5 +178,41 @@ public class ZlmMediaService { public ZlmResponse delFfmpegSource(String key){ return exchange.delFFmpegSource(secret, key); } + + + /** + * 开始录制 hls 或 MP4 + */ + public StartRecordResp startRecord(@RequestBody StartRecord params){ + return exchange.startRecord(secret, params); + } + + + /** + * 停止录制流 + */ + public StopRecordResp stopRecord(@RequestBody StopRecord params){ + return exchange.stopRecord(secret, params); + } + + /** + * 获取流录制状态 + */ + public IsRecordingResp isRecording(@RequestBody IsRecording params){ + return exchange.isRecording(secret, params); + } + + + /** + * 搜索文件系统,获取流对应的录像文件列表或日期文件夹列表 + */ + public GetMp4RecordFileResp getMp4RecordFile(@RequestBody GetMp4RecordFile params){ + return exchange.getMp4RecordFile(secret, params); + } + + + public DeleteRecordDirectoryResp deleteRecordDirectory(@RequestBody DeleteRecordDirectory params){ + return exchange.deleteRecordDirectory(secret, params); + } } diff --git a/zlmediakit-service/src/main/resources/application.yml b/zlmediakit-service/src/main/resources/application.yml index 3000647..6eafcc3 100644 --- a/zlmediakit-service/src/main/resources/application.yml +++ b/zlmediakit-service/src/main/resources/application.yml @@ -3,7 +3,7 @@ project: media: ip: 10.10.10.200 - url: 'http://10.10.10.200:5080' + url: 'http://10.10.10.200:5081' # url: 'http://10.10.10.200:12580/anything/' id: amrWMKmbKqoBjRQ9 secret: 4155cca6-2f9f-11ee-85e6-8de4ce2e7333 diff --git a/zlmediakit-service/src/test/java/cn/skcks/docking/gb28181/test/MediaServiceTest.java b/zlmediakit-service/src/test/java/cn/skcks/docking/gb28181/test/MediaServiceTest.java index a91da03..8288605 100644 --- a/zlmediakit-service/src/test/java/cn/skcks/docking/gb28181/test/MediaServiceTest.java +++ b/zlmediakit-service/src/test/java/cn/skcks/docking/gb28181/test/MediaServiceTest.java @@ -1,6 +1,7 @@ package cn.skcks.docking.gb28181.test; import cn.hutool.core.codec.Base64; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.skcks.docking.gb28181.common.json.JsonResponse; import cn.skcks.docking.gb28181.common.json.JsonUtils; @@ -10,6 +11,7 @@ import cn.skcks.docking.gb28181.media.dto.config.ServerConfig; import cn.skcks.docking.gb28181.media.dto.media.GetMediaList; import cn.skcks.docking.gb28181.media.dto.media.MediaResp; import cn.skcks.docking.gb28181.media.dto.proxy.*; +import cn.skcks.docking.gb28181.media.dto.record.*; import cn.skcks.docking.gb28181.media.dto.response.ZlmResponse; import cn.skcks.docking.gb28181.media.dto.response.ZlmResponseConvertor; import cn.skcks.docking.gb28181.media.dto.rtp.*; @@ -31,6 +33,10 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; @Slf4j @SpringBootTest @@ -41,6 +47,7 @@ public class MediaServiceTest { private ZlmMediaService zlmMediaService; @Autowired private ZlmMediaConfig config; + private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); @Test void test(){ @@ -212,4 +219,73 @@ public class MediaServiceTest { GetRtpInfoResp rtpInfo = zlmMediaService.getRtpInfo("test"); log.info("{}", rtpInfo); } + + @SneakyThrows + @Test + void recordTest(){ + CountDownLatch countDownLatch = new CountDownLatch(1); + + String customizedPath = "/tmp/record"; + + DeleteRecordDirectoryResp deleteRecordDirectoryResp = zlmMediaService.deleteRecordDirectory(DeleteRecordDirectory.builder() + .app("live") + .stream("test") + .period(DateUtil.formatDate(DateUtil.date())) + .customizedPath(customizedPath).build()); + log.info("{}", deleteRecordDirectoryResp); + // ZlmResponse addedStreamProxy = zlmMediaService.addStreamProxy(AddStreamProxy.builder() + // .app("live") + // .stream("test") + // .vhost("__defaultVhost__") + // .url("rtsp://10.10.10.200:554/camera/121") + // .build()); + // log.info("addedStreamProxy {}", addedStreamProxy); + ZlmResponse addFFmpegSourceRespZlmResponse = zlmMediaService.addFfmpegSource(AddFFmpegSource.builder() + .dstUrl("rtmp://10.10.10.200:1936/live/test") + .srcUrl("http://10.10.10.200:18183/video") + .timeoutMs(30 * 1000L) + .enableHls(false) + .enableMp4(false) + .build()); + log.info("addFfmpegSource {}", addFFmpegSourceRespZlmResponse); + + ZlmResponse> mediaList = zlmMediaService.getMediaList(GetMediaList.builder() + .schema("rtsp") + .app("live") + .stream("test") + .build()); + log.info("mediaList {}", mediaList); + + + StartRecordResp startRecordResp = zlmMediaService.startRecord(StartRecord.builder() + .app("live") + .stream("test") + .vhost("__defaultVhost__") + .customizedPath(customizedPath) + .build()); + + log.info("startRecordResp {}", startRecordResp); + + scheduledExecutorService.schedule(() -> { + zlmMediaService.delFfmpegSource(addFFmpegSourceRespZlmResponse.getData().getKey()); + // log.info("{}", zlmMediaService.delStreamProxy(addedStreamProxy.getData().getKey())); + + zlmMediaService.getMp4RecordFile(GetMp4RecordFile.builder() + .app("live") + .stream("test") + .period(DateUtil.formatDate(DateUtil.date())) + .customizedPath(customizedPath).build()); + + zlmMediaService.stopRecord(StopRecord.builder() + .app("live") + .stream("test") + .vhost("__defaultVhost__") + .build()); + countDownLatch.countDown(); + }, 15, TimeUnit.SECONDS); + + + + countDownLatch.await(); + } }