支持ABL录像回放

This commit is contained in:
lin
2025-09-17 18:19:33 +08:00
parent 403e7648f9
commit fb9ff67701
20 changed files with 314 additions and 141 deletions

View File

@@ -47,6 +47,9 @@ public class MediaConfig{
@Value("${media.flv-port:0}") @Value("${media.flv-port:0}")
private Integer flvPort = 0; private Integer flvPort = 0;
@Value("${media.mp4-port:0}")
private Integer mp4Port = 0;
@Value("${media.ws-flv-port:0}") @Value("${media.ws-flv-port:0}")
private Integer wsFlvPort = 0; private Integer wsFlvPort = 0;
@@ -56,6 +59,9 @@ public class MediaConfig{
@Value("${media.flv-ssl-port:0}") @Value("${media.flv-ssl-port:0}")
private Integer flvSSlPort = 0; private Integer flvSSlPort = 0;
@Value("${media.mp4-ssl-port:0}")
private Integer mp4SSlPort = 0;
@Value("${media.ws-flv-ssl-port:0}") @Value("${media.ws-flv-ssl-port:0}")
private Integer wsFlvSSlPort = 0; private Integer wsFlvSSlPort = 0;
@@ -164,6 +170,11 @@ public class MediaConfig{
}else { }else {
mediaServer.setFlvPort(flvPort); mediaServer.setFlvPort(flvPort);
} }
if (mp4Port == 0) {
mediaServer.setMp4Port(httpPort);
}else {
mediaServer.setMp4Port(mp4Port);
}
if (wsFlvPort == 0) { if (wsFlvPort == 0) {
mediaServer.setWsFlvPort(httpPort); mediaServer.setWsFlvPort(httpPort);
}else { }else {
@@ -174,6 +185,11 @@ public class MediaConfig{
}else { }else {
mediaServer.setFlvSSLPort(flvSSlPort); mediaServer.setFlvSSLPort(flvSSlPort);
} }
if (mp4SSlPort == 0) {
mediaServer.setMp4SSLPort(httpSSlPort);
}else {
mediaServer.setMp4SSLPort(mp4SSlPort);
}
if (wsFlvSSlPort == 0) { if (wsFlvSSlPort == 0) {
mediaServer.setWsFlvSSLPort(httpSSlPort); mediaServer.setWsFlvSSLPort(httpSSlPort);
}else { }else {

View File

@@ -1,6 +1,5 @@
package com.genersoft.iot.vmp.gb28181.service.impl; package com.genersoft.iot.vmp.gb28181.service.impl;
import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.*; import com.genersoft.iot.vmp.common.*;
import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
@@ -34,7 +33,6 @@ import com.genersoft.iot.vmp.service.ISendRtpServerService;
import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
@@ -981,8 +979,8 @@ public class PlayServiceImpl implements IPlayService {
} }
private void download(MediaServer mediaServerItem, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback<StreamInfo> callback) { private void download(MediaServer mediaServer, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback<StreamInfo> callback) {
if (mediaServerItem == null ) { if (mediaServer == null ) {
callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(),
InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(),
null); null);
@@ -992,7 +990,7 @@ public class PlayServiceImpl implements IPlayService {
int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0); int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0);
// 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起 // 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
RTPServerParam rtpServerParam = new RTPServerParam(); RTPServerParam rtpServerParam = new RTPServerParam();
rtpServerParam.setMediaServerItem(mediaServerItem); rtpServerParam.setMediaServerItem(mediaServer);
rtpServerParam.setSsrcCheck(device.isSsrcCheck()); rtpServerParam.setSsrcCheck(device.isSsrcCheck());
rtpServerParam.setPlayback(true); rtpServerParam.setPlayback(true);
rtpServerParam.setPort(0); rtpServerParam.setPort(0);
@@ -1002,7 +1000,7 @@ public class PlayServiceImpl implements IPlayService {
SSRCInfo ssrcInfo = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, result) -> { SSRCInfo ssrcInfo = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, result) -> {
if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) { if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) {
// hook响应 // hook响应
StreamInfo streamInfo = onPublishHandlerForDownload(mediaServerItem, result.getHookData().getMediaInfo(), device, channel, startTime, endTime); StreamInfo streamInfo = onPublishHandlerForDownload(mediaServer, result.getHookData().getMediaInfo(), device, channel, startTime, endTime);
if (streamInfo == null) { if (streamInfo == null) {
log.warn("[录像下载] 获取流地址信息失败"); log.warn("[录像下载] 获取流地址信息失败");
callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
@@ -1048,24 +1046,24 @@ public class PlayServiceImpl implements IPlayService {
device.isSsrcCheck()); device.isSsrcCheck());
// 初始化redis中的invite消息状态 // 初始化redis中的invite消息状态
InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServerItem.getId(), InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(),
mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.DOWNLOAD, mediaServer.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.DOWNLOAD,
InviteSessionStatus.ready, true); InviteSessionStatus.ready, true);
inviteInfo.setStartTime(startTime); inviteInfo.setStartTime(startTime);
inviteInfo.setEndTime(endTime); inviteInfo.setEndTime(endTime);
inviteStreamService.updateInviteInfo(inviteInfo); inviteStreamService.updateInviteInfo(inviteInfo);
try { try {
cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channel, startTime, endTime, downloadSpeed, cmder.downloadStreamCmd(mediaServer, ssrcInfo, device, channel, startTime, endTime, downloadSpeed,
eventResult -> { eventResult -> {
// 对方返回错误 // 对方返回错误
callback.run(InviteErrorCode.FAIL.getCode(), String.format("录像下载失败, 错误码: %s, %s", eventResult.statusCode, eventResult.msg), null); callback.run(InviteErrorCode.FAIL.getCode(), String.format("录像下载失败, 错误码: %s, %s", eventResult.statusCode, eventResult.msg), null);
receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo); receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo);
sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream());
inviteStreamService.removeInviteInfo(inviteInfo); inviteStreamService.removeInviteInfo(inviteInfo);
}, eventResult ->{ }, eventResult ->{
// 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channel, InviteOKHandler(eventResult, ssrcInfo, mediaServer, device, channel,
callback, inviteInfo, InviteSessionType.DOWNLOAD); callback, inviteInfo, InviteSessionType.DOWNLOAD);
// 注册录像回调事件,录像下载结束后写入下载地址 // 注册录像回调事件,录像下载结束后写入下载地址
@@ -1074,8 +1072,7 @@ public class PlayServiceImpl implements IPlayService {
inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream()); inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream());
log.info("[录像下载] 收到录像写入磁盘消息内容: " + hookData); log.info("[录像下载] 收到录像写入磁盘消息内容: " + hookData);
RecordInfo recordInfo = hookData.getRecordInfo(); RecordInfo recordInfo = hookData.getRecordInfo();
String filePath = recordInfo.getFilePath(); DownloadFileInfo downloadFileInfo = mediaServerService.getDownloadFilePath(mediaServer, recordInfo);
DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType() InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType()
, inviteInfo.getChannelId(), inviteInfo.getStream()); , inviteInfo.getChannelId(), inviteInfo.getStream());
if (inviteInfoForNew != null && inviteInfoForNew.getStreamInfo() != null) { if (inviteInfoForNew != null && inviteInfoForNew.getStreamInfo() != null) {
@@ -1084,7 +1081,7 @@ public class PlayServiceImpl implements IPlayService {
inviteStreamService.updateInviteInfo(inviteInfoForNew, 60*15L); inviteStreamService.updateInviteInfo(inviteInfoForNew, 60*15L);
} }
}; };
Hook hook = Hook.getInstance(HookType.on_record_mp4, "rtp", ssrcInfo.getStream(), mediaServerItem.getId()); Hook hook = Hook.getInstance(HookType.on_record_mp4, "rtp", ssrcInfo.getStream(), mediaServer.getId());
// 设置过期时间,下载失败时自动处理订阅数据 // 设置过期时间,下载失败时自动处理订阅数据
hook.setExpireTime(System.currentTimeMillis() + 24 * 60 * 60 * 1000); hook.setExpireTime(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
subscribe.addSubscribe(hook, hookEventForRecord); subscribe.addSubscribe(hook, hookEventForRecord);
@@ -1092,7 +1089,7 @@ public class PlayServiceImpl implements IPlayService {
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 录像下载: {}", e.getMessage()); log.error("[命令发送失败] 录像下载: {}", e.getMessage());
callback.run(InviteErrorCode.FAIL.getCode(),e.getMessage(), null); callback.run(InviteErrorCode.FAIL.getCode(),e.getMessage(), null);
receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo); receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo);
sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream());
inviteStreamService.removeInviteInfo(inviteInfo); inviteStreamService.removeInviteInfo(inviteInfo);
} }
@@ -1112,11 +1109,7 @@ public class PlayServiceImpl implements IPlayService {
log.warn("[获取下载进度] 未查询到录像下载的信息 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream); log.warn("[获取下载进度] 未查询到录像下载的信息 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream);
return null; return null;
} }
String filePath = allList.get(0).getFilePath();
if (filePath == null) {
log.warn("[获取下载进度] 未查询到录像下载的文件路径 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream);
return null;
}
String mediaServerId = allList.get(0).getMediaServerId(); String mediaServerId = allList.get(0).getMediaServerId();
MediaServer mediaServer = mediaServerService.getOne(mediaServerId); MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) { if (mediaServer == null) {
@@ -1124,7 +1117,7 @@ public class PlayServiceImpl implements IPlayService {
return null; return null;
} }
log.warn("[获取下载进度] 发现下载已经结束,直接从数据库获取到文件 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream); log.warn("[获取下载进度] 发现下载已经结束,直接从数据库获取到文件 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream);
DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServer, filePath); DownloadFileInfo downloadFileInfo = mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(allList.get(0)));
StreamInfo streamInfo = new StreamInfo(); StreamInfo streamInfo = new StreamInfo();
streamInfo.setDownLoadFilePath(downloadFileInfo); streamInfo.setDownLoadFilePath(downloadFileInfo);
streamInfo.setApp(app); streamInfo.setApp(app);

