增加录像seek和倍速播放接口

This commit is contained in:
lin
2025-04-12 21:24:02 +08:00
parent 9d0c8f91b9
commit eea367d6d7
12 changed files with 165 additions and 35 deletions

View File

@@ -28,6 +28,7 @@ import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.IReceiveRtpServerService;
import com.genersoft.iot.vmp.service.ISendRtpServerService;
import com.genersoft.iot.vmp.service.bean.*;

View File

@@ -70,4 +70,5 @@ public interface IMediaNodeServerService {
List<String> listRtpServer(MediaServer mediaServer);
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
}

View File

@@ -159,4 +159,6 @@ public interface IMediaServerService {
int createRTPServer(MediaServer mediaServerItem, String streamId, long ssrc, Integer port, boolean onlyAuto, boolean disableAudio, boolean reUsePort, Integer tcpMode);
List<String> listRtpServer(MediaServer mediaServer);
StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
}

View File

@@ -966,4 +966,15 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
mediaNodeServerService.stopProxy(mediaServer, streamKey);
}
@Override
public StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[loadMP4File] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.loadMP4File(mediaServer, app, stream, datePath);
return getStreamInfoByAppAndStream(mediaServer, app, stream, null, null);
}
}

View File

@@ -549,4 +549,15 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
return result;
}
@Override
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
JSONObject jsonObject = zlmresTfulUtils.loadMP4File(mediaServer, app, stream, datePath);
if (jsonObject == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}
if (jsonObject.getInteger("code") != 0) {
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg"));
}
}
}

View File

@@ -25,6 +25,7 @@ public class ZLMRESTfulUtils {
private OkHttpClient client;
public interface RequestCallback{
void run(JSONObject response);
}
@@ -415,4 +416,34 @@ public class ZLMRESTfulUtils {
param.put("name", fileName);
return sendPost(mediaServerItem, "deleteRecordDirectory",param, null);
}
public JSONObject loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
Map<String, Object> param = new HashMap<>(1);
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
param.put("file_path", datePath);
param.put("file_repeat", "0");
param.put("auto_close", "1");
return sendPost(mediaServer, "loadMP4File",param, null);
}
public JSONObject setRecordSpeed(MediaServer mediaServer, String app, String stream, int speed, String schema) {
Map<String, Object> param = new HashMap<>(1);
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
param.put("speed", speed);
param.put("schema", schema);
return sendPost(mediaServer, "setRecordSpeed",param, null);
}
public JSONObject seekRecordStamp(MediaServer mediaServer, String app, String stream, long stamp) {
Map<String, Object> param = new HashMap<>(1);
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
param.put("stamp", stamp);
return sendPost(mediaServer, "seekRecordStamp",param, null);
}
}

View File

@@ -1,10 +1,11 @@
package com.genersoft.iot.vmp.gb28181.service;
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.github.pagehelper.PageInfo;
import java.util.List;
@@ -57,5 +58,5 @@ public interface ICloudRecordService {
/**
* 加载录像文件形成录像流
*/
StreamContent loadRecord(String app, String stream, String date);
void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback);
}

View File

