Ver Fonte

完善监控功能

11868 há 2 dias atrás
pai
commit
af3b7f5169
15 ficheiros alterados com 1291 adições e 0 exclusões
  1. 281 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/controller/VideoController.java
  2. 40 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/domain/Video.java
  3. 52 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/mapper/VideoMapper.java
  4. 58 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/mapper/VideoMapper.xml
  5. 46 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/CommandUtil.java
  6. 50 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/ContextUtil.java
  7. 85 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/ErrorCodeEnum.java
  8. 89 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/FFmpegManager.java
  9. 31 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/MyRequestBody.java
  10. 16 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/NoAuthInterface.java
  11. 233 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/other/ResponseResult.java
  12. 59 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/service/VideoService.java
  13. 200 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/service/VideoServiceImpl.java
  14. 33 0
      supervision-admin/src/main/java/com/supervision/web/videoManage/vo/VideoVo.java
  15. 18 0
      supervision-admin/src/main/resources/application.yml

+ 281 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/controller/VideoController.java

@@ -0,0 +1,281 @@
+package com.supervision.web.videoManage.controller;
+
+
+import com.github.pagehelper.PageInfo;
+import com.supervision.web.videoManage.domain.Video;
+import com.supervision.web.videoManage.other.*;
+import com.supervision.web.videoManage.service.VideoService;
+import com.supervision.web.videoManage.vo.VideoVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+@Api(tags = "v2")
+@RestController
+//@RequestMapping("rest/v2/video/videos")
+@RequestMapping("/video")
+@AllArgsConstructor
+@Slf4j
+public class VideoController {
+
+    private VideoService videoService;
+
+//    @Autowired
+//    private TaskServiceV2 taskServiceV2;
+
+    /** Windows系统* */
+    private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("win");
+    /** Linux系统* */
+    private static final boolean IS_LINUX = System.getProperty("os.name").toLowerCase().contains("Linux");
+
+
+    /** 视频播放指令执行 rtsp地址【源播放地址】通过ffmpeg程序进行转化成m3u8,将地址传给video.js进行播放 */
+//    @PostMapping("/videoStart")
+//    public ResponseResult<VideoVo> videoPreview(@MyRequestBody String videoPath) {
+//        CommandUtil commandUtil = new CommandUtil();
+////        String begin = "cmd /k start ffmpeg -f dshow -i video='Integrated Webcam' -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -rtsp_transport tcp -f rtsp rtsp://127.0.0.1/test";
+////
+////        commandUtil.winExec(begin);
+//        String url = "http://localhost:8080/test.m3u8";
+//        VideoVo videoVo = new VideoVo(url);
+//        /*如果是winodws系统**/
+//        //将rtsp流转码为m3u8文件交与nginx代理
+//        if (IS_WINDOWS) {
+//            System.out.println("开始执行"+videoPath);
+//
+//
+//            String cmd ="cmd /k start ffmpeg -rtsp_transport tcp -i"
+//                    + " "
+//                    + videoPath
+//                    + " "
+//                    + "-c:v libx264 -c:a aac -f hls -hls_time 5 -hls_list_size 10 F://nginx-1.8.1/nginx-rtmp-win32-dev/html/test.m3u8";
+//            commandUtil.winExec(cmd);
+//        }
+
+
+        /*如果是Linux系统**/
+//        if (IS_LINUX){
+//            System.out.println("linux");
+//            String cmd = "ffmpeg -f rtsp -rtsp_transport tcp -i '"
+//                    + ""
+//                    + videoPath
+//                    + "'"
+//                    + ""
+//                    + " -codec copy -f flv -an 'rtmp://xxx.xxx.xxx.xxx:1935/myapp/room'";
+//            commandUtil.linuxExec(cmd);
+//        }
+//        return ResponseResult.success(videoVo);
+//    }
+    /** 关闭ffmpeg.exe程序 */
+    @GetMapping("/videoClose")
+    public ResponseResult<Void> close() {
+        closeHistoryProgram("ffmpeg.exe");
+        if (videoService.deleteTsFile()) {
+            return ResponseResult.success();
+        } else {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+    }
+
+    /**
+     * 获取某个课目下的所有视频地址。
+     *
+     * @return 查询结果。
+     */
+    @ApiOperation(value = "获取视频地址")
+    @NoAuthInterface
+    @PostMapping("/getUnitUrl")
+    public List<VideoVo> getUnitUrl(@MyRequestBody String subjectName) {
+       List<VideoVo> videoVoList = videoService.getSubjectVideo(subjectName);
+       return videoVoList;
+    }
+
+    /**
+     * 获取某个类别下的所有视频地址。
+     *
+     * @return 查询结果。
+     */
+//    @PostMapping("/getSubjectUrls")
+//    public ResponseResult<List<VideoVo>> getSubjectUrls(@RequestBody VideoVo requestBody) {
+//        return ResponseResult.success(videoService.listVideos(VideoVo));
+//    }
+
+//    /**
+//     * 查询所有视频
+//     * @return 视频列表
+//     */
+//    @ApiOperation("查询所有视频")
+//    @GetMapping("/list")
+//    public List<Video> listAll() {
+//        return videoService.listAll();
+//    }
+
+    @ApiOperation("分页查询视频列表(支持视频名称模糊查询)")
+    @PostMapping("/list")
+    public Map<String, Object> listAllPaged(@RequestBody Map<String, Object> params) {
+        int page = (int) params.getOrDefault("page", 1);
+        int size = (int) params.getOrDefault("size", 10);
+        String name = (String) params.getOrDefault("name", ""); // 视频名称模糊查询
+
+        List<Video> list = videoService.listAllPaged(page, size, name);
+
+        PageInfo<Video> pageInfo = new PageInfo<>(list);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("total", pageInfo.getTotal());
+        result.put("list", list);
+        return result;
+    }
+
+    @ApiOperation("分页查询视频列表(支持视频名称模糊查询)")
+    @PostMapping("/listVideo")
+    public Map<String, Object> listVideoAllPaged(@RequestBody Map<String, Object> params) {
+        int page = (int) params.getOrDefault("page", 1);
+        int size = (int) params.getOrDefault("size", 10);
+        String name = (String) params.getOrDefault("name", ""); // 视频名称模糊查询
+
+        List<Video> list = videoService.listVideoAllPaged(page, size, name);
+
+        PageInfo<Video> pageInfo = new PageInfo<>(list);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("total", pageInfo.getTotal());
+        result.put("list", list);
+        return result;
+    }
+
+    /**
+     * 根据ID查询单个视频
+     * @param id 视频ID
+     * @return 视频对象
+     */
+    @ApiOperation("根据ID查询视频")
+    @GetMapping("/{id}")
+    public Video getById(@PathVariable Long id) {
+        return videoService.getById(id);
+    }
+
+    /**
+     * 新增视频
+     * @param video 视频对象
+     * @return 操作结果
+     */
+    @ApiOperation("新增视频")
+    @PostMapping
+    public String add(@RequestBody Video video) {
+        videoService.add(video);
+        return "新增成功";
+    }
+
+    /**
+     * 更新视频
+     * @param video 视频对象
+     * @return 操作结果
+     */
+    @ApiOperation("更新视频")
+    @PutMapping
+    public String update(@RequestBody Video video) {
+        videoService.update(video);
+        return "更新成功";
+    }
+
+    /**
+     * 删除视频
+     * @param id 视频ID
+     * @return 操作结果
+     */
+    @ApiOperation("删除视频")
+    @DeleteMapping("/{id}")
+    public String delete(@PathVariable Long id) {
+        videoService.delete(id);
+        return "删除成功";
+    }
+
+    /**
+     * 获取某个课目下的所有视频地址。
+     *
+     * @return 查询结果。
+     */
+    @ApiOperation(value = "获取课目的监控视频地址")
+    @NoAuthInterface
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "taskId"),
+            @ApiImplicitParam(name = "subjectName"),
+    })
+    @PostMapping("/getSubjectCardUrls")
+    public ResponseResult<List<VideoVo>> getSubjectCardUrls(@MyRequestBody String taskId, @MyRequestBody String subjectName) {
+//        return ResponseResult.success(taskServiceV2.listCardOfSubject(taskId, subjectName));
+        return ResponseResult.success(new ArrayList<>());
+    }
+
+    /**
+     * 获取某个系统下的所有采集卡地址。
+     *
+     * @return 查询结果。
+     */
+    @ApiOperation(value = "获取采集卡地址")
+    @NoAuthInterface
+    @PostMapping("/getSystemUrl")
+    public List<VideoVo> getSystemUrl(String systemId) {
+//        List<VideoVo> videoVoList = videoService.getSystemVideo(systemId);
+        return new ArrayList<>();
+    }
+
+    public void deleteTsFile() {
+        File file = new File("F://nginx-1.8.1/nginx-rtmp-win32-dev/html");
+        String[] filelist = file.list();
+        for (int i = 0; i < filelist.length; i++) {
+            File delfile = new File("F://nginx-1.8.1/nginx-rtmp-win32-dev/html" + "/" + filelist[i]);
+            if (!delfile.isDirectory()) {
+                if (delfile.getName().endsWith("ts")) {
+                    delfile.delete();
+                    System.out.println(delfile.getAbsolutePath() + "删除文件成功");
+                }
+            }
+        }
+    }
+    public void closeHistoryProgram(String processName) {
+        String cmd = "taskkill /f /t /im " + processName;
+//        try {
+            CommandUtil commandUtil = new CommandUtil();
+            commandUtil.winExec(cmd);
+//            // exec执行cmd命令
+//            Process process = Runtime.getRuntime().exec(cmd);
+//            // 获取CMD命令结果的输出流
+//            InputStream fis = process.getInputStream();
+//            InputStreamReader isr = new InputStreamReader(fis, "GBK");
+//            // 使用缓冲器读取
+//            BufferedReader br = new BufferedReader(isr);
+//            String line = null;
+//            // 全部读取完成为止,一行一行读取
+//            while ((line = br.readLine()) != null) {
+//                // 输出读取的内容
+////                System.out.println(line);
+//            }
+//        } catch (IOException e) {
+//
+//            e.printStackTrace();
+//        }
+
+    }
+
+}