View File

@@ -15,11 +15,12 @@ import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig;
import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.event.media.MediaRecordProcessEvent;
import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
@@ -32,6 +33,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -192,8 +195,10 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
streamInfoResult.setMediaInfo(mediaInfo); if (mediaInfo != null) {
streamInfoResult.setOriginType(mediaInfo.getOriginType()); streamInfoResult.setMediaInfo(mediaInfo);
streamInfoResult.setOriginType(mediaInfo.getOriginType());
}
return streamInfoResult; return streamInfoResult;
} }
@@ -255,18 +260,18 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
} }
// 接受进度通知 // 接受进度通知
@EventListener // @EventListener
public void onApplicationEvent(MediaRecordProcessEvent event) { // public void onApplicationEvent(MediaRecordProcessEvent event) {
CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName()); // CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName());
if (cloudRecordItem == null) { // if (cloudRecordItem == null) {
cloudRecordItem = CloudRecordItem.getInstance(event); // cloudRecordItem = CloudRecordItem.getInstance(event);
cloudRecordItem.setStartTime(event.getStartTime()); // cloudRecordItem.setStartTime(event.getStartTime());
cloudRecordItem.setEndTime(event.getEndTime()); // cloudRecordItem.setEndTime(event.getEndTime());
cloudRecordServiceMapper.add(cloudRecordItem); // cloudRecordServiceMapper.add(cloudRecordItem);
}else { // }else {
cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis()); // cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis());
} // }
} // }
@EventListener @EventListener
public void onApplicationEvent(MediaRecordMp4Event event) { public void onApplicationEvent(MediaRecordMp4Event event) {
InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, null, event.getStream()); InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, null, event.getStream());
@@ -401,8 +406,24 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
} }
@Override @Override
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { public void loadMP4File(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
logger.warn("[abl-loadMP4File] 未实现"); // 解析为 LocalDate
LocalDate localDate = LocalDate.parse(date, DateUtil.DateFormatter);
LocalDateTime startOfDay = localDate.atStartOfDay();
LocalDateTime endOfDay = localDate.atTime(23, 59,59, 999);
String startTime = DateUtil.urlFormatter.format(startOfDay);
String endTime = DateUtil.urlFormatter.format(endOfDay);
ABLResult ablResult = ablresTfulUtils.queryRecordList(mediaServer, app, stream, startTime, endTime);
if (ablResult.getCode() != 0) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), ablResult.getMemo());
}
String resultApp = ablResult.getApp();
String resultStream = ablResult.getStream();
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, resultApp, resultStream, null, null, true);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
} }
@Override @Override
@@ -414,4 +435,39 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) { public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
logger.warn("[abl-setRecordSpeed] 未实现"); logger.warn("[abl-setRecordSpeed] 未实现");
} }
@Override
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) {
// 将filePath作为独立参数传入避免%符号解析问题
String pathTemplate = "%s://%s:%s/%s/%s__ReplayFMP4RecordFile__%s?download_speed=6";
DownloadFileInfo info = new DownloadFileInfo();
info.setHttpPath(
String.format(
pathTemplate,
"http",
mediaServer.getStreamIp(),
mediaServer.getHttpPort(),
recordInfo.getApp(),
recordInfo.getStream(),
recordInfo.getFileName()
)
);
if (mediaServer.getHttpSSlPort() > 0) {
info.setHttpsPath(
String.format(
pathTemplate,
"https",
mediaServer.getStreamIp(),
mediaServer.getHttpSSlPort(),
recordInfo.getApp(),
recordInfo.getStream(),
recordInfo.getFileName()
)
);
}
return info;
}
} }