@@ -2,23 +2,28 @@ package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
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.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
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.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
@@ -58,6 +63,9 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
@Autowired
private IRedisRpcPlayService redisRpcPlayService;
@Autowired
private HookSubscribe subscribe;
@Override
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) {
@@ -278,18 +286,40 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
@Override
public StreamContent loadRecord(String app, String stream, String date) {
public void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback) {
long startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(date + " 00:00:00");
long endTimestamp = startTimestamp + 24 * 60 * 60 * 1000;
List<String> mediaServerIds = cloudRecordServiceMapper.queryMediaServerInRecord(app, stream, startTimestamp, endTimestamp);
List<String> mediaServerIds = cloudRecordServiceMapper.queryMediaServerId(app, stream, startTimestamp, endTimestamp);
if (mediaServerIds.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "此时间无录像");
}
if (mediaServerIds.size() > 1) {
log.info("[云端录像] loadMP4File时发现录像文件分布在不通的zlm上默认使用第一个zlm开启录像");
log.info("[云端录像] loadMP4File时发现录像文件分布在不通的zlm上默认使用第一个zlm开启录像");
}
mediaServerService.loadMP4File()
return null;
String mediaServerId = mediaServerIds.get(0);
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
String buildStream = stream + "/" + date;
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, app, buildStream);
if (mediaInfo != null) {
if (callback != null) {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, app, buildStream, mediaInfo, null);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
return;
}
Hook hook = Hook.getInstance(HookType.on_media_arrival, app, buildStream, mediaServerId);
subscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, app, buildStream, hookData.getMediaInfo(), null);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
});
mediaServerService.loadMP4File(mediaServer, app, buildStream, String.format("%s/%s/%s/%s", mediaServer.getRecordPath(), app, stream, date));
}
}

View File

@@ -6,7 +6,7 @@ import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController;
import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping;

View File

@@ -127,14 +127,17 @@ public interface CloudRecordServiceMapper {
CloudRecordItem queryOne(@Param("id") Integer id);
@Select(" <script>" +
"select count(1)" +
"select media_server_id " +
" from wvp_cloud_record " +
" where 0 = 0" +
" <if test= 'app != null '> and app=#{app}</if>" +
" <if test= 'stream != null '> and stream=#{stream}</if>" +
" <if test= 'startTimeStamp != null '> and end_time &gt;= #{startTimeStamp}</if>" +
" <if test= 'endTimeStamp != null '> and start_time &lt;= #{endTimeStamp}</if>" +
" group by media_server_id" +
" </script>")
int queryCount(@Param("app") String app, @Param("stream") String stream,
@Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp,);
List<String> queryMediaServerId(@Param("app") String app,
@Param("stream") String stream,
@Param("startTimeStamp")Long startTimeStamp,
@Param("endTimeStamp")Long endTimeStamp);
}

View File

@@ -1,16 +1,21 @@
package com.genersoft.iot.vmp.vmanager.cloudRecord;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
@@ -21,12 +26,15 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@@ -47,6 +55,9 @@ public class CloudRecordController {
@Autowired
private IMediaServerService mediaServerService;
@Autowired
private UserSetting userSetting;
@ResponseBody
@GetMapping("/date/list")
@@ -242,12 +253,58 @@ public class CloudRecordController {
@Parameter(name = "app", description = "应用名", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@Parameter(name = "date", description = "日期, 例如 2025-04-10", required = true)
public StreamContent loadRecord(
public DeferredResult<WVPResult<StreamContent>> loadRecord(
HttpServletRequest request,
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = true) String date
) {
return cloudRecordService.loadRecord(app, stream, date);
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>();
result.onTimeout(()->{
log.info("[加载录像文件超时] app={}, stream={}, date={}", app, stream, date);
WVPResult<StreamContent> wvpResult = new WVPResult<>();
wvpResult.setCode(ErrorCode.ERROR100.getCode());
wvpResult.setMsg("加载录像文件超时");
result.setResult(wvpResult);
});
ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> {
WVPResult<StreamContent> wvpResult = new WVPResult<>();
if (code == InviteErrorCode.SUCCESS.getCode()) {
wvpResult.setCode(ErrorCode.SUCCESS.getCode());
wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
if (streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
if (!org.springframework.util.ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) {
streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfo));
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
result.setResult(wvpResult);
};
cloudRecordService.loadRecord(app, stream, date, callback);
return result;
}
/************************* 以下这些接口只适合wvp和zlm部署在同一台服务器的情况且wvp只有一个zlm节点的情况 ***************************************/

View File

@@ -1,21 +1,10 @@
package com.genersoft.iot.vmp.vmanager.log;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.core.rolling.RollingFileAppender;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.ILogService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.LogFileInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -23,24 +12,17 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@SuppressWarnings("rawtypes")
@Tag(name = "日志文件查询接口")