+ 40 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/domain/Video.java

@@ -0,0 +1,40 @@
+package com.supervision.web.videoManage.domain;
+
+
+import lombok.Data;
+
+/**
+ * 视频信息实体类,对应数据库表 video
+ * 用于存储视频的基本连接与账户信息
+ */
+@Data
+public class Video {
+
+    /** 主键ID */
+    private Long id;
+
+    /** 视频流访问URL */
+    private String url;       // 原始 RTSP 地址
+
+    // transient 表示不映射到数据库字段
+    private transient String playUrl;   // m3u8 播放地址
+
+    /** 视频名称 */
+    private String name;
+
+    /** 视频设备IP地址 */
+    private String ip;
+
+    /** 设备端口号 */
+    private String port;
+
+    /** 登录用户名 */
+    private String username;
+
+    /** 登录密码 */
+    private String password;
+
+    /** 视频通道名称 */
+    private String channelName;
+
+}

+ 52 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/mapper/VideoMapper.java

@@ -0,0 +1,52 @@
+package com.supervision.web.videoManage.mapper;
+
+
+import com.supervision.web.videoManage.domain.Video;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * VideoMapper 接口
+ * 用于定义数据库操作方法(由 XML 文件具体实现)
+ */
+@Mapper
+public interface VideoMapper {
+
+    /**
+     * 查询所有视频信息
+     * @return 视频列表
+     */
+    List<Video> selectAll();
+
+    /**
+     * 根据ID查询视频信息
+     * @param id 视频ID
+     * @return 视频对象
+     */
+    Video selectById(Long id);
+
+    /**
+     * 新增视频记录
+     * @param video 视频对象
+     * @return 影响行数(1 表示成功)
+     */
+    int insert(Video video);
+
+    /**
+     * 更新视频信息
+     * @param video 视频对象
+     * @return 影响行数(1 表示成功)
+     */
+    int update(Video video);
+
+    /**
+     * 根据ID删除视频
+     * @param id 视频ID
+     * @return 影响行数(1 表示成功)
+     */
+    int deleteById(Long id);
+
+    List<Video> selectByNameLike(String name);
+
+}