View File

@@ -1,8 +1,10 @@
package com.genersoft.iot.vmp.media.abl; package com.genersoft.iot.vmp.media.abl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.abl.bean.ABLResult; import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import okhttp3.*; import okhttp3.*;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -12,8 +14,10 @@ import org.springframework.stereotype.Component;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -426,6 +430,12 @@ public class ABLRESTfulUtils {
} }
public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("app", app); param.put("app", app);
param.put("stream", stream); param.put("stream", stream);
@@ -443,6 +453,11 @@ public class ABLRESTfulUtils {
} }
public ABLResult addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { public ABLResult addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("app", app); param.put("app", app);
param.put("stream", stream); param.put("stream", stream);

View File

@@ -21,7 +21,7 @@ public class ABLResult {
private String starttime; private String starttime;
private String endtime; private String endtime;
private ABLUrls url; private ABLUrls url;
private ABLRecordFile recordFileList; private List<ABLRecordFile> recordFileList;
public static ABLResult getFailForMediaServer() { public static ABLResult getFailForMediaServer() {
ABLResult zlmResult = new ABLResult(); ABLResult zlmResult = new ABLResult();

View File

@@ -1,13 +1,13 @@
package com.genersoft.iot.vmp.media.abl.bean.hook; package com.genersoft.iot.vmp.media.abl.bean.hook;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OnRecordMp4ABLHookParam extends ABLHookParam{ public class OnRecordMp4ABLHookParam extends ABLHookParam{
private String fileName; private String fileName;
private String startTime;
public String getFileName() { private String endTime;
return fileName; private long fileSize;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
} }

View File

@@ -41,6 +41,12 @@ public class MediaServer {
@Schema(description = "https-flv端口") @Schema(description = "https-flv端口")
private int flvSSLPort; private int flvSSLPort;
@Schema(description = "mp4端口")
private int mp4Port;
@Schema(description = "https-mp4端口")
private int mp4SSLPort;
@Schema(description = "ws-flv端口") @Schema(description = "ws-flv端口")
private int wsFlvPort; private int wsFlvPort;

View File

@@ -2,34 +2,63 @@ package com.genersoft.iot.vmp.media.bean;
import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam; import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.utils.DateUtil;
import lombok.Data; import lombok.Data;
@Data @Data
public class RecordInfo { public class RecordInfo {
private String app;
private String stream;
private String fileName; private String fileName;
private String filePath; private String filePath;
private long fileSize; private long fileSize;
private String folder; private String folder;
private String url; private String url;
/**
* 单位毫秒
*/
private long startTime; private long startTime;
/**
* 单位毫秒
*/
private double timeLen; private double timeLen;
private String params; private String params;
public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) { public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) {
RecordInfo recordInfo = new RecordInfo(); RecordInfo recordInfo = new RecordInfo();
recordInfo.setApp(hookParam.getApp());
recordInfo.setStream(hookParam.getStream());
recordInfo.setFileName(hookParam.getFile_name()); recordInfo.setFileName(hookParam.getFile_name());
recordInfo.setUrl(hookParam.getUrl()); recordInfo.setUrl(hookParam.getUrl());
recordInfo.setFolder(hookParam.getFolder()); recordInfo.setFolder(hookParam.getFolder());
recordInfo.setFilePath(hookParam.getFile_path()); recordInfo.setFilePath(hookParam.getFile_path());
recordInfo.setFileSize(hookParam.getFile_size()); recordInfo.setFileSize(hookParam.getFile_size());
recordInfo.setStartTime(hookParam.getStart_time()); recordInfo.setStartTime(hookParam.getStart_time() * 1000);
recordInfo.setTimeLen(hookParam.getTime_len()); recordInfo.setTimeLen(hookParam.getTime_len() * 1000);
return recordInfo; return recordInfo;
} }
public static RecordInfo getInstance(OnRecordMp4ABLHookParam hookParam) { public static RecordInfo getInstance(OnRecordMp4ABLHookParam hookParam) {
RecordInfo recordInfo = new RecordInfo(); RecordInfo recordInfo = new RecordInfo();
recordInfo.setApp(hookParam.getApp());
recordInfo.setStream(hookParam.getStream());
recordInfo.setFileName(hookParam.getFileName()); recordInfo.setFileName(hookParam.getFileName());
recordInfo.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime()));
recordInfo.setTimeLen(DateUtil.urlToTimestampMs(hookParam.getEndTime()) - recordInfo.getStartTime());
recordInfo.setFileSize(hookParam.getFileSize());
return recordInfo;
}
public static RecordInfo getInstance(CloudRecordItem cloudRecordItem) {
RecordInfo recordInfo = new RecordInfo();
recordInfo.setApp(cloudRecordItem.getApp());
recordInfo.setStream(cloudRecordItem.getStream());
recordInfo.setFileName(cloudRecordItem.getFileName());
recordInfo.setStartTime(cloudRecordItem.getStartTime());
recordInfo.setTimeLen(cloudRecordItem.getTimeLen());
recordInfo.setFileSize(cloudRecordItem.getFileSize());
recordInfo.setFilePath(cloudRecordItem.getFilePath());
return recordInfo; return recordInfo;
} }

View File

@@ -5,6 +5,9 @@ import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -74,9 +77,11 @@ public interface IMediaNodeServerService {
List<String> listRtpServer(MediaServer mediaServer); List<String> listRtpServer(MediaServer mediaServer);
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath); void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback<StreamInfo> callback);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema); void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema); void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo);
} }

