修复云端录像seek问题

This commit is contained in:
lin
2025-04-16 18:07:04 +08:00
parent 1a9307003a
commit 3b4a6018cb
14 changed files with 67 additions and 103 deletions

View File

@@ -178,7 +178,7 @@ public class UserSetting {
/**
* 登录超时时间(分钟)
*/
private long loginTimeout = 30;
private long loginTimeout = 60;
/**
* jwk文件路径若不指定则使用resources目录下的jwk.json

View File

@@ -1,7 +1,9 @@
package com.genersoft.iot.vmp.media.bean;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import lombok.Data;
@Data
public class RecordInfo {
private String fileName;
private String filePath;
@@ -24,70 +26,6 @@ public class RecordInfo {
return recordInfo;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public double getTimeLen() {
return timeLen;
}
public void setTimeLen(double timeLen) {
this.timeLen = timeLen;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
@Override
public String toString() {
return "RecordInfo{" +

View File

@@ -72,7 +72,7 @@ public interface IMediaNodeServerService {
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
}

View File

@@ -162,7 +162,7 @@ public interface IMediaServerService {
StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
}

View File

@@ -983,22 +983,22 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
@Override
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp) {
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[seekRecordStamp] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.seekRecordStamp(mediaServer, app, stream, stamp);
mediaNodeServerService.seekRecordStamp(mediaServer, app, stream, stamp, schema);
}
@Override
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed) {
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[setRecordSpeed] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed);
mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
}
}

View File

@@ -126,7 +126,6 @@ public class ZLMHttpHookListener {
@ResponseBody
@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId());
if (mediaServer == null) {
return HookResult.SUCCESS();

View File

@@ -562,8 +562,8 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
@Override
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp) {
JSONObject jsonObject = zlmresTfulUtils.seekRecordStamp(mediaServer, app, stream, stamp, "ts");
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) {
JSONObject jsonObject = zlmresTfulUtils.seekRecordStamp(mediaServer, app, stream, stamp, schema);
if (jsonObject == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}
@@ -573,8 +573,8 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
@Override
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed) {
JSONObject jsonObject = zlmresTfulUtils.setRecordSpeed(mediaServer, app, stream, speed, "ts");
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
JSONObject jsonObject = zlmresTfulUtils.setRecordSpeed(mediaServer, app, stream, speed, schema);
if (jsonObject == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}

View File

@@ -424,7 +424,6 @@ public class ZLMRESTfulUtils {
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);
}

View File

@@ -61,9 +61,9 @@ public interface ICloudRecordService {
*/
void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback);
void seekRecord(String mediaServerId,String app, String stream, Double seek);
void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema);
void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed);
void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema);
void deleteFileByIds(Set<Integer> ids);
}

View File