+ 58 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/mapper/VideoMapper.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.supervision.web.videoManage.mapper.VideoMapper">
+
+    <!-- 结果映射,将数据库字段映射到实体属性 -->
+    <resultMap id="VideoResultMap" type="com.supervision.web.videoManage.domain.Video">
+        <id property="id" column="id"/>
+        <result property="url" column="url"/>
+        <result property="name" column="name"/>
+        <result property="ip" column="ip"/>
+        <result property="port" column="port"/>
+        <result property="username" column="username"/>
+        <result property="password" column="password"/>
+        <result property="channelName" column="channel_name"/>
+    </resultMap>
+
+    <!-- 查询所有视频 -->
+    <select id="selectAll" resultMap="VideoResultMap">
+        SELECT * FROM new_video
+    </select>
+
+    <!-- 根据视频名称模糊查询 -->
+    <select id="selectByNameLike" parameterType="String" resultMap="VideoResultMap">
+        SELECT * FROM new_video
+        WHERE name LIKE CONCAT('%', #{name}, '%')
+    </select>
+
+    <!-- 根据ID查询视频 -->
+    <select id="selectById" parameterType="long" resultMap="VideoResultMap">
+        SELECT * FROM new_video WHERE id = #{id}
+    </select>
+
+    <!-- 新增视频 -->
+    <insert id="insert" parameterType="com.supervision.web.videoManage.domain.Video" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO new_video (url, name, ip, port, username, password, channel_name)
+        VALUES (#{url}, #{name}, #{ip}, #{port}, #{username}, #{password}, #{channelName})
+    </insert>
+
+    <!-- 更新视频信息 -->
+    <update id="update" parameterType="com.supervision.web.videoManage.domain.Video">
+        UPDATE new_video
+        SET url = #{url},
+            name = #{name},
+            ip = #{ip},
+            port = #{port},
+            username = #{username},
+            password = #{password},
+            channel_name = #{channelName}
+        WHERE id = #{id}
+    </update>
+
+    <!-- 根据ID删除视频 -->
+    <delete id="deleteById" parameterType="long">
+        DELETE FROM new_video WHERE id = #{id}
+    </delete>
+</mapper>

+ 46 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/CommandUtil.java

@@ -0,0 +1,46 @@
+package com.supervision.web.videoManage.other;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+public class CommandUtil {
+    public String linuxExec(String cmd) {
+        System.out.println("执行命令[ " + cmd + "]");
+        Runtime run = Runtime.getRuntime();
+        try {
+            Process process = run.exec(cmd);
+            String line;
+            BufferedReader stdoutReader =
+                    new BufferedReader(new InputStreamReader(process.getInputStream()));
+            StringBuffer out = new StringBuffer();
+            while ((line = stdoutReader.readLine()) != null) {
+                out.append(line);
+            }
+            try {
+                process.waitFor();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            process.destroy();
+            return out.toString();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+    /** 调用windwos命令* */
+    public String winExec(String cmd) {
+        Runtime runtime = Runtime.getRuntime();
+        String command =cmd;
+        try {
+            System.out.println("开始执行命令行"+command);
+            Process process = runtime.exec(command);
+            new InputStreamReader(process.getInputStream());
+            return "成功";
+        } catch (IOException e) {
+            e.printStackTrace();
+            return "请检查摄像头地址";
+        }
+    }
+
+}

+ 50 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/ContextUtil.java

@@ -0,0 +1,50 @@
+package com.supervision.web.videoManage.other;
+
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 获取Servlet HttpRequest和HttpResponse的工具类。
+ *
+ * @author Jerry
+ * @date 2021-06-06
+ */
+public class ContextUtil {
+
+    /**
+     * 判断当前是否处于HttpServletRequest上下文环境。
+     *
+     * @return 是返回true,否则false。
+     */
+    public static boolean hasRequestContext() {
+        return RequestContextHolder.getRequestAttributes() != null;
+    }
+
+    /**
+     * 获取Servlet请求上下文的HttpRequest对象。
+     *
+     * @return 请求上下文中的HttpRequest对象。
+     */
+    public static HttpServletRequest getHttpRequest() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+    }
+
+    /**
+     * 获取Servlet请求上下文的HttpResponse对象。
+     *
+     * @return 请求上下文中的HttpResponse对象。
+     */
+    public static HttpServletResponse getHttpResponse() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
+    }
+
+    /**
+     * 私有构造函数,明确标识该常量类的作用。
+     */
+    private ContextUtil() {
+    }
+}

+ 85 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/ErrorCodeEnum.java

@@ -0,0 +1,85 @@
+package com.supervision.web.videoManage.other;
+
+
+/**
+ * 返回应答中的错误代码和错误信息。
+ *
+ * @author Jerry
+ * @date 2021-06-06
+ */
+public enum ErrorCodeEnum {
+
+    /**
+     * 没有错误
+     */
+    NO_ERROR("没有错误"),
+    /**
+     * 未处理的异常!
+     */
+    UNHANDLED_EXCEPTION("未处理的异常!"),
+
+    ARGUMENT_NULL_EXIST("数据验证失败,接口调用参数存在空值,请核对!"),
+    ARGUMENT_PK_ID_NULL("数据验证失败,接口调用主键Id参数为空,请核对!"),
+    INVALID_ARGUMENT_FORMAT("数据验证失败,不合法的参数格式,请核对!"),
+    INVALID_STATUS_ARGUMENT("数据验证失败,无效的状态参数值,请核对!"),
+    UPLOAD_FAILED("数据验证失败,数据上传失败!"),
+    INVALID_UPLOAD_FIELD("数据验证失败,该字段不支持数据上传!"),
+    INVALID_UPLOAD_STORE_TYPE("数据验证失败,并不支持上传存储类型!"),
+    INVALID_UPLOAD_FILE_ARGUMENT("数据验证失败,上传文件参数错误,请核对!"),
+    INVALID_UPLOAD_FILE_IOERROR("上传文件写入失败,请联系管理员!"),
+    UNAUTHORIZED_LOGIN("当前用户尚未登录或登录已超时,请重新登录!"),
+    UNAUTHORIZED_USER_PERMISSION("权限验证失败,当前用户不能访问该接口,请核对!"),
+    NO_ACCESS_PERMISSION("当前用户没有访问权限,请核对!"),
+    NO_OPERATION_PERMISSION("当前用户没有操作权限,请核对!"),
+
+    PASSWORD_ERR("密码错误,请重试!"),
+    INVALID_USERNAME_PASSWORD("用户名或密码错误,请重试!"),
+    INVALID_ACCESS_TOKEN("无效的用户访问令牌!"),
+    INVALID_USER_STATUS("用户状态错误,请刷新后重试!"),
+    INVALID_TENANT_CODE("指定的租户编码并不存在,请刷新后重试!"),
+    INVALID_TENANT_STATUS("当前租户为不可用状态,请刷新后重试!"),
+    INVALID_USER_TENANT("当前用户并不属于当前租户,请刷新后重试!"),
+
+    HAS_CHILDREN_DATA("数据验证失败,子数据存在,请刷新后重试!"),
+    DATA_VALIDATED_FAILED("数据验证失败,请核对!"),
+    UPLOAD_FILE_FAILED("文件上传失败,请联系管理员!"),
+    DATA_SAVE_FAILED("数据保存失败,请联系管理员!"),
+    DATA_ACCESS_FAILED("数据访问失败,请联系管理员!"),
+    DATA_PERM_ACCESS_FAILED("数据访问失败,您没有该页面的数据访问权限!"),
+    DUPLICATED_UNIQUE_KEY("数据保存失败,存在重复数据,请核对!"),
+    DATA_NOT_EXIST("数据不存在,请刷新后重试!"),
+    DATA_PARENT_LEVEL_ID_NOT_EXIST("数据验证失败,父级别关联Id不存在,请刷新后重试!"),
+    DATA_PARENT_ID_NOT_EXIST("数据验证失败,ParentId不存在,请核对!"),
+    INVALID_RELATED_RECORD_ID("数据验证失败,关联数据并不存在,请刷新后重试!"),
+    INVALID_DATA_MODEL("数据验证失败,无效的数据实体对象!"),
+    INVALID_DATA_FIELD("数据验证失败,无效的数据实体对象字段!"),
+    INVALID_CLASS_FIELD("数据验证失败,无效的类对象字段!"),
+    SERVER_INTERNAL_ERROR("服务器内部错误,请联系管理员!"),
+    REDIS_CACHE_ACCESS_TIMEOUT("Redis缓存数据访问超时,请刷新后重试!"),
+    REDIS_CACHE_ACCESS_STATE_ERROR("Redis缓存数据访问状态错误,请刷新后重试!");
+
+    // 下面的枚举值为特定枚举值,即开发者可以根据自己的项目需求定义更多的非通用枚举值
+
+    /**
+     * 构造函数。
+     *
+     * @param errorMessage 错误消息。
+     */
+    ErrorCodeEnum(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    /**
+     * 错误信息。
+     */
+    private final String errorMessage;
+
+    /**
+     * 获取错误信息。
+     *
+     * @return 错误信息。
+     */
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+}

+ 89 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/FFmpegManager.java

@@ -0,0 +1,89 @@
+package com.supervision.web.videoManage.other;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import java.io.*;
+import java.util.concurrent.*;
+
+@Component
+public class FFmpegManager {
+
+    private static final Logger logger = LoggerFactory.getLogger(FFmpegManager.class);
+
+    private String ffmpegPath;
+    private String filePath;
+    private String nginxProxyUrl;
+    private String videoFormat = ".m3u8";
+    private ExecutorService executor;
+
+    public FFmpegManager(@Value("${video.ffmpegPath}") String ffmpegPath,
+                         @Value("${video.filePath}") String filePath,
+                         @Value("${video.nginxProxyUrl}") String nginxProxyUrl) {
+        this.ffmpegPath = ffmpegPath;
+        this.filePath = filePath;
+        this.nginxProxyUrl = nginxProxyUrl;
+        this.executor = Executors.newFixedThreadPool(5);
+    }
+
+    // 获取播放 URL
+    public String getPlayUrl(String cameraId) {
+        return String.format("%s%s%s", nginxProxyUrl, cameraId, videoFormat);
+    }
+
+    // 确保视频流 m3u8 文件已生成
+    public void ensureStream(String cameraId, String rtspUrl) throws IOException {
+        String m3u8FilePath = String.format("%s%s%s", filePath, cameraId, videoFormat);
+        if (isM3u8Exist(m3u8FilePath)) {
+            return;  // 文件已存在,无需重新生成
+        }
+        String cmd = buildFFmpegCommand(rtspUrl, cameraId);
+        runCommandAsync(cmd);  // 异步执行 FFmpeg 命令
+    }
+
+    private boolean isM3u8Exist(String m3u8FilePath) {
+        File file = new File(m3u8FilePath);
+        return file.exists() && file.isFile();
+    }
+
+    private String buildFFmpegCommand(String rtspUrl, String cameraId) {
+        // -rtsp_transport udp → 使用 UDP 协议拉 RTSP 流
+        // -i rtsp://... → 输入 RTSP 流地址
+        // -an → 不处理音频(如果有音频会被忽略)
+        // -c:v libx264 → 用 H.264 编码视频
+        // -preset veryfast → 编码速度快、延迟低
+        // -crf 23 → 恒定质量,23 为默认画质
+        // -f hls → 输出 HLS 流
+        // -hls_time 4 → 每个分片 4 秒
+        // -hls_list_size 15 → 主播放列表保留 15 个分片
+        // -hls_segment_filename "segment_%03d.ts" → 分片文件名模板
+        // stream.m3u8 → 主播放列表文件
+        String ffmpegCommand = String.format(
+                "\"%s\" -rtsp_transport udp -i \"%s\" -an -c:v libx264 -preset veryfast -crf 23 -f hls -hls_time 2 -hls_list_size 15 " +
+                        "-hls_segment_filename \"%s/%s_%%04d.ts\" \"%s/%s.m3u8\"",
+                ffmpegPath,       // FFmpeg 可执行文件
+                rtspUrl,          // RTSP URL
+                filePath,        // 分片输出目录
+                cameraId,         // 分片前缀
+                filePath,        // 播放列表输出目录
+                cameraId          // 播放列表文件名
+        );
+        logger.info("FFmpeg 命令:{}", ffmpegCommand);
+        return ffmpegCommand;
+    }
+
+    private void runCommandAsync(String cmd) {
+        executor.submit(() -> {
+            try {
+                Process process = new ProcessBuilder(cmd.split(" ")).start();
+                int exitCode = process.waitFor();
+                if (exitCode != 0) {
+                    logger.error("FFmpeg 进程执行失败,退出代码:" + exitCode);
+                }
+            } catch (IOException | InterruptedException e) {
+                logger.error("FFmpeg 执行出错", e);
+            }
+        });
+    }
+}

+ 31 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/MyRequestBody.java

@@ -0,0 +1,31 @@
+package com.supervision.web.videoManage.other;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标记Controller中的方法参数,参数解析器会根据该注解将请求中的JSON数据,映射到参数中的绑定字段。
+ *
+ * @author Jerry
+ * @date 2021-06-06
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MyRequestBody {
+
+    /**
+     * 是否必须出现的参数。
+     */
+    boolean required() default false;
+    /**
+     * 解析时用到的JSON的key。
+     */
+    String value() default "";
+    /**
+     * 集合元素的ClassType。只有在接口参数为List<E>的时候,需要把E的class传入。
+     * 缺省值Class.class表示没有设置。
+     */
+    Class<?> elementType() default Class.class;
+}

+ 16 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/NoAuthInterface.java

@@ -0,0 +1,16 @@
+package com.supervision.web.videoManage.other;
+
+
+import java.lang.annotation.*;
+
+/**
+ * 主要用于标记无需Token验证的接口
+ *
+ * @author Jerry
+ * @date 2021-06-06
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NoAuthInterface {
+}

+ 233 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/other/ResponseResult.java

@@ -0,0 +1,233 @@
+package com.supervision.web.videoManage.other;
+
+
+import com.alibaba.fastjson.JSON;
+import com.supervision.web.videoManage.other.ErrorCodeEnum;
+import com.supervision.web.videoManage.other.ContextUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * 接口返回对象
+ */
+@Slf4j
+@Data
+public class ResponseResult<T> {
+
+    /**
+     * 为了优化性能,所有没有携带数据的正确结果,均可用该对象表示。
+     */
+    private static final ResponseResult<Void> OK = new ResponseResult<>();
+    /**
+     * 是否成功标记。
+     */
+    private boolean success = true;
+    /**
+     * 错误码。
+     */
+    private String errorCode = "NO-ERROR";
+    /**
+     * 错误信息描述。
+     */
+    private String errorMessage = "NO-MESSAGE";
+    /**
+     * 实际数据。
+     */
+    private T data = null;
+
+    /**
+     * 根据参数errorCodeEnum的枚举值,判断创建成功对象还是错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和 getErrorMessage()。
+     *
+     * @param errorCodeEnum 错误码枚举
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static ResponseResult<Void> create(ErrorCodeEnum errorCodeEnum) {
+        return create(errorCodeEnum, errorCodeEnum.getErrorMessage());
+    }
+
+    /**
+     * 根据参数errorCodeEnum的枚举值,判断创建成功对象还是错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和参数 errorMessage。
+     *
+     * @param errorCodeEnum 错误码枚举。
+     * @param errorMessage  如果该参数为null,错误信息取自errorCodeEnum参数内置的errorMessage,否则使用当前参数。
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static ResponseResult<Void> create(ErrorCodeEnum errorCodeEnum, String errorMessage) {
+        errorMessage = errorMessage != null ? errorMessage : errorCodeEnum.getErrorMessage();
+        return errorCodeEnum == ErrorCodeEnum.NO_ERROR ? success() : error(errorCodeEnum.name(), errorMessage);
+    }
+
+    /**
+     * 根据参数errorCode是否为空,判断创建成功对象还是错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCode 和参数 errorMessage。
+     *
+     * @param errorCode    自定义的错误码
+     * @param errorMessage 自定义的错误信息
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static ResponseResult<Void> create(String errorCode, String errorMessage) {
+        return errorCode == null ? success() : error(errorCode, errorMessage);
+    }
+
+    /**
+     * 根据参数errorCodeEnum的枚举值,判断创建成功对象还是错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和参数 errorMessage。
+     *
+     * @param errorCodeEnum 错误码枚举。
+     * @param errorMessage  如果该参数为null,错误信息取自errorCodeEnum参数内置的errorMessage,否则使用当前参数。
+     * @param data          如果错误枚举值为NO_ERROR,则返回该数据。
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static <T> ResponseResult<T> create(ErrorCodeEnum errorCodeEnum, String errorMessage, T data) {
+        errorMessage = errorMessage != null ? errorMessage : errorCodeEnum.getErrorMessage();
+        return errorCodeEnum == ErrorCodeEnum.NO_ERROR ? success(data) : error(errorCodeEnum.name(), errorMessage);
+    }
+
+    /**
+     * 创建成功对象。
+     * 如果需要绑定返回数据,可以在实例化后调用setDataObject方法。
+     *
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static ResponseResult<Void> success() {
+        return OK;
+    }
+
+    /**
+     * 创建带有返回数据的成功对象。
+     *
+     * @param data 返回的数据对象
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static <T> ResponseResult<T> success(T data) {
+        ResponseResult<T> resp = new ResponseResult<>();
+        resp.data = data;
+        return resp;
+    }
+
+    /**
+     * 创建错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和 getErrorMessage()。
+     *
+     * @param errorCodeEnum 错误码枚举
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static <T> ResponseResult<T> error(ErrorCodeEnum errorCodeEnum) {
+        return error(errorCodeEnum.name(), errorCodeEnum.getErrorMessage());
+    }
+
+    /**
+     * 创建错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCodeEnum 的 name() 和参数 errorMessage。
+     *
+     * @param errorCodeEnum 错误码枚举
+     * @param errorMessage  自定义的错误信息
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static <T> ResponseResult<T> error(ErrorCodeEnum errorCodeEnum, String errorMessage) {
+        return error(errorCodeEnum.name(), errorMessage);
+    }
+
+    /**
+     * 创建错误对象。
+     * 如果返回错误对象,errorCode 和 errorMessage 分别取自于参数 errorCode 和参数 errorMessage。
+     *
+     * @param errorCode    自定义的错误码
+     * @param errorMessage 自定义的错误信息
+     * @return 返回创建的ResponseResult实例对象
+     */
+    public static <T> ResponseResult<T> error(String errorCode, String errorMessage) {
+        return new ResponseResult<>(errorCode, errorMessage);
+    }
+
+    /**
+     * 根据参数中出错的ResponseResult,创建新的错误应答对象。
+     *
+     * @param errorCause 导致错误原因的应答对象。
+     * @return 返回创建的ResponseResult实例对象。
+     */
+    public static <T, E> ResponseResult<T> errorFrom(ResponseResult<E> errorCause) {
+        return error(errorCause.errorCode, errorCause.getErrorMessage());
+    }
+
+    /**
+     * 根据参数中出错的CallResult,创建新的错误应答对象。
+     *
+     * @param errorCause 导致错误原因的应答对象。
+     * @return 返回创建的ResponseResult实例对象。
+     */
+//    public static <T> ResponseResult<T> errorFrom(CallResult errorCause) {
+//        return error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorCause.getErrorMessage());
+//    }
+
+    /**
+     * 是否成功。
+     *
+     * @return true成功,否则false。
+     */
+    public boolean isSuccess() {
+        return success;
+    }
+
+    /**
+     * 通过HttpServletResponse直接输出应该信息的工具方法。
+     *
+     * @param httpStatus     http状态码。
+     * @param responseResult 应答内容。
+     * @param <T>            数据对象类型。
+     * @throws IOException 异常错误。
+     */
+    public static <T> void output(int httpStatus, ResponseResult<T> responseResult) throws IOException {
+        if (httpStatus != HttpServletResponse.SC_OK) {
+            log.error(JSON.toJSONString(responseResult));
+        } else {
+            log.info(JSON.toJSONString(responseResult));
+        }
+        HttpServletResponse response = ContextUtil.getHttpResponse();
+        PrintWriter out = response.getWriter();
+        response.setContentType("application/json; charset=utf-8");
+        response.setStatus(httpStatus);
+        if (responseResult != null) {
+            out.print(JSON.toJSONString(responseResult));
+        }
+        out.flush();
+    }
+
+    /**
+     * 通过HttpServletResponse直接输出应该信息的工具方法。
+     *
+     * @param httpStatus     http状态码。
+     * @param <T>            数据对象类型。
+     * @throws IOException 异常错误。
+     */
+    public static <T> void output(int httpStatus) throws IOException {
+        output(httpStatus, null);
+    }
+
+    /**
+     * 通过HttpServletResponse直接输出应该信息的工具方法。Http状态码为200。
+     *
+     * @param responseResult 应答内容。
+     * @param <T>            数据对象类型。
+     * @throws IOException 异常错误。
+     */
+    public static <T> void output(ResponseResult<T> responseResult) throws IOException {
+        output(HttpServletResponse.SC_OK, responseResult);
+    }
+
+    private ResponseResult() {
+
+    }
+
+    private ResponseResult(String errorCode, String errorMessage) {
+        this.success = false;
+        this.errorCode = errorCode;
+        this.errorMessage = errorMessage;
+    }
+}

+ 59 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/service/VideoService.java

@@ -0,0 +1,59 @@
+package com.supervision.web.videoManage.service;
+
+
+import com.supervision.web.videoManage.domain.Video;
+import com.supervision.web.videoManage.vo.VideoVo;
+
+import java.util.List;
+
+/**
+ * 视频管理服务接口层
+ * 定义视频信息的业务操作方法
+ */
+public interface VideoService {
+
+    /**
+     * 查询所有视频
+     * @return 视频列表
+     */
+    List<Video> listAll();
+
+    List<Video> listAllPaged(int pageNum, int pageSize, String videoName);
+
+    List<Video> listVideoAllPaged(int pageNum, int pageSize, String name);
+
+    /**
+     * 根据ID查询视频
+     * @param id 视频ID
+     * @return 视频对象
+     */
+    Video getById(Long id);
+
+    /**
+     * 新增视频
+     * @param video 视频对象
+     * @return 影响行数
+     */
+    int add(Video video);
+
+    /**
+     * 更新视频
+     * @param video 视频对象
+     * @return 影响行数
+     */
+    int update(Video video);
+
+    /**
+     * 删除视频
+     * @param id 视频ID
+     * @return 影响行数
+     */
+    int delete(Long id);
+
+    List<VideoVo> getSubjectVideo(String subjectName);
+
+    List<VideoVo> getSystemVideo(String systemId);
+
+    Boolean deleteTsFile();
+}
+

+ 200 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/service/VideoServiceImpl.java

@@ -0,0 +1,200 @@
+package com.supervision.web.videoManage.service;
+
+
+import com.github.pagehelper.PageHelper;
+import com.supervision.web.videoManage.domain.Video;
+import com.supervision.web.videoManage.mapper.VideoMapper;
+import com.supervision.web.videoManage.other.FFmpegManager;
+import com.supervision.web.videoManage.vo.VideoVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+@Slf4j
+@Service("VideoService")
+public class VideoServiceImpl implements VideoService {
+
+    @Value("${video.nginxProxyUrl}")
+    private String nginxProxyUrl;
+
+//    @Value("${video.videoFormat}")
+//    private String videoFormat;
+
+    @Value("${video.videoFilePath}")
+    private String videoFilePath;
+
+//    @Value("${video.cmd}")
+//    private String ffmpegCmd;
+
+//    private CommandUtil commandUtil;
+
+//    private int i;
+
+    /** 注入Mapper用于数据库访问 */
+    @Autowired
+    private VideoMapper videoMapper;
+
+    // FFmpegManager 作为单例
+    private final FFmpegManager ffmpegManager;
+
+    // 构造函数注入 FFmpegManager
+    @Autowired
+    public VideoServiceImpl(FFmpegManager ffmpegManager) {
+        this.ffmpegManager = ffmpegManager;
+    }
+
+    /** 查询所有视频 */
+    @Override
+    public List<Video> listAll() {
+        return videoMapper.selectAll();
+    }
+
+    /**
+     * 分页查询所有视频,并在需要时生成 m3u8 播放流文件
+     * @param pageNum 页码(从1开始)
+     * @param pageSize 每页数量
+     * @param name 视频名称(模糊查询)
+     * @return 当前页的视频列表(含播放URL)
+     */
+    @Override
+    public List<Video> listAllPaged(int pageNum, int pageSize, String name) {
+        // 启用分页
+        PageHelper.startPage(pageNum, pageSize);
+        // 模糊查询数据库
+        List<Video> list = videoMapper.selectByNameLike(name);
+        return list;
+    }
+
+    /**
+     * 分页查询所有视频,并在需要时生成 m3u8 播放流文件
+     * @param pageNum 页码(从1开始)
+     * @param pageSize 每页数量
+     * @param name 视频名称(模糊查询)
+     * @return 当前页的视频列表(含播放URL)
+     */
+    @Override
+    public List<Video> listVideoAllPaged(int pageNum, int pageSize, String name) {
+        // 启用分页
+        PageHelper.startPage(pageNum, pageSize);
+
+        // 模糊查询数据库
+        List<Video> list = videoMapper.selectByNameLike(name);
+
+        // 异步生成 m3u8 地址
+        for (Video video : list) {
+            String cameraId = String.valueOf(video.getId());
+            String playUrl = ffmpegManager.getPlayUrl(cameraId);  // 获取 m3u8 播放地址
+            video.setPlayUrl(playUrl);
+
+            String rtspUrl = video.getUrl();
+            CompletableFuture.runAsync(() -> {
+                try {
+                    ffmpegManager.ensureStream(cameraId, rtspUrl);  // 异步生成 m3u8 文件
+                } catch (Exception e) {
+                    log.error("FFmpeg 生成流失败: {}", cameraId, e);
+                }
+            });
+        }
+
+        return list;
+    }
+
+    /** 根据ID查询视频 */
+    @Override
+    public Video getById(Long id) {
+        return videoMapper.selectById(id);
+    }
+
+    /** 新增视频 */
+    @Override
+    public int add(Video video) {
+        return videoMapper.insert(video);
+    }
+
+    /** 更新视频 */
+    @Override
+    public int update(Video video) {
+        return videoMapper.update(video);
+    }
+
+    /** 删除视频 */
+    @Override
+    public int delete(Long id) {
+        return videoMapper.deleteById(id);
+    }
+
+    @Override
+    public List<VideoVo> getSubjectVideo(String subjectName) {
+//        List<Map<String,String>> urlAndNameList = videoMapper.getSubjectVideoUrl(subjectName);
+//        List<VideoVo> videoList = new ArrayList<>();
+//        i=0;
+//        commandUtil = new CommandUtil();
+//        urlAndNameList.forEach(item->{
+//            VideoVo videoVo = new VideoVo();
+//            videoVo.setName(item.get("name"));
+////            videoVo.setUrl(item.get("url"));
+////            videoVo.setUrl("http://localhost:8080/"+subjectName+String.valueOf(i)+".m3u8");
+//            videoVo.setUrl(String.format("%s%s%s%s", nginxProxyUrl, subjectName, i, videoFormat));
+//            boolean ism3u8Exist = isM3u8Exist(String.format("%s%s%s%s", videoFilePath, subjectName, i, videoFormat));
+//
+//            if (!ism3u8Exist) {
+//                String cmd = String.format("%s%s%s%s%s", ffmpegCmd.replace("rtspUrl", item.get("url")), videoFilePath, subjectName, i, videoFormat);
+//                commandUtil.winExec(cmd);
+//            }
+//
+//            videoList.add(videoVo);
+//            i++;
+//        });
+//        return videoList;
+        return new ArrayList<>();
+    }
+
+    private boolean isM3u8Exist(String m3u8Path) {
+        return new File(m3u8Path).exists();
+    }
+
+    @Override
+    public List<VideoVo> getSystemVideo(String systemId) {
+//        List<Map<String,String>> urlAndNameList = videoMapper.getSystemVideoUrl(systemId);
+//        List<VideoVo> videoList = new ArrayList<>();
+//        urlAndNameList.forEach(item->{
+//            VideoVo videoVo = new VideoVo();
+//            videoVo.setName(item.get("name"));
+//            videoVo.setUrl(item.get("url"));
+//            videoList.add(videoVo);
+//        });
+//
+//        return videoList;
+        return new ArrayList<>();
+    }
+
+    @Override
+    public Boolean deleteTsFile() {
+        File file = new File(videoFilePath);
+        String[] fileList = file.list();
+        boolean res = true;
+        if (fileList != null) {
+            for (String s : fileList) {
+                File delfile = new File(videoFilePath + s);
+                if (!delfile.isDirectory()) {
+                    if (delfile.getName().endsWith("ts")) {
+                        boolean delete = delfile.delete();
+                        if (delete) {
+                            log.info("删除文件{}成功", delfile.getAbsolutePath());
+                        } else {
+                            log.warn("删除文件{}失败", delfile.getAbsolutePath());
+                            res = false;
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+}
+

+ 33 - 0
supervision-admin/src/main/java/com/supervision/web/videoManage/vo/VideoVo.java

@@ -0,0 +1,33 @@
+package com.supervision.web.videoManage.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@ApiModel
+public class VideoVo {
+    @ApiModelProperty(value = "url")
+    private String url;
+
+    @ApiModelProperty(value = "name")
+    private String name;
+
+    @ApiModelProperty(value = "ip")
+    private String ip;
+
+    @ApiModelProperty(value = "port")
+    private String port;
+
+    @ApiModelProperty(value = "username")
+    private String username;
+
+    @ApiModelProperty(value = "password")
+    private String password;
+
+    @ApiModelProperty(value = "channelName")
+    private String channelName;
+
+}

+ 18 - 0
supervision-admin/src/main/resources/application.yml

@@ -129,3 +129,21 @@ iclock:
 
 # 门禁图片路径
 faceImgPath: D:/supervision/faceImgPath
+
+# FFmpeg 配置
+video:
+  filePath: C:/Users/11868/Desktop/videos/file  # 存放 m3u8 文件目录
+  nginxProxyUrl: "http://localhost:8080/"             # 播放地址前缀
+  ffmpegPath: C:/Users/11868/Desktop/videos/ffmpeg/bin/ffmpeg.exe  # ffmpeg 可执行路径
+  hlsSegmentTime: 1                                  # HLS 切片时间
+  hlsListSize: 10                                    # HLS playlist 最大条数
+  videoFormat: ".m3u8"
+  videoFilePath: C:/Users/11868/Desktop/videos/hk   # 视频存放路径
+  cameraUrl: "http://192.168.5.89:4747/video"  #使用 HTTP 流地址
+
+# 监控推流nginx
+#video:
+#  nginxProxyUrl: "http://localhost:8080/"
+#  videoFormat: ".m3u8"
+#  videoFilePath: "F:/nginx-1.8.1/nginx-rtmp-win32-dev/html/hk/"
+#  cmd: "cmd /k start ffmpeg -rtsp_transport tcp -i rtspUrl -c:v libx264 -c:a aac -f hls -force_key_frames \"expr:gte(t,n_forced*1)\" -hls_time 1 -hls_list_size 10 "