View File

@@ -5,8 +5,8 @@ import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad; import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -164,9 +164,11 @@ public interface IMediaServerService {
List<String> listRtpServer(MediaServer mediaServer); List<String> listRtpServer(MediaServer mediaServer);
StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath); void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback<StreamInfo> callback);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema); void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema); void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo);
} }

View File

@@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent;
@@ -20,8 +21,7 @@ import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad; import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper; import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
@@ -1008,14 +1008,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
} }
@Override @Override
public StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { public void loadMP4File(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) { if (mediaNodeServerService == null) {
log.info("[loadMP4File] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType()); log.info("[loadMP4File] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
} }
mediaNodeServerService.loadMP4File(mediaServer, app, stream, datePath); mediaNodeServerService.loadMP4File(mediaServer, app, stream, date, dateDir, callback);
return getStreamInfoByAppAndStream(mediaServer, app, stream, null, null);
} }
@Override @Override
@@ -1037,4 +1037,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
} }
mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema); mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
} }
@Override
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[setRecordSpeed] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
return mediaNodeServerService.getDownloadFilePath(mediaServer, recordInfo);
}
} }

View File

@@ -10,8 +10,14 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -20,7 +26,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import java.util.*; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j @Slf4j
@Service("zlm") @Service("zlm")
@@ -36,6 +45,9 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
@Autowired @Autowired
private UserSetting userSetting; private UserSetting userSetting;
@Autowired
private HookSubscribe subscribe;
@Override @Override
public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) {
return zlmServerFactory.createRTPServer(mediaServer, "rtp", streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode); return zlmServerFactory.createRTPServer(mediaServer, "rtp", streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode);
@@ -552,8 +564,28 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
} }
@Override @Override
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { public void loadMP4File(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, app, stream, datePath); String buildApp = "mp4_record";
String buildStream = app + "_" + stream + "_" + date;
MediaInfo mediaInfo = getMediaInfo(mediaServer, buildApp, buildStream);
if (mediaInfo != null) {
if (callback != null) {
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null, true);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
return;
}
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId());
subscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, true);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
});
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
if (zlmResult == null) { if (zlmResult == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
} }
@@ -583,4 +615,30 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg());
} }
} }
@Override
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, RecordInfo recordInfo) {
// 将filePath作为独立参数传入避免%符号解析问题
String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s";
DownloadFileInfo info = new DownloadFileInfo();
// filePath作为第4个参数
info.setHttpPath(String.format(pathTemplate,
"http",
mediaServerItem.getStreamIp(),
mediaServerItem.getHttpPort(),
recordInfo.getFilePath()));
// 同样作为第4个参数
if (mediaServerItem.getHttpSSlPort() > 0) {
info.setHttpsPath(String.format(pathTemplate,
"https",
mediaServerItem.getStreamIp(),
mediaServerItem.getHttpSSlPort(),
recordInfo.getFilePath()));
}
return info;
}
} }

