Sfoglia il codice sorgente

视频回放 视频缓存

ZhangWenTao 2 anni fa
parent
commit
ca7bc4d754

+ 24 - 1
src/main/java/com/persagy/cameractl/common/VideoExportProcessContext.java

@@ -1,5 +1,6 @@
 package com.persagy.cameractl.common;
 
+import com.persagy.cameractl.utils.Camera;
 import com.sun.jna.Pointer;
 import lombok.Getter;
 import lombok.SneakyThrows;
@@ -32,6 +33,9 @@ public class VideoExportProcessContext {
     @Getter
     private final String tempFilePath;
 
+    @Getter
+    private final Camera camera;
+
     private final CountDownLatch latch = new CountDownLatch(1);
 
     private final RandomAccessFile randomFile;
@@ -43,10 +47,11 @@ public class VideoExportProcessContext {
     private volatile boolean inTransmission;
 
     @SneakyThrows
-    public VideoExportProcessContext(Pointer pUser, String filePath, String tempFilePath) {
+    public VideoExportProcessContext(Pointer pUser, String filePath, String tempFilePath, Camera camera) {
         this.pUser = pUser.getString(0);
         this.filePath = filePath;
         this.tempFilePath = tempFilePath;
+        this.camera = camera;
         this.randomFile = new RandomAccessFile(tempFilePath, "rw");
     }
 
@@ -89,6 +94,24 @@ public class VideoExportProcessContext {
         endTransmission();
     }
 
+    public synchronized boolean videoExists() throws InterruptedException {
+        try {
+            semaphore.acquire();
+            File file = new File(filePath);
+
+
+            if (file.exists() && file.isFile()) {
+                file.setLastModified(System.currentTimeMillis());
+
+                return true;
+            }
+
+            return false;
+        } finally {
+            semaphore.release();
+        }
+    }
+
     public synchronized void startTransmission() throws InterruptedException {
         if (inTransmission) {
             return;

+ 12 - 15
src/main/java/com/persagy/cameractl/controller/HelloController.java

@@ -3,6 +3,7 @@ package com.persagy.cameractl.controller;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.OutputStream;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -77,36 +78,32 @@ public class HelloController {
 	/**
 	 * 请求视频文件接口
 	 */
-	@RequestMapping(value = "/vplayf/{token}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
-	public void vplay(@PathVariable("token") String token, HttpServletResponse response,
+	@RequestMapping(value = "/vplayf/{file}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
+	public void vplay(@PathVariable("file") String file, HttpServletResponse response,
 			HttpServletRequest request) {
-		String filePath = OtherTools.getVideoFilePathByT(token);
+		String filePath = OtherTools.getLocalVideoFilePath(file);
 		if (filePath.equals(""))
 			return;
 		try {
-			File mp4File = new File(filePath);
-			if (!mp4File.exists())
+			File videoFile = new File(filePath);
+			if (!videoFile.exists())
 				return;
-			FileInputStream inputStream = new FileInputStream(filePath);
-			byte[] data = new byte[inputStream.available()];
-			inputStream.read(data);
-			String diskfilename = "_.mp4";
-			response.setContentType("video/mp4");
-			response.setHeader("Content-Disposition", "attachment; filename=\"" + diskfilename + "\"");
-			response.setContentLength(data.length);
+
+			response.setContentType(String.format("video/%s", OtherTools.getFileSuffix(file)));
+			response.setHeader("Content-Disposition", "attachment; filename=\"" + file + "\"");
+			response.setContentLength((int) videoFile.length());
 			// 不支持分段请求
 			response.setHeader("Accept-Ranges", "none");
 			response.setHeader("Etag", "W/\"" + StringTools.getUUID() + "\"");
 			OutputStream os = response.getOutputStream();
+			Files.copy(videoFile.toPath(), os);
 
-			os.write(data);
 			// 先声明的流后关掉!
 			os.flush();
 			os.close();
-			inputStream.close();
 
 		} catch (Exception e) {
-			log.error("播放" + token + "异常:", e);
+			log.error("播放" + file + "异常:", e);
 		}
 	}
 	

+ 26 - 12
src/main/java/com/persagy/cameractl/service/windows/ZhaosMainWindows.java

@@ -1,5 +1,6 @@
 package com.persagy.cameractl.service.windows;
 
+import cn.hutool.crypto.digest.MD5;
 import com.persagy.cameractl.common.VideoExportProcessContext;
 import com.persagy.cameractl.conf.AllStaticConfig;
 import com.persagy.cameractl.utils.*;
@@ -307,11 +308,17 @@ public class ZhaosMainWindows {
 	};
 
 	/**
-	 * 回放入口 synchronized关键字即把方法改为同步,两个线程同时调用该方法时,上一个线程执行完后下一个线程才会执行
+	 * 回放入口
 	 */
 	public ResultClass playBackMain() {
 		ResultClass returnResult = new ResultClass();
-		String playBackFilePath = OtherTools.getVideoFilePath();
+
+		String token = OtherTools.generateFileToken(_camera);
+
+		String playBackFilePath = OtherTools.getVideoFilePathByT(
+				token,
+				_camera.transcoding ? "mp4" : "h264");
+
 		if (StringUtils.isEmpty(playBackFilePath)) {
 			returnResult.name = false;
 			returnResult.reason = "回放文件名称生成失败";
@@ -322,7 +329,6 @@ public class ZhaosMainWindows {
 		String playBackFileName = mp4File.getName();
 
 		String tempPlayBackFilePath = playBackFilePath + ".tmp";
-		String token = OtherTools.getMp4NamePrefix(playBackFileName);
 
 		String errPrefixStr = "回放失败";
 		// 初始化
@@ -336,12 +342,17 @@ public class ZhaosMainWindows {
 		WinDef.LPVOID pUser = new WinDef.LPVOID(pUserPointer);
 
 		VideoExportProcessContext context = new VideoExportProcessContext(
-				pUserPointer, playBackFilePath, tempPlayBackFilePath);
-
-		// 保证在当前流程中不被GC回收掉
-		EndPlayCallBackClass endPlayCallBackClass = new EndPlayCallBackClass(context);
+				pUserPointer, playBackFilePath, tempPlayBackFilePath, _camera);
 
 		try {
+			// 视频已经存在就直接返回
+			if (context.videoExists()) {
+				return playBackSuccess(playBackFileName);
+			}
+
+			// 保证在当前流程中不被GC回收掉
+			EndPlayCallBackClass endPlayCallBackClass = new EndPlayCallBackClass(context);
+
 			// 回放开始
 			String playBackResult = this.startPlayBack(pUser, endPlayCallBackClass, context);
 			if (!"true".equals(playBackResult))
@@ -353,10 +364,7 @@ public class ZhaosMainWindows {
 					return this.executeErr(true, "无可回放内容", errPrefixStr);
 				}
 
-				String url = OtherTools.playMp4RootUrl + token;
-				Map<String, String> dataMap = new HashMap<>();
-				dataMap.put("url", url);
-				return this.executeSuccess(dataMap);
+				return playBackSuccess(playBackFileName);
 			}
 
 			log.info("errPrefixStr:" + errPrefixStr);
@@ -368,7 +376,13 @@ public class ZhaosMainWindows {
 			context.clean();
 		}
 
-	};
+	}
+
+	private ResultClass playBackSuccess(String playBackFileName){
+		Map<String, String> dataMap = new HashMap<>();
+		dataMap.put("file", playBackFileName);
+		return this.executeSuccess(dataMap);
+	}
 
 	/** 查询日志入口 */
 	public ResultClass searchLogMain() {

+ 3 - 0
src/main/java/com/persagy/cameractl/utils/Camera.java

@@ -52,6 +52,9 @@ public class Camera {
 	// 结束时间,用于回放。格式:2021-03-01 15:02:11
 	public String endDateStr;
 
+	// 视频是否转码
+	public boolean transcoding;
+
 	// 通道ID
 	public String cameraIndexCode;
 	

+ 33 - 23
src/main/java/com/persagy/cameractl/utils/OtherTools.java

@@ -1,6 +1,7 @@
 package com.persagy.cameractl.utils;
 
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.MD5;
 import com.persagy.cameractl.utils.EnumTools.OperatingSystem;
 import lombok.extern.slf4j.Slf4j;
 
@@ -57,21 +58,40 @@ public class OtherTools {
 		return (StrUtil.isBlank(prefix) ? "" : prefix) + (StrUtil.isBlank(errCode) ? "" : ",厂家返回错误码:" + errCode);
 	}
 
-	public static String getVideoFilePath() {
-		String token = StringTools.getUUID();
-		return OtherTools.getVideoFilePathByT(token);
-	};
+	public static String generateFileToken(Camera camera) {
+		final String sep = ":";
+		return MD5.create().digestHex(
+				camera.cameraIp +
+						sep +
+						camera.cameraPort +
+						sep +
+						camera.channel +
+						sep +
+						camera.startDateStr +
+						sep +
+						camera.endDateStr +
+						sep +
+						camera.streamType +
+						sep +
+						camera.transcoding
+		);
+	}
 
-	public static String getVideoFilePathByT(String token) {
+	public static String getVideoFilePathByT(String token, String type) {
 		try {
-			String playFileName = OtherTools.getMp4FullName(token);
+			String playFileName = OtherTools.getFullName(token, type);
 			String playFileDir = OtherTools.getVideoFileDir();
 			return playFileDir + fileSperator + playFileName;
 		} catch (Exception e) {
 			log.error(e.getMessage());
 			return "";
 		}
-	};
+	}
+
+	public static String getLocalVideoFilePath(String file) {
+		String playFileDir = OtherTools.getVideoFileDir();
+		return playFileDir + fileSperator + file;
+	}
 
 	public static String getVideoFileDir() {
 		String dllPath = "./tempVideo";
@@ -90,23 +110,13 @@ public class OtherTools {
 		}
 	}
 
-	public static String getVideoFilePath(Camera _camera) {
-		try {
-			String startTimeStr = _camera.startDateStr.replaceAll("[-:\\s]", "");
-			String endTimeStr = _camera.endDateStr.replaceAll("[-:\\s]", "");
-			String ipStr = _camera.cameraIp.replace(".", "_");
-			String playFileName = OtherTools.getMp4FullName(startTimeStr + "_" + endTimeStr + "_" + _camera.channel);
-			String playFileDir = getVideoFileDir() + fileSperator + ipStr;
-			return playFileDir + fileSperator + playFileName;
-		} catch (Exception e) {
-			log.error(e.getMessage());
-			return "";
-		}
-	};
+	public static String getFullName(String prefix, String type) {
+		return prefix + "." + type;
+	}
 
-	public static String getMp4FullName(String prefix) {
-		return prefix + ".mp4";
-	};
+	public static String getFileSuffix(String file) {
+		return file.substring(file.lastIndexOf(".") + 1);
+	}
 
 	public static String getMp4NamePrefix(String fullName) {
 		return fullName.substring(0, fullName.lastIndexOf("."));

+ 3 - 2
src/main/java/com/persagy/cameractl/utils/TimerInterval.java

@@ -29,8 +29,9 @@ class ClearTileTimer extends java.util.TimerTask {
 		if (!dir.exists() || !dir.isDirectory()) {// 判断是否存在目录
 			return;
 		}
-		// 十五分钟
-		long timeP = TimeUnit.MINUTES.toMillis(15);
+
+		// 删除时间阈值
+		long timeP = TimeUnit.MINUTES.toMillis(90);
 		long currTime = System.currentTimeMillis();
 		String[] files = dir.list();			// 读取目录下的所有目录文件信息
 		for (int i = 0; i < Objects.requireNonNull(files).length; i++) {// 循环,添加文件名或回调自身

+ 13 - 1
src/main/java/com/persagy/nvr/EndPlayCallBackClass.java

@@ -2,12 +2,15 @@ package com.persagy.nvr;
 
 import com.persagy.cameractl.common.VideoExportProcessContext;
 import com.persagy.cameractl.utils.OtherTools;
-import com.sun.jna.Pointer;
 import com.sun.jna.platform.win32.WinDef;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
 @Slf4j
 @AllArgsConstructor
 public class EndPlayCallBackClass implements VskClient.PlayBackEndCallBack {
@@ -27,6 +30,15 @@ public class EndPlayCallBackClass implements VskClient.PlayBackEndCallBack {
             String tempFilePath = context.getTempFilePath();
             String filePath = context.getFilePath();
 
+            // 不进行转码操作
+            if (!context.getCamera().transcoding) {
+                Files.copy(
+                        Paths.get(context.getTempFilePath()),
+                        Paths.get(context.getFilePath()),
+                        StandardCopyOption.REPLACE_EXISTING);
+                return;
+            }
+
             // 把源MP4转为页面上可播放的MP4
             Runtime run = Runtime.getRuntime();
             Process p = run.exec("ffmpeg -i \"" + tempFilePath + "\" -c copy -y \"" + filePath + "\"");