zlm 录像api

This commit is contained in:
shikong 2024-02-10 19:07:51 +08:00
parent c993a15711
commit 14d9a80759
15 changed files with 434 additions and 1 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<GetMp4RecordFileRespData> {
}

View File

@ -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<String> paths;
private String rootPath;
}

View File

@ -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 hls1 mp4
*/
@Builder.Default
private Integer type = 1;
/**
* 添加的流的应用名例如live
*/
private String app;
/**
* 添加的流的id名
*/
private String stream;
}

View File

@ -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;
}

View File

@ -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 hls1 mp4
*/
@Builder.Default
private Integer type = 1;
/**
* 添加的流的应用名例如live
*/
private String app;
/**
* 添加的流的id名
*/
private String stream;
/**
* 录像保存目录
*/
private String customizedPath;
/**
* mp4 录像切片时间大小,单位秒 0 则采用配置项
*/
private int maxSecond = 0;
}

View File

@ -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;
}

View File

@ -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 hls1 mp4
*/
@Builder.Default
private Integer type = 1;
/**
* 添加的流的应用名例如live
*/
private String app;
/**
* 添加的流的id名
*/
private String stream;
}

View File

@ -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;
}

View File

@ -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<DelFFmpegSourceResp> 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);
}

View File

@ -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<DelFFmpegSourceResp> 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);
}
}

View File

@ -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

View File

@ -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<AddStreamProxyResp> 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<AddFFmpegSourceResp> 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<List<MediaResp>> 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();
}
}