View File

@@ -4,8 +4,10 @@ import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference; import com.alibaba.fastjson2.TypeReference;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.*; import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
@@ -15,9 +17,11 @@ import org.springframework.stereotype.Component;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -327,6 +331,12 @@ public class ZLMRESTfulUtils {
public ZLMResult<StreamProxyResult> addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec, public ZLMResult<StreamProxyResult> addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec,
boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){ boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
try {
src_url = URLEncoder.encode(src_url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("src_url", src_url); param.put("src_url", src_url);
param.put("dst_url", dst_url); param.put("dst_url", dst_url);
@@ -573,6 +583,11 @@ public class ZLMRESTfulUtils {
} }
public ZLMResult<StreamProxyResult> addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) { public ZLMResult<StreamProxyResult> addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__"); param.put("vhost", "__defaultVhost__");
param.put("app", app); param.put("app", app);

View File

@@ -91,14 +91,14 @@ public class CloudRecordItem {
CloudRecordItem cloudRecordItem = new CloudRecordItem(); CloudRecordItem cloudRecordItem = new CloudRecordItem();
cloudRecordItem.setApp(param.getApp()); cloudRecordItem.setApp(param.getApp());
cloudRecordItem.setStream(param.getStream()); cloudRecordItem.setStream(param.getStream());
cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime()*1000); cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime());
cloudRecordItem.setFileName(param.getRecordInfo().getFileName()); cloudRecordItem.setFileName(param.getRecordInfo().getFileName());
cloudRecordItem.setFolder(param.getRecordInfo().getFolder()); cloudRecordItem.setFolder(param.getRecordInfo().getFolder());
cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize()); cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize());
cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath()); cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath());
cloudRecordItem.setMediaServerId(param.getMediaServer().getId()); cloudRecordItem.setMediaServerId(param.getMediaServer().getId());
cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen() * 1000); cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen());
cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()) * 1000); cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()));
Map<String, String> paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams()); Map<String, String> paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams());
if (paramsMap.get("callId") != null) { if (paramsMap.get("callId") != null) {
cloudRecordItem.setCallId(paramsMap.get("callId")); cloudRecordItem.setCallId(paramsMap.get("callId"));

View File

@@ -5,11 +5,8 @@ import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
@@ -21,7 +18,6 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
@@ -37,7 +33,10 @@ import org.springframework.util.Assert;
import java.io.File; import java.io.File;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.*; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Slf4j @Slf4j
@Service @Service
@@ -61,9 +60,6 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
@Autowired @Autowired
private IRedisRpcPlayService redisRpcPlayService; private IRedisRpcPlayService redisRpcPlayService;
@Autowired
private HookSubscribe subscribe;
@Override @Override
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime,
String endTime, List<MediaServer> mediaServerItems, String callId, Boolean ascOrder) { String endTime, List<MediaServer> mediaServerItems, String callId, Boolean ascOrder) {
@@ -255,9 +251,10 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
if (!userSetting.getServerId().equals(recordItem.getServerId())) { if (!userSetting.getServerId().equals(recordItem.getServerId())) {
return redisRpcPlayService.getRecordPlayUrl(recordItem.getServerId(), recordId); return redisRpcPlayService.getRecordPlayUrl(recordItem.getServerId(), recordId);
} }
String filePath = recordItem.getFilePath();
MediaServer mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId()); MediaServer mediaServer = mediaServerService.getOne(recordItem.getMediaServerId());
return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
return mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(recordItem));
} }
@Override @Override
@@ -297,26 +294,13 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
if (mediaServer == null) { if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
} }
String buildApp = "mp4_record"; String dateDir = null;
String buildStream = app + "_" + stream + "_" + date; String filePath = recordItemList.get(0).getFilePath();
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, buildApp, buildStream); if (filePath != null) {
if (mediaInfo != null) { dateDir = filePath.substring(0, filePath.lastIndexOf("/"));
if (callback != null) { }
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null); mediaServerService.loadMP4File(mediaServer, app, stream, date, dateDir, callback);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
return;
}
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServerId);
subscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
});
String dateDir = recordItemList.get(0).getFilePath().substring(0, recordItemList.get(0).getFilePath().lastIndexOf("/"));
mediaServerService.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
} }
@Override @Override