@@ -297,43 +297,44 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
String buildStream = stream + "_" + date;
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, app, buildStream);
String buildApp = "mp4_record";
String buildStream = app + "_" + stream + "_" + date;
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, buildApp, buildStream);
if (mediaInfo != null) {
if (callback != null) {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, app, buildStream, mediaInfo, null);
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
return;
}
Hook hook = Hook.getInstance(HookType.on_media_arrival, app, buildStream, mediaServerId);
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServerId);
subscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, app, buildStream, hookData.getMediaInfo(), null);
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, app, buildStream, dateDir);
mediaServerService.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
}
@Override
public void seekRecord(String mediaServerId,String app, String stream, Double seek) {
public void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema) {
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
mediaServerService.seekRecordStamp(mediaServer, app, stream, seek);
mediaServerService.seekRecordStamp(mediaServer, app, stream, seek, schema);
}
@Override
public void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed) {
public void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema) {
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
mediaServerService.setRecordSpeed(mediaServer, app, stream, speed);
mediaServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
}
@Override
@@ -352,7 +353,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
boolean deleteResult = mediaServerService.deleteRecordDirectory(mediaServer, cloudRecordItem.getApp(),
cloudRecordItem.getStream(), date, cloudRecordItem.getFileName());
if (deleteResult) {
log.warn("[录像文件定时清理] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath());
log.warn("[录像文件] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath());
cloudRecordItemIdListForDelete.add(cloudRecordItem);
}
}catch (ControllerException e) {

View File

@@ -318,10 +318,13 @@ public class CloudRecordController {
@RequestParam(required = true) String mediaServerId,
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = true) Double seek
@RequestParam(required = true) Double seek,
@RequestParam(required = false) String schema
) {
cloudRecordService.seekRecord(mediaServerId, app, stream, seek);
if (schema == null) {
schema = "ts";
}
cloudRecordService.seekRecord(mediaServerId, app, stream, seek, schema);
}
@ResponseBody
@@ -334,10 +337,14 @@ public class CloudRecordController {
@RequestParam(required = true) String mediaServerId,
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = true) Integer speed
) {
@RequestParam(required = true) Integer speed,
@RequestParam(required = false) String schema
) {
if (schema == null) {
schema = "ts";
}
cloudRecordService.setRecordSpeed(mediaServerId, app, stream, speed);
cloudRecordService.setRecordSpeed(mediaServerId, app, stream, speed, schema);
}
@ResponseBody
@@ -410,7 +417,7 @@ public class CloudRecordController {
try {
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
zos.putNextEntry(new ZipEntry(DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(cloudRecordItem.getStartTime()) + ".mp4"));
zos.putNextEntry(new ZipEntry(DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime()) + ".mp4"));
File file = new File(cloudRecordItem.getFilePath());
if (!file.exists() || file.isDirectory()) {
continue;
@@ -519,7 +526,7 @@ public class CloudRecordController {
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
CloudRecordUrl cloudRecordUrl = new CloudRecordUrl();
cloudRecordUrl.setId(cloudRecordItem.getId());
cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(cloudRecordItem.getStartTime()));
cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime()));
cloudRecordUrl.setPlayUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath());
cloudRecordUrlList.add(cloudRecordUrl);
}

View File

@@ -63,9 +63,9 @@ public class UserApiKeyController {
Long expirationTime = null;
if (expiresAt != null) {
long timestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(expiresAt);
expirationTime = (timestamp - System.currentTimeMillis()) / (60 * 1000);
if (expirationTime < 0) {
expirationTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(expiresAt);
long difference = (expirationTime - System.currentTimeMillis()) / (60 * 1000);
if (difference < 0) {
throw new ControllerException(ErrorCode.ERROR400.getCode(), "过期时间不能早于当前时间");
}
}

View File

@@ -5,6 +5,10 @@
spring:
cache:
type: redis
thymeleaf:
cache: false
# 设置接口超时时间
mvc:
async:

16
zlm.md Normal file
View File

@@ -0,0 +1,16 @@
1. 增加接口用来返回hook调用耗时。
参数: sn
返回值: hook耗时
功能: zlm收到调用后立即调用心跳hookhook中携带sn信息 zlm收到hook返回后计算此次hook的耗时然后返回耗时
用途: 1. 可以用来检测hook配置是否正常
2. 检测wvp与zlm连接的健康程度
2. 增加接口查询loadMP4File的录像播放位置
参数: app steam
返回: 当前播放的hook位置
功能: zlm收到请求后查询流的seek位置并返回
用途: 1. 获取当前播放位置,可以在页面同步显示时间
2. 用来时快进五秒 快退五秒类似的操作
3. loadMP4File扩展 增加默认seek值和默认倍速开始时直接从这个位置开始并使用指定的倍速
4. 支持下载指定取件的mp4文件这个区间可能包括多个文件和文件的一部分。
5. 希望seek接口和倍速接口可以去除schema的必选作为可选项不传则默认全部
6. 可选: seek值如果可以换算为日期会有更好的体验 比如2025-4-15 12:32:11