View File

@@ -1,46 +0,0 @@
package com.genersoft.iot.vmp.utils;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import lombok.experimental.UtilityClass;
/**
* 云录像工具类
*
* @author 648540858
*/
@UtilityClass
public class CloudRecordUtils {
/**
* 修复原始工具类中的格式化问题
*
* @param mediaServerItem 媒体服务器配置
* @param filePath 文件路径(可能包含%等特殊字符)
* @return 修复后的下载信息
*/
public static DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, String filePath) {
// 将filePath作为独立参数传入避免%符号解析问题
String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s";
DownloadFileInfo info = new DownloadFileInfo();
// filePath作为第4个参数
info.setHttpPath(String.format(pathTemplate,
"http",
mediaServerItem.getStreamIp(),
mediaServerItem.getHttpPort(),
filePath));
// 同样作为第4个参数
if (mediaServerItem.getHttpSSlPort() > 0) {
info.setHttpsPath(String.format(pathTemplate,
"https",
mediaServerItem.getStreamIp(),
mediaServerItem.getHttpSSlPort(),
filePath));
}
return info;
}
}

View File

@@ -181,6 +181,8 @@ create table IF NOT EXISTS wvp_media_server
rtsp_ssl_port integer, rtsp_ssl_port integer,
flv_port integer, flv_port integer,
flv_ssl_port integer, flv_ssl_port integer,
mp4_port integer,
mp4_ssl_port integer,
ws_flv_port integer, ws_flv_port integer,
ws_flv_ssl_port integer, ws_flv_ssl_port integer,
jtt_proxy_port integer, jtt_proxy_port integer,

View File

@@ -182,6 +182,8 @@ create table IF NOT EXISTS wvp_media_server
rtsp_ssl_port integer, rtsp_ssl_port integer,
flv_port integer, flv_port integer,
flv_ssl_port integer, flv_ssl_port integer,
mp4_port integer,
mp4_ssl_port integer,
ws_flv_port integer, ws_flv_port integer,
ws_flv_ssl_port integer, ws_flv_ssl_port integer,
jtt_proxy_port integer, jtt_proxy_port integer,

View File

@@ -51,4 +51,28 @@ call wvp_20250708();
DROP PROCEDURE wvp_20250708; DROP PROCEDURE wvp_20250708;
DELIMITER ; DELIMITER ;
/*
* 20250917
*/
DELIMITER // -- 重定义分隔符避免分号冲突
CREATE PROCEDURE `wvp_20250917`()
BEGIN
IF NOT EXISTS (SELECT column_name FROM information_schema.columns
WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'mp4_port')
THEN
ALTER TABLE wvp_media_server ADD mp4_port integer;
END IF;
IF NOT EXISTS (SELECT column_name FROM information_schema.columns
WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'mp4_ssl_port')
THEN
ALTER TABLE wvp_media_server ADD mp4_ssl_port integer;
END IF;
END; //
call wvp_20250917();
DROP PROCEDURE wvp_20250917;
DELIMITER ;

View File

@@ -35,3 +35,5 @@ create table IF NOT EXISTS wvp_jt_channel (
); );
ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS jtt_proxy_port integer; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS jtt_proxy_port integer;
ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS mp4_port integer;
ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS mp4_ssl_port integer;