From 5141265c6214e84d4ef8a48c6cead6c372cc3892 Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Tue, 22 Oct 2024 16:10:51 +0800
Subject: [PATCH] 增加:tcp连接注册和心跳

---
 ard-work/src/main/java/com/ard/vtdu/domain/Readers.java                         |   16 
 ard-work/src/main/java/com/ard/tianyu/netty/TyNettyTcpClient.java               |  103 ++
 ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolDecoder.java |   51 +
 ard-work/src/main/java/com/ard/vtdu/domain/Source.java                          |   16 
 ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSession.java                   |   31 
 ard-work/src/main/java/com/ard/vtdu/util/ArdTool.java                           |  214 +++++
 ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java           |   70 +
 ard-work/src/main/java/com/ard/vtdu/domain/RtspSessions.java                    |   21 
 ard-work/src/main/java/com/ard/vtdu/service/IMediaService.java                  |   69 +
 ard-work/src/main/java/com/ard/tianyu/config/TyConfiguration.java               |   17 
 ard-work/src/main/java/com/ard/vtdu/domain/Item.java                            |   29 
 ard-work/src/main/java/com/ard/vtdu/domain/Segment.java                         |   22 
 ard-work/src/main/java/com/ard/tianyu/model/dto/BoxSettingsDTO.java             |   74 +
 ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolEncoder.java |   16 
 ard-work/src/main/java/com/ard/vtdu/client/MediaClient.java                     |  123 +++
 ard-work/src/main/java/com/ard/vtdu/controller/MediaController.java             |  216 +++++
 ard-work/src/main/java/com/ard/vtdu/domain/Paths.java                           |   23 
 ard-work/src/main/java/com/ard/vtdu/domain/StreamInfo.java                      |   85 ++
 ard-work/src/main/java/com/ard/tianyu/netty/handler/ClientHandler.java          |   89 ++
 ard-work/src/main/java/com/ard/tianyu/http/TyClient.java                        |   23 
 ard-work/src/main/java/com/ard/vtdu/domain/RtmpSession.java                     |   27 
 ard-work/src/main/java/com/ard/vtdu/domain/RtspSession.java                     |   29 
 ard-work/src/main/java/com/ard/tianyu/http/TyConst.java                         |    2 
 ard-work/src/main/java/com/ard/tianyu/service/ITyService.java                   |   14 
 ard-work/src/main/java/com/ard/vtdu/domain/ArdVtdu.java                         |   74 +
 ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSessions.java                  |   21 
 ard-work/src/main/java/com/ard/vtdu/domain/Conf.java                            |   46 +
 ard-admin/src/main/resources/application.yml                                    |    2 
 ard-work/pom.xml                                                                |    8 
 ard-work/src/main/java/com/ard/vtdu/service/impl/MediaServiceImpl.java          |  669 ++++++++++++++++
 ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatCommand.java       |   22 
 ard-work/src/main/java/com/ard/tianyu/netty/command/RegisterCommand.java        |   24 
 ard-work/src/main/java/com/ard/tianyu/netty/NettyConfig.java                    |   39 
 ard-admin/src/main/java/com/ard/ArdUavApplication.java                          |    2 
 ard-work/src/main/java/com/ard/tianyu/controller/UavController.java             |    7 
 ard-work/src/main/java/com/ard/tianyu/netty/command/BaseCommand.java            |   14 
 ard-work/src/main/java/com/ard/tianyu/netty/command/PointFlyCommand.java        |   30 
 ard-admin/src/main/resources/application-dev.yml                                |   15 
 ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatReplyCommand.java  |   26 
 ard-work/src/main/java/com/ard/vtdu/domain/RtmpSessions.java                    |   21 
 ard-work/src/main/java/com/ard/vtdu/domain/PathConfig.java                      |   20 
 ard-work/src/main/java/com/ard/tianyu/netty/TyBoxSession.java                   |   21 
 42 files changed, 2,427 insertions(+), 14 deletions(-)

diff --git a/ard-admin/src/main/java/com/ard/ArdUavApplication.java b/ard-admin/src/main/java/com/ard/ArdUavApplication.java
index 27423d1..3778c26 100644
--- a/ard-admin/src/main/java/com/ard/ArdUavApplication.java
+++ b/ard-admin/src/main/java/com/ard/ArdUavApplication.java
@@ -3,12 +3,14 @@
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
  * 启动程序
  * 
  * @author ruoyi
  */
+@EnableScheduling
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
 public class ArdUavApplication
 {
diff --git a/ard-admin/src/main/resources/application-dev.yml b/ard-admin/src/main/resources/application-dev.yml
index 3ed4194..cb2ffc8 100644
--- a/ard-admin/src/main/resources/application-dev.yml
+++ b/ard-admin/src/main/resources/application-dev.yml
@@ -23,8 +23,19 @@
 
 # 天宇服务器配置
 tianyu:
-  # 天宇服务器地址
+  # 天宇http服务器地址
   endpoint: 112.98.106.22:12000
   accesskeyid: qmfmd3Ws7M4dMcylnrTU
   accesskeysecret: 5WBU2uEhHZaVefpZdaxpFh4LJW9fZZaa
-
+  # 天宇tcp服务器地址
+  tcpserver: 112.98.106.22
+  tcpport: 13002
+# netty配置
+netty:
+  tcp:
+    enabled: true
+    reConnectInterval: 30000
+#流媒体配置
+mediamtx:
+  # 流媒体服务器地址
+  host: 192.168.1.27
diff --git a/ard-admin/src/main/resources/application.yml b/ard-admin/src/main/resources/application.yml
index e50cece..00af4c8 100644
--- a/ard-admin/src/main/resources/application.yml
+++ b/ard-admin/src/main/resources/application.yml
@@ -89,7 +89,7 @@
   read-timeout: 3000           # 数据读取超时时间,单位为毫秒(默认为 timeout)
   max-retry-count: 0           # 请求失败后重试次数(默认为 0 次不重试)
   ssl-protocol: TLS            # 单向验证的HTTPS的默认TLS协议(默认为 TLS)
-  log-enabled: true            # 打开或关闭日志(默认为 true)
+  log-enabled: false            # 打开或关闭日志(默认为 true)
   log-request: true            # 打开/关闭Forest请求日志(默认为 true)
   log-response-status: true    # 打开/关闭Forest响应状态日志(默认为 true)
   log-response-content: true   # 打开/关闭Forest响应内容日志(默认为 false)
diff --git a/ard-work/pom.xml b/ard-work/pom.xml
index b0ce0c3..6e94a3d 100644
--- a/ard-work/pom.xml
+++ b/ard-work/pom.xml
@@ -35,6 +35,14 @@
             <artifactId>forest-spring-boot-starter</artifactId>
             <version>1.5.33</version>
         </dependency>
+
+        <!-- netty依赖 springboot2.x自动导入版本 -->
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+
+        <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
diff --git a/ard-work/src/main/java/com/ard/tianyu/config/TyConfiguration.java b/ard-work/src/main/java/com/ard/tianyu/config/TyConfiguration.java
index d93dfe2..513b94a 100644
--- a/ard-work/src/main/java/com/ard/tianyu/config/TyConfiguration.java
+++ b/ard-work/src/main/java/com/ard/tianyu/config/TyConfiguration.java
@@ -33,17 +33,20 @@
 
     private String accesskeysecret;
 
+    private String tcpserver;
+
+    private Integer tcpport;
 
     private long timestamp;
-    public long getTimeStamp()
-    {
+
+    public long getTimeStamp() {
         timestamp = new Date().getTime();
         return timestamp;
     }
+
     private String encryptstr;
 
-    public String getEncryptStr()
-    {
+    public String getEncryptStr() {
         String sb = new StringBuilder()
                 .append(accesskeyid)
                 .append(accesskeysecret)
@@ -53,13 +56,17 @@
         return encryptstr;
     }
 
+    public String getIpPort() {
+        return tcpserver + "/" + tcpport;
+    }
+
     @Resource
     private TyClient tyClient;
 
     private AccessTokenReceiver accessToken;
 
     public AccessTokenReceiver getAccessToken() {
-        if(accessToken != null)
+        if (accessToken != null)
             return accessToken;
         // 准备请求的json
         TokenDTO gtd = TokenDTO.builder()
diff --git a/ard-work/src/main/java/com/ard/tianyu/controller/UavController.java b/ard-work/src/main/java/com/ard/tianyu/controller/UavController.java
index 4eba05c..ca7aae4 100644
--- a/ard-work/src/main/java/com/ard/tianyu/controller/UavController.java
+++ b/ard-work/src/main/java/com/ard/tianyu/controller/UavController.java
@@ -3,6 +3,7 @@
 import com.ard.common.core.domain.AjaxResult;
 import com.ard.tianyu.model.dto.BoxHistoryDTO;
 import com.ard.tianyu.model.dto.BoxPhotosDTO;
+import com.ard.tianyu.model.dto.BoxSettingsDTO;
 import com.ard.tianyu.model.receiver.BoxHistoryReceiver;
 import com.ard.tianyu.model.receiver.BoxPhotosReceiver;
 import com.ard.tianyu.model.receiver.BoxReceiver;
@@ -62,4 +63,10 @@
         List<BoxPhotosReceiver> boxPhotos = tyService.getBoxPhotos(boxPhotosDTO);
         return AjaxResult.success(boxPhotos);
     }
+    @ApiOperation("修改云盒设置")
+    @PostMapping("/updateBoxSettings")
+    public AjaxResult updateBoxSettings(@RequestBody BoxSettingsDTO boxSettingsDTO) {
+        Boolean state = tyService.updateBoxSettings(boxSettingsDTO);
+        return AjaxResult.success(state);
+    }
 }
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/http/TyClient.java b/ard-work/src/main/java/com/ard/tianyu/http/TyClient.java
index 3978c29..e86b171 100644
--- a/ard-work/src/main/java/com/ard/tianyu/http/TyClient.java
+++ b/ard-work/src/main/java/com/ard/tianyu/http/TyClient.java
@@ -2,6 +2,7 @@
 
 import com.ard.tianyu.model.dto.BoxHistoryDTO;
 import com.ard.tianyu.model.dto.BoxPhotosDTO;
+import com.ard.tianyu.model.dto.BoxSettingsDTO;
 import com.ard.tianyu.model.dto.TokenDTO;
 import com.ard.tianyu.model.receiver.*;
 import com.dtflys.forest.annotation.*;
@@ -19,19 +20,39 @@
         baseURL = "http://#{tianyu.endpoint}"    // 默认域名
 )
 public interface TyClient {
-
+    /**
+     * 获取云盒TOKEN
+     */
     @Post(TyConst.POST_TOKEN)
     TyHttpCommonReceiver<AccessTokenReceiver> getToken(@JSONBody TokenDTO tokenDTO);
 
+    /**
+     * 获取云盒列表
+     */
     @Get(TyConst.GET_BOX_LIST)
     TyHttpCommonReceiver<List<BoxReceiver>> getBoxList(@Header Map<String, String> headers);
 
+    /**
+     * 获取直播地址
+     */
     @Get(TyConst.GET_BOX_LIVE_URL)
     TyHttpCommonReceiver<PullStreamUrlReceiver> getBoxLiveUrl(@Var("boxSn") String boxSn, @Header Map<String, String> headers);
 
+    /**
+     * 获取飞行历史
+     */
     @Post(TyConst.POST_BOX_HISTORY)
     TyHttpCommonReceiver<List<BoxHistoryReceiver>> getBoxHistory(@JSONBody BoxHistoryDTO boxHistoryDTO, @Header Map<String, String> headers);
 
+    /**
+     * 获取任务照片
+     */
     @Post(TyConst.POST_BOX_PHOTOS)
     TyHttpCommonReceiver<List<BoxPhotosReceiver>> getBoxPhotos(@JSONBody BoxPhotosDTO boxPhotosDTO, @Header Map<String, String> headers);
+
+    /**
+     * 修改云盒设置
+     */
+    @Post(TyConst.POST_BOX_UPDATE)
+    TyHttpCommonReceiver updateBoxSettings(@JSONBody BoxSettingsDTO boxSettingsDTO, @Header Map<String, String> headers);
 }
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/http/TyConst.java b/ard-work/src/main/java/com/ard/tianyu/http/TyConst.java
index 17394ae..8655da4 100644
--- a/ard-work/src/main/java/com/ard/tianyu/http/TyConst.java
+++ b/ard-work/src/main/java/com/ard/tianyu/http/TyConst.java
@@ -5,7 +5,7 @@
 
     public static final String GET_BOX_LIST = "/eapi/box/list";
     public static final String POST_BOX_HISTORY = "/eapi/box/history";
-    public static final String POST_BOX_NAME = "/eapi/box/name";
+    public static final String POST_BOX_UPDATE = "/eapi/box/update";
     public static final String POST_UPDATE_LIVE = "/eapi/update/live";
     public static final String GET_BOX_LIVE_URL = "/eapi/box/pullUrl/{boxSn}";
     public static final String POST_BOX_PHOTOS = "/eapi/box/photos";
diff --git a/ard-work/src/main/java/com/ard/tianyu/model/dto/BoxSettingsDTO.java b/ard-work/src/main/java/com/ard/tianyu/model/dto/BoxSettingsDTO.java
new file mode 100644
index 0000000..5e607d5
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/model/dto/BoxSettingsDTO.java
@@ -0,0 +1,74 @@
+package com.ard.tianyu.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * BoxSettingsDTO
+ *
+ * @author liusuyi
+ * @date 2024年10月22日11:54
+ **/
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class BoxSettingsDTO {
+
+    /**
+     * 云盒SN编号
+     */
+    @JsonProperty("boxSn")
+    private String boxSn;
+
+    /**
+     * 云盒名称(20个字符以内)
+     */
+    @JsonProperty("boxName")
+    private String boxName;
+
+    /**
+     * 照片实时回传开关,0-关闭,1-开启
+     */
+    @JsonProperty("openImgRtp")
+    private String openImgRtp;
+
+    /**
+     * 视频包排序开关,0-关闭,1-开启(排序会增加视频延时)
+     */
+    @JsonProperty("openVideoSort")
+    private String openVideoSort;
+
+    /**
+     * 云端视频存储开关,0-关闭,1-开启
+     */
+    @JsonProperty("openVideoStorage")
+    private String openVideoStorage;
+
+    /**
+     * 视频自动推流开关,0-关闭,1-开启
+     */
+    @JsonProperty("videoAutoPush")
+    private String videoAutoPush;
+
+    /**
+     * 云盒固件自动升级开关,0-关闭,1-开启
+     */
+    @JsonProperty("autoUpdate")
+    private String autoUpdate;
+
+    /**
+     * 云盒与平台失联xx秒后自动返航
+     */
+    @JsonProperty("lostTime")
+    private String lostTime;
+
+    /**
+     * 低电量自动返航开关:0-关闭,1-智能计算,xx-指定的返航电量
+     */
+    @JsonProperty("minHomeBattery")
+    private String minHomeBattery;
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/NettyConfig.java b/ard-work/src/main/java/com/ard/tianyu/netty/NettyConfig.java
new file mode 100644
index 0000000..304caa2
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/NettyConfig.java
@@ -0,0 +1,39 @@
+package com.ard.tianyu.netty;
+
+import com.ard.tianyu.config.TyConfiguration;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+@Slf4j
+public class NettyConfig {
+
+    @Resource
+    private TyConfiguration tianyuConfiguration;
+
+    @Resource
+    private TyNettyTcpClient tyNettyTcpClient;
+
+    @PostConstruct
+    public void createTyConnect() {
+        try {
+            // 将Tcp访问的IP和地址加入到字典
+            Map<String,Object> mapParam =new HashMap<String, Object>();
+            mapParam.put("IP",tianyuConfiguration.getTcpserver());
+            mapParam.put("PORT",tianyuConfiguration.getTcpport());
+
+            //要么从数据库查询信息,要么从配置文件读取信息
+            List<Map<String, Object>> list = new ArrayList<>();
+            list.add(mapParam);
+            tyNettyTcpClient.initAllChannel(list);
+        }catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/TyBoxSession.java b/ard-work/src/main/java/com/ard/tianyu/netty/TyBoxSession.java
new file mode 100644
index 0000000..9173c53
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/TyBoxSession.java
@@ -0,0 +1,21 @@
+package com.ard.tianyu.netty;
+
+import io.netty.channel.ChannelFuture;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class TyBoxSession {
+
+    public String subBoxSn;
+
+    public ChannelFuture future;
+
+    public Boolean isReConnect;
+
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/TyNettyTcpClient.java b/ard-work/src/main/java/com/ard/tianyu/netty/TyNettyTcpClient.java
new file mode 100644
index 0000000..9c8d52d
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/TyNettyTcpClient.java
@@ -0,0 +1,103 @@
+package com.ard.tianyu.netty;
+
+import com.ard.tianyu.netty.handler.ClientHandler;
+import com.ard.tianyu.netty.handler.MessageProtocolDecoder;
+import com.ard.tianyu.netty.handler.MessageProtocolEncoder;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.timeout.IdleStateHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import javax.annotation.Resource;
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Component
+@Slf4j
+public class TyNettyTcpClient {
+
+    public static Map<String, TyBoxSession> channels = new HashMap<>();
+
+    EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
+
+    @Resource
+    ClientHandler clientHandler;
+
+    /**
+     * 初始化Bootstrap
+     */
+    public Bootstrap getBootstrap(EventLoopGroup group) {
+        if (null == group) {
+            group = eventLoopGroup;
+        }
+        Bootstrap bootstrap = new Bootstrap();
+        bootstrap.group(group)
+                .channel(NioSocketChannel.class)
+                .option(ChannelOption.TCP_NODELAY, true)
+                .option(ChannelOption.SO_KEEPALIVE, true)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel socketChannel) throws Exception {
+                        ChannelPipeline pipeline = socketChannel.pipeline();
+                        pipeline.addLast("heart",new IdleStateHandler(0, 3, 0, TimeUnit.SECONDS));
+                        pipeline.addLast("encoder",new MessageProtocolEncoder());
+                        pipeline.addLast("decoder",new MessageProtocolDecoder());
+                        pipeline.addLast("handler",clientHandler);
+                    }
+                });
+        return bootstrap;
+    }
+
+    //   获取所有连接
+    public void initAllChannel(List<Map<String,Object>> portHosts) {
+        for (Map<String, Object> map : portHosts) {
+            String host = (String) map.get("IP");
+            Integer port = (Integer) map.get("PORT");
+            initOneConnection(host,port);
+        }
+    }
+
+    public void initOneConnection(String host,Integer port){
+
+        log.info("调用一次{}:{}",host,port);
+        //判断该账号下,是否有盒子被控制
+        //当前正在被控制的盒子
+        String currentBoxSn = "";
+        if(channels.containsKey(host+"/"+port))
+        {
+            currentBoxSn = channels.get(host+"/"+port).getSubBoxSn();
+            channels.get(host+"/"+port).getFuture().channel().close();
+        }
+
+        //异步连接tcp服务端
+        Bootstrap bootstrap = getBootstrap(null);
+        ChannelFuture future = bootstrap.connect(host,port).addListener((ChannelFuture futureListener) -> {
+            if (futureListener.isSuccess()) {
+                log.info("连接天宇TCP服务端成功:"+host+":"+port);
+            } else {
+                final EventLoop eventLoop = futureListener.channel().eventLoop();
+                eventLoop.schedule(new Runnable() {
+                    @Override
+                    public void run() {
+                        log.info("连接天宇TCP服务端失败ip:{},port:{},正尝试重连...",host,port);
+                        try {
+                            initOneConnection(host,port);
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }, 5, TimeUnit.SECONDS);
+            }
+        });
+
+        //判断是否有盒子被订阅
+        channels.put(host+"/"+port, TyBoxSession.builder().subBoxSn(currentBoxSn).future(future)
+                .isReConnect(!currentBoxSn.equals("")).build());
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/command/BaseCommand.java b/ard-work/src/main/java/com/ard/tianyu/netty/command/BaseCommand.java
new file mode 100644
index 0000000..9254917
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/command/BaseCommand.java
@@ -0,0 +1,14 @@
+package com.ard.tianyu.netty.command;
+
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+
+@Data
+public abstract class BaseCommand {
+    private short dataHeader; // 帧头
+    private short dataLength; // 数据长度
+    private byte cmdCode; // 指令编号
+
+    // 编码方法,子类需要实现
+    public abstract void encode(ByteBuf out);
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatCommand.java b/ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatCommand.java
new file mode 100644
index 0000000..17c3aba
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatCommand.java
@@ -0,0 +1,22 @@
+package com.ard.tianyu.netty.command;
+
+import io.netty.buffer.ByteBuf;
+
+
+public class HeartbeatCommand extends BaseCommand {
+
+    private final long timestamp;
+
+    public HeartbeatCommand(long timestamp) {
+        setDataHeader((short)0x7479);
+        setDataLength((short)0x09);
+        setCmdCode((byte)0x02);
+        this.timestamp = timestamp;
+    }
+    public void encode(ByteBuf out) {
+        out.writeShort(getDataHeader());
+        out.writeShort(getDataLength());
+        out.writeByte(getCmdCode());
+        out.writeLong(timestamp); // 写入时间戳
+    }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatReplyCommand.java b/ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatReplyCommand.java
new file mode 100644
index 0000000..495c275
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/command/HeartbeatReplyCommand.java
@@ -0,0 +1,26 @@
+package com.ard.tianyu.netty.command;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class HeartbeatReplyCommand extends BaseCommand {
+
+    private long timestamp; // 时间戳
+
+    public HeartbeatReplyCommand(long timestamp) {
+        this.timestamp = timestamp;
+        setDataHeader((short) 0x6A77); // 设置帧头
+        setCmdCode((byte) 0x02); // 设置指令编号
+        setDataLength((short) (13)); // 数据长度 (指令编号 + 时间戳)
+    }
+    public void encode(ByteBuf out) {
+        out.writeShort(getDataHeader());
+        out.writeShort(getDataLength());
+        out.writeByte(getCmdCode());
+        out.writeLong(timestamp); // 写入时间戳
+    }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/command/PointFlyCommand.java b/ard-work/src/main/java/com/ard/tianyu/netty/command/PointFlyCommand.java
new file mode 100644
index 0000000..109bdc5
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/command/PointFlyCommand.java
@@ -0,0 +1,30 @@
+package com.ard.tianyu.netty.command;// PointFlyCommand.java
+import io.netty.buffer.ByteBuf;
+
+public class PointFlyCommand extends BaseCommand {
+    private double longitude;
+    private double latitude;
+    private float height;
+    private float speed;
+
+    public PointFlyCommand(double longitude, double latitude, float height, float speed) {
+        this.longitude = longitude;
+        this.latitude = latitude;
+        this.height = height;
+        this.speed = speed;
+        setDataHeader((short) 0x7479);
+        setCmdCode((byte) 0xD1);
+        setDataLength((short) 17); // 数据长度计算
+    }
+
+    @Override
+    public void encode(ByteBuf out) {
+        out.writeShort(getDataHeader());    // 写入帧头
+        out.writeShort(getDataLength());    // 写入数据长度
+        out.writeByte(getCmdCode());        // 写入指令编号
+        out.writeDouble(longitude);         // 写入经度
+        out.writeDouble(latitude);          // 写入纬度
+        out.writeFloat(height);             // 写入高度
+        out.writeFloat(speed);              // 写入速度
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/command/RegisterCommand.java b/ard-work/src/main/java/com/ard/tianyu/netty/command/RegisterCommand.java
new file mode 100644
index 0000000..5d61c98
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/command/RegisterCommand.java
@@ -0,0 +1,24 @@
+package com.ard.tianyu.netty.command;// RegisterMessage.java
+import io.netty.buffer.ByteBuf;
+
+public class RegisterCommand extends BaseCommand {
+    private int userId;
+    private byte[] accessToken;
+
+    public RegisterCommand(int userId, byte[] accessToken) {
+        setDataHeader((short) 0x7479); // 设置帧头
+        setDataLength((short) (5 + accessToken.length)); // 数据长度
+        setCmdCode((byte) 0x01); // 设置命令编号
+        this.userId = userId;
+        this.accessToken = accessToken;
+    }
+
+    @Override
+    public void encode(ByteBuf out) {
+        out.writeShort(getDataHeader());    // 写入帧头
+        out.writeShort(getDataLength());    // 写入数据长度
+        out.writeByte(getCmdCode());        // 写入指令编号
+        out.writeInt(userId);               // 写入用户编号
+        out.writeBytes(accessToken);        // 写入访问令牌
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/handler/ClientHandler.java b/ard-work/src/main/java/com/ard/tianyu/netty/handler/ClientHandler.java
new file mode 100644
index 0000000..00b850f
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/handler/ClientHandler.java
@@ -0,0 +1,89 @@
+package com.ard.tianyu.netty.handler;
+
+import com.ard.tianyu.netty.TyNettyTcpClient;
+import com.ard.tianyu.netty.command.BaseCommand;
+import com.ard.tianyu.netty.command.HeartbeatCommand;
+import com.ard.tianyu.service.ITyService;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+
+
+@Component
+@Slf4j
+@ChannelHandler.Sharable
+public class ClientHandler extends SimpleChannelInboundHandler<BaseCommand> {
+
+    @Autowired
+    ITyService tyService;
+
+    @Resource
+    private TyNettyTcpClient tyNettyTcpClient;
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, BaseCommand command){
+        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
+        String host = ipSocket.getHostString();
+        int port = ipSocket.getPort();
+        //tyService.processMessage(host + "/" + port, msg);
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        //当客户端connect成功后,可以在最后一个handler等待用户输入
+        log.info("连接成功建立!");
+        tyService.registerMessage();
+        super.channelActive(ctx);
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        cause.printStackTrace();
+        ctx.close();
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object obj) {
+        if (obj instanceof IdleStateEvent) {
+            IdleStateEvent event = (IdleStateEvent) obj;
+            if (event.state() == IdleState.READER_IDLE) {
+                log.info("客户端读超时");
+            } else if (event.state() == IdleState.WRITER_IDLE) {
+//                HeartbeatCommand msg = new HeartbeatCommand(System.currentTimeMillis());
+//                log.info("发送⼼跳!");//1652345657305  1652345707631
+//                ctx.writeAndFlush(msg);
+            } else if (event.state() == IdleState.ALL_IDLE) {
+                log.info("客户端所有操作超时");
+            }
+        }
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
+        int port = ipSocket.getPort();
+        String host = ipSocket.getHostString();
+        log.error("与天宇设备" + host + ":" + port + "连接断开!");
+        ctx.channel().eventLoop().schedule(new Runnable() {
+            @Override
+            public void run() {
+                log.error("服务端链接不上,开始重连操作...");
+                try {
+                    tyNettyTcpClient.initOneConnection(host, port);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }, 5, TimeUnit.SECONDS);
+        super.channelInactive(ctx);
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolDecoder.java b/ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolDecoder.java
new file mode 100644
index 0000000..5c27bd5
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolDecoder.java
@@ -0,0 +1,51 @@
+package com.ard.tianyu.netty.handler;// MessageProtocolDecoder.java
+
+import com.ard.tianyu.netty.command.BaseCommand;
+import com.ard.tianyu.netty.command.HeartbeatReplyCommand;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+@Slf4j
+public class MessageProtocolDecoder extends ByteToMessageDecoder {
+    @Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        // 检查可读字节数是否足够
+        if (in.readableBytes() < 4) {
+            return; // 不足以读取头部
+        }
+
+        in.markReaderIndex(); // 标记当前读取位置
+        short dataHead = in.readShort();
+        if(dataHead != 0x6A77){
+            return;
+        }
+        short dataLength = in.readShort();
+
+        if (in.readableBytes() < dataLength) {
+            in.resetReaderIndex(); // 如果不足,重置读取位置
+            return;
+        }
+        byte cmdCode = in.readByte();
+        BaseCommand command = null;
+
+        // 根据 cmdCode 创建相应消息对象
+        if (cmdCode == 0x01) { // 注册回复消息
+            byte success = in.readByte(); // 读取是否成功
+            log.info("注册回复: {}", success == 0x01 ? "成功" : "失败");
+        }
+        if (cmdCode == 0x02) { // 注册回复消息
+            long timestamp = in.readLong(); // 读取时间戳
+            command = new HeartbeatReplyCommand(timestamp); // 创建心跳回复对象
+            log.info("心跳回复: 时间戳 {}", timestamp);
+        }
+        if (command != null) {
+            out.add(command); // 添加解码的命令对象到输出列表
+        } else {
+           // log.warn("无法识别的命令码: {}", cmdCode);
+        }
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolEncoder.java b/ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolEncoder.java
new file mode 100644
index 0000000..8ed5690
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolEncoder.java
@@ -0,0 +1,16 @@
+package com.ard.tianyu.netty.handler;// MessageProtocolEncoder.java
+
+import com.ard.tianyu.netty.command.BaseCommand;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+
+public class MessageProtocolEncoder extends MessageToByteEncoder<BaseCommand> {
+    @Override
+    protected void encode(ChannelHandlerContext ctx, BaseCommand command, ByteBuf out) throws Exception {
+        out.writeShort(command.getDataHeader()); // 写入帧头
+        out.writeShort(command.getDataLength()); // 写入数据长度
+        out.writeByte(command.getCmdCode()); // 写入指令编号
+        command.encode(out); // 编码具体负载
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/ITyService.java b/ard-work/src/main/java/com/ard/tianyu/service/ITyService.java
index c1bccd2..6301887 100644
--- a/ard-work/src/main/java/com/ard/tianyu/service/ITyService.java
+++ b/ard-work/src/main/java/com/ard/tianyu/service/ITyService.java
@@ -2,12 +2,11 @@
 
 import com.ard.tianyu.model.dto.BoxHistoryDTO;
 import com.ard.tianyu.model.dto.BoxPhotosDTO;
+import com.ard.tianyu.model.dto.BoxSettingsDTO;
 import com.ard.tianyu.model.receiver.*;
-import com.dtflys.forest.annotation.Header;
 import com.dtflys.forest.annotation.Var;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * ITyService
@@ -48,4 +47,15 @@
      */
     List<BoxPhotosReceiver> getBoxPhotos(BoxPhotosDTO boxPhotosDTO);
 
+    /**
+     * 修改云盒设置
+     * @return
+     */
+    Boolean updateBoxSettings(BoxSettingsDTO boxSettingsDTO);
+
+    /**
+     * 注册
+     * @return
+     */
+    void registerMessage();
 }
diff --git a/ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java b/ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java
index 8fbd088..3438c14 100644
--- a/ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java
+++ b/ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java
@@ -4,12 +4,23 @@
 import com.ard.tianyu.http.TyClient;
 import com.ard.tianyu.model.dto.BoxHistoryDTO;
 import com.ard.tianyu.model.dto.BoxPhotosDTO;
+import com.ard.tianyu.model.dto.BoxSettingsDTO;
 import com.ard.tianyu.model.receiver.*;
+import com.ard.tianyu.netty.command.BaseCommand;
+import com.ard.tianyu.netty.command.HeartbeatCommand;
+import com.ard.tianyu.netty.command.RegisterCommand;
+import com.ard.tianyu.netty.TyNettyTcpClient;
 import com.ard.tianyu.service.ITyService;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.Collections;
+import java.net.InetSocketAddress;
 import java.util.List;
 
 /**
@@ -18,11 +29,13 @@
  * @author liusuyi
  * @date 2024年10月22日8:37
  **/
+@Slf4j
 @Service
 public class TyServiceImpl implements ITyService {
 
     @Resource
     private TyClient tyClient;
+
     @Resource
     private TyConfiguration tyConfiguration;
 
@@ -81,4 +94,57 @@
         TyHttpCommonReceiver<List<BoxPhotosReceiver>> boxPhotos = tyClient.getBoxPhotos(boxPhotosDTO, tyConfiguration.getAccessToken().convertFromDTO());
         return boxPhotos.getContent();
     }
-}
+
+    /**
+     * 修改云盒设置
+     */
+    @Override
+    public Boolean updateBoxSettings(BoxSettingsDTO boxSettingsDTO) {
+        TyHttpCommonReceiver<String> result = tyClient.updateBoxSettings(boxSettingsDTO, tyConfiguration.getAccessToken().convertFromDTO());
+        return result.getState() == 1;
+    }
+
+
+    @Override
+    public void registerMessage() {
+        AccessTokenReceiver tyToken = tyConfiguration.getAccessToken();
+        RegisterCommand registerMessage = new RegisterCommand(tyToken.getCompanyId(), tyToken.getAccessToken().getBytes());
+        this.sendMessage(tyConfiguration.getIpPort(), registerMessage);
+    }
+
+    public boolean sendMessage(String key, BaseCommand msg) {
+        //将从用户接收的信息发送到天宇 commandCache
+        boolean cmdResult = false;
+        try {
+            ChannelFuture future = TyNettyTcpClient.channels.get(key).getFuture();
+            Channel channel = future.channel();
+            if (future != null && channel.isActive()) {
+                InetSocketAddress ipSocket = (InetSocketAddress) channel.remoteAddress();
+                String host = ipSocket.getHostString();
+                int port = ipSocket.getPort();
+                // 创建 ByteBuf 并编码消息
+                ByteBuf buffer = Unpooled.buffer();
+                msg.encode(buffer); // 确保对消息进行编码
+
+                channel.writeAndFlush(buffer); // 发送编码后的数据
+                cmdResult = true;
+                log.info("向天宇服务器" + host + ":" + port + "发送数据" + ",命令码:" + msg.getCmdCode());
+            } else {
+                log.error("与天宇服务器链接断开,无法发送数据!");
+            }
+        } catch (Exception e) {
+            log.info("Failed to publish the message. {}", "转发信息");
+            e.printStackTrace();
+        } finally {
+            return cmdResult;
+        }
+    }
+
+    @Scheduled(fixedRate = 5000)
+    public void sendHeartBeat() {
+        long timestamp = System.currentTimeMillis();
+        log.debug("定时向天宇发送⼼跳,时间戳{}", timestamp);
+        HeartbeatCommand heartbeatMessage = new HeartbeatCommand(timestamp);
+        this.sendMessage(tyConfiguration.getIpPort(), heartbeatMessage);
+    }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/vtdu/client/MediaClient.java b/ard-work/src/main/java/com/ard/vtdu/client/MediaClient.java
new file mode 100644
index 0000000..a979248
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/client/MediaClient.java
@@ -0,0 +1,123 @@
+package com.ard.vtdu.client;
+
+import com.ard.vtdu.domain.*;
+import com.dtflys.forest.annotation.*;
+
+/**
+ * @Description: mediamtx流媒体客户端
+ * @ClassName: client
+ * @Author: 刘苏义
+ * @Date: 2023年07月06日9:51
+ * @Version: 1.0
+ **/
+@BaseRequest(baseURL = "http://#{mediamtx.host}:9997")
+public interface MediaClient {
+    /**
+     * 增加路径配置
+     */
+    @Post("/v3/config/paths/add/{name}")
+    public String addPath(@Var("name") String name, @JSONBody Conf body);
+
+    /**
+     * 修改路径配置
+     */
+    @Patch("/v3/config/paths/patch/{name}")
+    public String editPath(@Var("name") String name, @JSONBody Conf body);
+
+    /**
+     * 移除路径配置
+     */
+    @Delete("/v3/config/paths/delete/{name}")
+    public String removePath(@Var("name") String name);
+
+    /**
+     * 获取路径配置详情
+     */
+    @Get(url = "/v3/config/paths/get/{name}")
+    public Conf getPathConfig(@Var("name") String name);
+
+    /**
+     * 获取路径配置列表
+     */
+    @Get(url = "/v3/config/paths/list?page={pageNum}&itemsPerPage={pageSize}")
+    public PathConfig getPathConfigs(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize);
+
+    /**
+     * 查询所有路径
+     */
+    @Get("/v3/paths/list?page={pageNum}&itemsPerPage={pageSize}")
+    public Paths paths(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize);
+
+    /**
+     * 查询所有rtsp会话
+     */
+    @Get("/v3/rtspsessions/list?page={pageNum}&itemsPerPage={pageSize}")
+    public RtspSessions rtspsessions(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize);
+
+    /**
+     * 查询所有webrt会话
+     */
+    @Get("/v3/webrtcsessions/list?page={pageNum}&itemsPerPage={pageSize}")
+    public WebrtcSessions webrtcsessions(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize);
+
+    /**
+     * 查询所有rtmp会话
+     */
+    @Get("/v3/rtmpconns/list?page={pageNum}&itemsPerPage={pageSize}")
+    public RtmpSessions rtmpsessions(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize);
+
+    /**
+     * 按sessionId查询rtsp会话
+     */
+    @Get("/v3/rtspsessions/get/{sessionId}")
+    public RtspSession getRtspsessionById(@Var("sessionId") String sessionId);
+
+    /**
+     * 按sessionId查询webrtc会话
+     */
+    @Get("/v3/webrtcsessions/get/{sessionId}")
+    public WebrtcSession getWebrtcsessionById(@Var("sessionId") String sessionId);
+
+    /**
+     * 按sessionId查询rtmp会话
+     */
+    @Get("/v3/rtmpconns/get/{sessionId}")
+    public RtmpSession getRtmpsessionById(@Var("sessionId") String sessionId);
+
+    /**
+     * 按sessionId删除rtsp会话
+     */
+    @Post("/v3/rtspsessions/kick/{sessionId}")
+    public String kickRtspSessions(@Var("sessionId") String sessionId);
+
+    /**
+     * 按sessionId删除rtmp连接
+     */
+    @Post("/v3/rtmpconns/kick/{sessionId}")
+    public String kickRtmpSessions(@Var("sessionId") String sessionId);
+
+    /**
+     * 按sessionId删除webrtc会话
+     */
+    @Post("/v3/webrtcsessions/kick/{sessionId}")
+    public String kickWebrtcSessions(@Var("sessionId") String sessionId);
+
+    /**
+     * 录像分段列表
+     */
+    @Get("/v3/recordings/list/")
+    public Paths recordingList(@Query("pageNum") Integer pageNum, @Query("pageSize") Integer pageSize);
+
+    /**
+     * 通过路径名获取录像分段列表
+     */
+    @Get("/v3/recordings/get/{name}")
+    public Item recordingListByName(@Var("name") String name);
+
+    /**
+     * 通过路径名和分段录像开始时间删除录像分段
+     */
+    @Delete("/v3/recordings/deletesegment")
+    public String deleteSegment(@Query("path") String name, @Query("start") String start);
+
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/controller/MediaController.java b/ard-work/src/main/java/com/ard/vtdu/controller/MediaController.java
new file mode 100644
index 0000000..7653063
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/controller/MediaController.java
@@ -0,0 +1,216 @@
+package com.ard.vtdu.controller;
+import com.ard.common.core.controller.BaseController;
+import com.ard.common.core.domain.AjaxResult;
+import com.ard.common.core.page.TableDataInfo;
+import com.ard.vtdu.domain.ArdVtdu;
+import com.ard.vtdu.domain.Conf;
+import com.ard.vtdu.service.IMediaService;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import java.util.Arrays;
+
+/**
+ * @Description: mediaMTX流媒体业务
+ * @ClassName: controller
+ * @Author: 刘苏义
+ * @Date: 2023年07月13日9:26
+ * @Version: 1.0
+ **/
+@RestController
+@Api(tags = "mediaMTX接口")
+@RequestMapping("/mediamtx/media")
+public class MediaController extends BaseController {
+
+    @Resource
+    private IMediaService mediaService;
+
+    /**
+     * 增加通道
+     */
+    @PostMapping()
+    @ApiOperation("增加通道")
+    @PreAuthorize("@ss.hasPermi('mediamtx:media:add')")
+    @ApiOperationSupport(includeParameters = {"ardVtdu.name", "ardVtdu.rtspSource", "ardVtdu.mode", "ardVtdu.isCode", "ardVtdu.isRecord"}, order = 1)
+    public AjaxResult addPath(@RequestBody ArdVtdu ardVtdu) {
+        if (StringUtils.isEmpty(ardVtdu.getName())) {
+            return AjaxResult.error("通道名称不能为空");
+        }
+        if (StringUtils.isEmpty(ardVtdu.getRtspSource())) {
+            return AjaxResult.error("rtsp地址不能为空");
+        }
+        return AjaxResult.success(mediaService.addPath(ardVtdu));
+    }
+
+
+    /**
+     * 修改通道
+     */
+    @ApiOperation("修改通道")
+    @ApiOperationSupport(includeParameters = {"ardVtdu.name", "ardVtdu.rtspSource", "ardVtdu.mode", "ardVtdu.isCode", "ardVtdu.isRecord"}, order = 2)
+    @PreAuthorize("@ss.hasPermi('mediamtx:media:edit')")
+    @PutMapping
+    public AjaxResult edit(@RequestBody ArdVtdu ardVtdu) {
+        return AjaxResult.success(mediaService.editPath(ardVtdu));
+    }
+
+    /**
+     * 移除通道
+     */
+    @DeleteMapping("/path/{names}")
+    @PreAuthorize("@ss.hasPermi('mediamtx:media:remove')")
+    @ApiOperation("移除通道")
+    @ApiOperationSupport(order = 3)
+
+    public AjaxResult removePath(@PathVariable String[] names) {
+        mediaService.removePath(names);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 移除Webrtc拉流
+     */
+    @ApiOperation("移除Webrtc拉流")
+    @ApiOperationSupport(order = 4)
+    @DeleteMapping("/webrtc/{sessionIds}")
+    public AjaxResult removeWebrtcPullStreamSession(@PathVariable String[] sessionIds) {
+        Arrays.stream(sessionIds).forEach(sessionId -> mediaService.kickWebrtcSession(sessionId));
+        return AjaxResult.success();
+    }
+
+    /**
+     * 移除Rtmp拉流
+     */
+    @ApiOperation("移除Rtmp拉流")
+    @ApiOperationSupport(order = 4)
+    @DeleteMapping("/rtmp/{sessionIds}")
+    public AjaxResult removeRtmpPullStreamSession(@PathVariable String[] sessionIds) {
+        Arrays.stream(sessionIds).forEach(sessionId -> mediaService.kickRtmpSession(sessionId));
+        return AjaxResult.success();
+    }
+
+    /**
+     * 移除Rtsp拉流
+     */
+    @ApiOperation("移除Rtsp拉流")
+    @ApiOperationSupport(order = 4)
+    @DeleteMapping("/rtsp/{sessionIds}")
+    public AjaxResult removeRtspPullStreamSession(@PathVariable String[] sessionIds) {
+        Arrays.stream(sessionIds).forEach(sessionId -> mediaService.kickRtspSession(sessionId));
+        return AjaxResult.success();
+    }
+
+
+    /**
+     * 获取当前通道列表
+     */
+    @GetMapping("/path/list")
+    @ApiOperation("获取当前通道列表")
+    @ApiOperationSupport(order = 5)
+    public TableDataInfo getPaths(Integer pageNum, Integer pageSize) {
+        startPage();
+        return getDataTable(mediaService.paths(pageNum, pageSize));
+    }
+
+    /**
+     * 按ID查询Webrtc拉流详情
+     */
+    @GetMapping("/getWebrtcSessionById")
+    @ApiOperation("按ID查询Webrtc拉流详情")
+    public AjaxResult getWebrtcSessionById(String sessionId) {
+        return AjaxResult.success(mediaService.getWebrtcSessionById(sessionId));
+    }
+
+    /**
+     * 获取推流列表
+     */
+
+    @PreAuthorize("@ss.hasPermi('mediamtx:media:list')")
+    @GetMapping("/pushList")
+    @ApiOperation("获取推流列表")
+    @ApiOperationSupport(order = 6)
+    public TableDataInfo getPushStreamList(Integer pageNum, Integer pageSize) {
+        TableDataInfo tableDataInfo = mediaService.getPushStreamList(pageNum, pageSize);
+        return tableDataInfo;
+    }
+
+    /**
+     * 获取WebRtc拉流列表
+     */
+
+    @GetMapping("/getPullWebrtcStreamList")
+    @ApiOperation("获取WebRtc拉流列表")
+    @ApiOperationSupport(order = 7)
+    public TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize) {
+        return mediaService.getPullWebrtcStreamList(pageNum, pageSize);
+    }
+
+    /**
+     * 获取rtsp拉流列表
+     */
+
+    @GetMapping("/getPullRtspStreamList")
+    @ApiOperation("获取rtsp拉流列表")
+    @ApiOperationSupport(order = 8)
+    public TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize) {
+        return mediaService.getPullRtspStreamList(pageNum, pageSize);
+
+    }
+
+    /**
+     * 获取rtmp拉流列表
+     */
+    @GetMapping("/getPullRtmpStreamList")
+    @ApiOperation("获取rtmp拉流列表")
+    @ApiOperationSupport(order = 9)
+    public TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize) {
+        return mediaService.getPullRtmpStreamList(pageNum, pageSize);
+    }
+
+    //配置录像
+    @PostMapping("/setRecord")
+    @ApiOperation("配置录像")
+    @ApiOperationSupport(includeParameters = {"conf.name", "conf.sourceOnDemand", "conf.record"})
+    public AjaxResult setRecord(@RequestBody Conf conf) {
+        return AjaxResult.success(mediaService.setRecord(conf));
+    }
+
+    //获取全部录像分段列表
+    @GetMapping("/getRecordingList")
+    @ApiOperation("获取全部录像分段列表")
+    public AjaxResult getRecordingList(Integer pageNum, Integer pageSize) {
+        return AjaxResult.success(mediaService.recordingList(pageNum, pageSize));
+    }
+
+    //通过路径名获取录像分段列表
+    @GetMapping("/getRecordingListByName/{name}")
+    @ApiOperation("通过路径名获取录像分段列表")
+    public AjaxResult getRecordingListByName(@PathVariable String name) {
+        return AjaxResult.success(mediaService.recordingListByName(name));
+    }
+
+    //删除录像
+    @DeleteMapping("/deleteRecording")
+    @ApiOperation("删除录像")
+    public AjaxResult deleteRecording(String name, String start) {
+        return AjaxResult.success(mediaService.deleteSegment(name, start));
+    }
+
+    //获取通道配置
+    @GetMapping("/{name}")
+    @ApiOperation("获取通道配置")
+    public AjaxResult getPathConfig(@PathVariable String name) {
+        return AjaxResult.success(mediaService.getPathConfig(name));
+    }
+
+    //获取所有通道配置列表
+    @GetMapping("/getRecordConfList")
+    @ApiOperation("获取所有通道配置列表")
+    public AjaxResult getRecordConfList(Integer pageNum, Integer pageSize) {
+        return AjaxResult.success(mediaService.getPathConfigs(pageNum, pageSize));
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/ArdVtdu.java b/ard-work/src/main/java/com/ard/vtdu/domain/ArdVtdu.java
new file mode 100644
index 0000000..82f7208
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/ArdVtdu.java
@@ -0,0 +1,74 @@
+package com.ard.vtdu.domain;
+
+import com.ard.common.annotation.Excel;
+import com.ard.common.core.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 流媒体管理对象 vtdu
+ *
+ * @author ard
+ * @date 2023-08-29
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ArdVtdu extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键名称
+     */
+    @Excel(name = "名称")
+    private String name;
+
+    /**
+     * rtsp源地址
+     */
+    @Excel(name = "rtsp源地址")
+    private String rtspSource;
+
+    /**
+     * 是否转码
+     * 0:不转码 1:转码
+     */
+    @Excel(name = "是否转码")
+    @Builder.Default
+    private Integer isCode = 0;
+
+    /**
+     * 编解码模式
+     * 0:cpu 1:gpu
+     */
+    @Excel(name = "编解码模式")
+    @Builder.Default
+    private Integer mode = 0;
+    /**
+     * 是否录像
+     * 0:不录像 1:录像
+     */
+    @Excel(name = "是否录像")
+    @Builder.Default
+    private Integer isRecord = 0;
+    /**
+     * rtsp播放地址
+     */
+    String rtspUrl;
+    /**
+     * rtmp播放地址
+     */
+    String rtmpUrl;
+    /**
+     * webrtc播放地址
+     */
+    String webrtcUrl;
+    /**
+     * 相机ID
+     */
+    String cameraId;
+
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/Conf.java b/ard-work/src/main/java/com/ard/vtdu/domain/Conf.java
new file mode 100644
index 0000000..edfebb7
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/Conf.java
@@ -0,0 +1,46 @@
+package com.ard.vtdu.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Description:
+ * @ClassName: MediaInfo
+ * @Author: 刘苏义
+ * @Date: 2023年07月13日9:32
+ * @Version: 1.0
+ **/
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class Conf {
+    private String name;
+    private String source;
+    //是否按需发布
+    private Boolean sourceOnDemand;
+    private String sourceProtocol;
+    private Integer maxReaders;
+    private String runOnDemand;
+    private Boolean runOnDemandRestart;
+    private String runOnDemandStartTimeout;
+    private String runOnDemandCloseAfter;
+    private String runOnInit;
+    private Boolean runOnInitRestart;
+    //录像
+    @Builder.Default
+    private Boolean record = false;
+    @Builder.Default
+    private String recordPath = "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f";
+    @Builder.Default
+    private String recordFormat = "fmp4";
+    @Builder.Default
+    private String recordPartDuration = "1s";
+    @Builder.Default
+    private String recordSegmentDuration = "0h5m0s";
+    @Builder.Default
+    private String recordDeleteAfter = "24h0m0s";
+
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/Item.java b/ard-work/src/main/java/com/ard/vtdu/domain/Item.java
new file mode 100644
index 0000000..1577f23
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/Item.java
@@ -0,0 +1,29 @@
+package com.ard.vtdu.domain;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Description:
+ * @ClassName: Items
+ * @Author: 刘苏义
+ * @Date: 2023年07月13日10:23
+ * @Version: 1.0
+ **/
+@Data
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class Item {
+    private String name;
+    private String confName;
+    private Source source;
+    private List<Readers> readers;
+    private List<String> tracks;
+    private Long bytesReceived;
+    private Long bytesSent;
+    private Date readyTime;
+    private List<Segment> segments;
+}
+
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/PathConfig.java b/ard-work/src/main/java/com/ard/vtdu/domain/PathConfig.java
new file mode 100644
index 0000000..4592025
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/PathConfig.java
@@ -0,0 +1,20 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * ConfigPath
+ *
+ * @author liusuyi
+ * @date 2024年09月26日10:04
+ **/
+@Data
+public class PathConfig {
+    private int itemCount;
+
+    private int pageCount;
+
+    private List<Conf> items;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/Paths.java b/ard-work/src/main/java/com/ard/vtdu/domain/Paths.java
new file mode 100644
index 0000000..d8e0c2c
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/Paths.java
@@ -0,0 +1,23 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Description: returns all paths.
+ * @ClassName: Paths
+ * @Author: 刘苏义
+ * @Date: 2023年07月13日10:06
+ * @Version: 1.0
+ **/
+@Data
+public class Paths {
+
+    private int itemCount;
+
+    private int pageCount;
+
+    private List<Item> items;
+
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/Readers.java b/ard-work/src/main/java/com/ard/vtdu/domain/Readers.java
new file mode 100644
index 0000000..39345ba
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/Readers.java
@@ -0,0 +1,16 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+/**
+ * @ClassName Readers
+ * @Description:
+ * @Author 刘苏义
+ * @Date 2023/7/14 21:04
+ * @Version 1.0
+ */
+@Data
+public class Readers {
+    private String type;
+    private String id;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/RtmpSession.java b/ard-work/src/main/java/com/ard/vtdu/domain/RtmpSession.java
new file mode 100644
index 0000000..4be7dad
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/RtmpSession.java
@@ -0,0 +1,27 @@
+package com.ard.vtdu.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @Description:
+ * @ClassName: WebrtcSession
+ * @Author: 刘苏义
+ * @Date: 2023年08月26日10:16:21
+ **/
+@Data
+public class RtmpSession {
+    private String path;
+    private String id;
+    private Date created;
+    private String remoteAddr;
+    private String state;
+    private long bytesReceived;
+    private long bytesSent;
+    private String upStream;
+    private String downStream;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
+    private Date beginTime;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/RtmpSessions.java b/ard-work/src/main/java/com/ard/vtdu/domain/RtmpSessions.java
new file mode 100644
index 0000000..b9fe9af
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/RtmpSessions.java
@@ -0,0 +1,21 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @ClassName:Sessions
+ * @Description:
+ * @Author:ard
+ * @Date:2024年07月12日13:34
+ * @Version:1.0
+ **/
+@Data
+public class RtmpSessions {
+    private int itemCount;
+
+    private int pageCount;
+
+    private List<RtmpSession> items;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/RtspSession.java b/ard-work/src/main/java/com/ard/vtdu/domain/RtspSession.java
new file mode 100644
index 0000000..9c68e23
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/RtspSession.java
@@ -0,0 +1,29 @@
+package com.ard.vtdu.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @ClassName RtspSession
+ * @Description:
+ * @Author 刘苏义
+ * @Date 2023/7/14 21:19
+ * @Version 1.0
+ */
+@Data
+public class RtspSession {
+    private String id;
+    private String path;
+    private Date created;
+    private String remoteAddr;
+    private String state;
+    private String transport;
+    private long bytesReceived;
+    private long bytesSent;
+    private String upStream;
+    private String downStream;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date beginTime;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/RtspSessions.java b/ard-work/src/main/java/com/ard/vtdu/domain/RtspSessions.java
new file mode 100644
index 0000000..728b773
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/RtspSessions.java
@@ -0,0 +1,21 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @ClassName:Sessions
+ * @Description:
+ * @Author:ard
+ * @Date:2024年07月12日13:34
+ * @Version:1.0
+ **/
+@Data
+public class RtspSessions {
+    private int itemCount;
+
+    private int pageCount;
+
+    private List<RtspSession> items;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/Segment.java b/ard-work/src/main/java/com/ard/vtdu/domain/Segment.java
new file mode 100644
index 0000000..250ccb2
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/Segment.java
@@ -0,0 +1,22 @@
+package com.ard.vtdu.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * Segment
+ *
+ * @author liusuyi
+ * @date 2024年09月24日13:26
+ **/
+@Data
+public class Segment {
+    private String start;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
+    private Date startTime;
+
+
+}
+
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/Source.java b/ard-work/src/main/java/com/ard/vtdu/domain/Source.java
new file mode 100644
index 0000000..0e9b6bb
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/Source.java
@@ -0,0 +1,16 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+/**
+ * @Description:
+ * @ClassName: Source
+ * @Author: 刘苏义
+ * @Date: 2023年07月13日10:24
+ * @Version: 1.0
+ **/
+@Data
+public class Source {
+    private String type;
+    private String id;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/StreamInfo.java b/ard-work/src/main/java/com/ard/vtdu/domain/StreamInfo.java
new file mode 100644
index 0000000..1a7ab46
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/StreamInfo.java
@@ -0,0 +1,85 @@
+package com.ard.vtdu.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @Description:流信息
+ * @ClassName: PushStreamInfo
+ * @Author: 刘苏义
+ * @Date: 2023年07月15日8:40:37
+ * @Version: 1.0
+ **/
+@Data
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class StreamInfo {
+    /**
+     * 会话ID
+     */
+    String id;
+    /**
+     * 路径名称
+     */
+    String name;
+    /**
+     * 源rtsp地址
+     */
+    String rtspSource;
+    /**
+     * rtsp播放地址
+     */
+    String rtspUrl;
+    /**
+     * rtmp播放地址
+     */
+    String rtmpUrl;
+    /**
+     * webrtc播放地址
+     */
+    String webrtcUrl;
+    /**
+     * 传输方式
+     */
+    String protocol;
+    /**
+     * 上行流量
+     */
+    String upTraffic;
+    /**
+     * 下行流量
+     */
+    String downTraffic;
+    /**
+     * 拉流数量
+     */
+    Integer num;
+    /**
+     * 开始时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    Date beginTime;
+    /**
+     * 远程主机
+     */
+    String remoteAddr;
+
+    /**
+     * 解码模式
+     * 0-gpu硬解码 1-cpu软解码
+     */
+    Integer mode;
+
+    /**
+     * 是否转码
+     * 0-不转码 1-转码
+     */
+    Integer isCode;
+    /**
+     * 媒体类型(webRTCSession/rtspSession)
+     */
+    String sessionType;
+
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSession.java b/ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSession.java
new file mode 100644
index 0000000..d6f9ff9
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSession.java
@@ -0,0 +1,31 @@
+package com.ard.vtdu.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @Description:
+ * @ClassName: WebrtcSession
+ * @Author: 刘苏义
+ * @Date: 2023年08月26日10:16:21
+ **/
+@Data
+public class WebrtcSession {
+    private String id;
+    private String path;
+    private Date created;
+    private String remoteAddr;
+    private String peerConnectionEstablished;
+    private String localCandidate;
+    private String remoteCandidate;
+    private String state;
+    private long bytesReceived;
+    private long bytesSent;
+    private String upStream;
+    private String downStream;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date beginTime;
+}
+
diff --git a/ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSessions.java b/ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSessions.java
new file mode 100644
index 0000000..21ef5d0
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/domain/WebrtcSessions.java
@@ -0,0 +1,21 @@
+package com.ard.vtdu.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @ClassName:Sessions
+ * @Description:
+ * @Author:ard
+ * @Date:2024年07月12日13:34
+ * @Version:1.0
+ **/
+@Data
+public class WebrtcSessions {
+    private int itemCount;
+
+    private int pageCount;
+
+    private List<WebrtcSession> items;
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/service/IMediaService.java b/ard-work/src/main/java/com/ard/vtdu/service/IMediaService.java
new file mode 100644
index 0000000..edd2e07
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/service/IMediaService.java
@@ -0,0 +1,69 @@
+package com.ard.vtdu.service;
+
+import com.ard.common.core.page.TableDataInfo;
+import com.ard.vtdu.domain.*;
+import java.util.List;
+
+public interface IMediaService {
+    /**
+     * 增加路径
+     */
+    ArdVtdu addPath(ArdVtdu ardVtdu);
+
+    /**
+     * 修改路径
+     */
+    ArdVtdu editPath(ArdVtdu ardVtdu);
+
+    //获取通道配置
+    Conf getPathConfig(String name);
+
+    //获取通道配置列表
+    PathConfig getPathConfigs(Integer pageNum, Integer pageSize);
+
+    void removePath(String[] names);
+
+    void removePath(String name);
+
+    List<StreamInfo> paths(Integer pageNum, Integer pageSize);
+
+    List<String> getNameList();
+
+    boolean checkNameExist(String name);
+
+    RtspSession getRtspSessionById(String sessionId);
+
+    WebrtcSession getWebrtcSessionById(String sessionId);
+
+    RtmpSession getRtmpSessionById(String sessionId);
+
+    TableDataInfo getPushStreamList(Integer pageNum, Integer pageSize);
+
+    TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize);
+
+    TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize);
+
+    TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize);
+
+    Boolean kickRtspSession(String sessionId);
+
+    Boolean kickRtmpSession(String sessionId);
+
+    Boolean kickWebrtcSession(String sessionId);
+
+    void clearAll();
+
+    //开启录像
+    String setRecord(Conf conf);
+
+    //获取全部录像分段列表
+    Paths recordingList(Integer pageNum, Integer pageSize);
+
+    //通过路径名获取录像分段列表
+    Item recordingListByName(String name);
+
+    //通过路径名和分段录像开始时间删除录像分段
+    String deleteSegment(String name, String start);
+
+
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/vtdu/service/impl/MediaServiceImpl.java b/ard-work/src/main/java/com/ard/vtdu/service/impl/MediaServiceImpl.java
new file mode 100644
index 0000000..031509c
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/service/impl/MediaServiceImpl.java
@@ -0,0 +1,669 @@
+package com.ard.vtdu.service.impl;
+
+import com.ard.common.constant.HttpStatus;
+import com.ard.common.core.page.TableDataInfo;
+import com.ard.tianyu.model.receiver.BoxReceiver;
+import com.ard.tianyu.model.receiver.PullStreamUrlReceiver;
+import com.ard.tianyu.service.ITyService;
+import com.ard.vtdu.client.MediaClient;
+import com.ard.vtdu.domain.*;
+import com.ard.vtdu.service.IMediaService;
+import com.ard.vtdu.util.ArdTool;
+import com.dtflys.forest.exceptions.ForestRuntimeException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: 流媒体业务
+ * @ClassName: MediaService
+ * @Author: 刘苏义
+ * @Date: 2023年07月13日9:28
+ * @Version: 1.0
+ **/
+@Service
+@Slf4j
+public class MediaServiceImpl implements IMediaService {
+
+    @Resource
+    private MediaClient mediaClient;
+    @Resource
+    private ITyService tyService;
+    @Value("${mediamtx.host}")
+    private String mediamtxHost;
+
+
+    @PostConstruct
+    public void initMediaMTX() {
+        //清空mediamtx流媒体数据
+        clearAll();
+    }
+
+
+    /**
+     * @return
+     * @Author 刘苏义
+     * @Description 添加流媒体
+     * name 相机ID
+     * sourceUrl rtsp推流地址
+     * isCode 0-不转码 1-转码
+     * mode 1-gpu硬解码 0-cpu软解码
+     * @Date 2024/7/3 8:45
+     * @Param
+     */
+    @Override
+    public ArdVtdu addPath(ArdVtdu ardVtdu) {
+        try {
+            String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + ardVtdu.getName();
+            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + ardVtdu.getName();
+            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + ardVtdu.getName();
+
+            Conf conf = new Conf();
+            boolean isCodeEnabled = ardVtdu.getIsCode().equals(1);
+            boolean isModeEabled = ardVtdu.getMode().equals(1);
+            boolean isRecordEnabled = ardVtdu.getIsRecord().equals(1);
+            if (isCodeEnabled) {
+                conf.setSource("publisher");
+                //默认软解码
+                String cmd = "ffmpeg -rtsp_transport tcp -i " + ardVtdu.getRtspSource() + " -vcodec libx264 -preset ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+                if (isModeEabled) {//硬解码
+                    cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid  -rtsp_transport tcp  -i " + ardVtdu.getRtspSource() + " -c:v h264_nvenc  -r 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -bf 0 -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+                }
+                if (isRecordEnabled) {
+                    //录像
+                    conf.setRunOnInit(cmd);
+                    conf.setRunOnInitRestart(true);
+                    conf.setRunOnDemand("");
+                    conf.setRecord(true);
+                } else {
+                    conf.setRunOnDemand(cmd);
+                    conf.setRunOnDemandRestart(true);
+                    conf.setRunOnDemandCloseAfter("5s");
+                    conf.setRecord(false);
+                    conf.setRunOnInit("");
+                }
+            } else {
+                conf.setSource(ardVtdu.getRtspSource());
+                conf.setRunOnDemandRestart(false);
+                conf.setRunOnDemandCloseAfter("5s");
+                conf.setSourceOnDemand(!isRecordEnabled);
+                conf.setRecord(isRecordEnabled);
+            }
+            conf.setMaxReaders(100);
+            conf.setSourceProtocol("tcp");
+
+            if (!checkNameExist(ardVtdu.getName())) {
+                mediaClient.addPath(ardVtdu.getName(), conf);
+            } else {
+                mediaClient.editPath(ardVtdu.getName(), conf);
+            }
+            ardVtdu.setCameraId(ardVtdu.getName().split("_")[0]);
+            ardVtdu.setRtspUrl(rtspUrl);
+            ardVtdu.setRtmpUrl(rtmpUrl);
+            ardVtdu.setWebrtcUrl(webrtcUrl);
+            return ardVtdu;
+        } catch (ForestRuntimeException ex) {
+            log.error("添加流媒体异常:" + ex.getMessage());
+        }
+        return null;
+    }
+
+    @Override
+    public ArdVtdu editPath(ArdVtdu ardVtdu) {
+        try {
+            String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + ardVtdu.getName();
+            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + ardVtdu.getName();
+            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + ardVtdu.getName();
+
+            Conf conf = new Conf();
+            boolean isCodeEnabled = ardVtdu.getIsCode().equals(1);
+            boolean isModeEabled = ardVtdu.getMode().equals(1);
+            boolean isRecordEnabled = ardVtdu.getIsRecord().equals(1);
+            if (isCodeEnabled) {
+                conf.setSource("publisher");
+                //默认软解码
+                String cmd = "ffmpeg -rtsp_transport tcp -i " + ardVtdu.getRtspSource() + " -vcodec libx264 -preset ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+                if (isModeEabled) {//硬解码
+                    cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid  -rtsp_transport tcp  -i " + ardVtdu.getRtspSource() + " -c:v h264_nvenc  -r 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -bf 0 -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
+                }
+                if (isRecordEnabled) {
+                    //录像
+                    conf.setRunOnInit(cmd);
+                    conf.setRunOnInitRestart(true);
+                    conf.setRunOnDemand("");
+                    conf.setRecord(true);
+                } else {
+                    conf.setRunOnDemand(cmd);
+                    conf.setRunOnDemandRestart(true);
+                    conf.setRunOnDemandCloseAfter("5s");
+                    conf.setRecord(false);
+                    conf.setRunOnInit("");
+                }
+            } else {
+                conf.setSource(ardVtdu.getRtspSource());
+                conf.setRunOnDemand("");
+                conf.setRunOnDemandRestart(false);
+                conf.setRunOnDemandCloseAfter("5s");
+                conf.setSourceOnDemand(!isRecordEnabled);
+                conf.setRecord(isRecordEnabled);
+            }
+            conf.setMaxReaders(100);
+            conf.setSourceProtocol("tcp");
+
+            if (checkNameExist(ardVtdu.getName())) {
+                mediaClient.editPath(ardVtdu.getName(), conf);
+            }
+            ardVtdu.setRtspUrl(rtspUrl);
+            ardVtdu.setRtmpUrl(rtmpUrl);
+            ardVtdu.setWebrtcUrl(webrtcUrl);
+            return ardVtdu;
+        } catch (ForestRuntimeException ex) {
+            log.error("修改流媒体异常:" + ex.getMessage());
+        }
+        return null;
+    }
+
+    @Override
+    public void removePath(String[] names) {
+        try {
+            for (String name : names) {
+                if (checkNameExist(name)) {
+                    mediaClient.removePath(name);
+                }
+            }
+        } catch (Exception ex) {
+            log.error("批量移除流媒体异常:" + ex.getMessage());
+        }
+    }
+
+    @Override
+    public void removePath(String name) {
+        try {
+            if (checkNameExist(name)) {
+                mediaClient.removePath(name);
+            }
+        } catch (ForestRuntimeException ex) {
+            log.error("移除流媒体异常:" + ex.getMessage());
+        }
+    }
+
+    @Override
+    public List<StreamInfo> paths(Integer pageNum, Integer pageSize) {
+        Paths paths = mediaClient.paths(pageNum - 1, pageSize);
+        List<Item> items = paths.getItems();
+        List<StreamInfo> pathInfoList = new ArrayList<>();
+        for (Item item : items) {
+            StreamInfo info = new StreamInfo();
+            //ID
+            String name = item.getName();
+            info.setName(name);
+
+            Conf conf = mediaClient.getPathConfig(name);
+            String runOn = "";
+            if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
+                runOn = conf.getRunOnDemand();
+            }
+            //RTSP源地址
+            Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
+            if (matcher.find()) {
+                info.setRtspSource(matcher.group());
+                info.setIsCode(1);
+            } else {
+                info.setRtspSource(conf.getSource());
+                info.setIsCode(0);
+            }
+            //传输协议
+            matcher = Pattern.compile("-rtsp_transport\\s+(\\w+)").matcher(runOn);
+            if (matcher.find()) {
+                info.setProtocol(matcher.group(1));
+            }
+
+            pathInfoList.add(info);
+        }
+        return pathInfoList;
+    }
+
+    @Override
+    public RtspSession getRtspSessionById(String sessionId) {
+        RtspSession rtspSession = mediaClient.getRtspsessionById(sessionId);
+        return rtspSession;
+    }
+
+    @Override
+    public WebrtcSession getWebrtcSessionById(String sessionId) {
+        WebrtcSession webrtcSession = mediaClient.getWebrtcsessionById(sessionId);
+        return webrtcSession;
+    }
+
+    @Override
+    public RtmpSession getRtmpSessionById(String sessionId) {
+        RtmpSession rtmpSession = mediaClient.getRtmpsessionById(sessionId);
+        return rtmpSession;
+    }
+
+    /**
+     * 获取推流列表
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public TableDataInfo getPushStreamList(Integer pageNum, Integer pageSize) {
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        List<StreamInfo> PushStreamInfoList = new ArrayList<>();
+        Paths paths = mediaClient.paths(pageNum - 1, pageSize);
+        int itemCount = paths.getItemCount();
+        tableDataInfo.setTotal(itemCount);
+        List<Item> items = paths.getItems();
+        for (Item item : items) {
+            StreamInfo info = new StreamInfo();
+            //ID
+            String name = item.getName();
+            info.setName(name);
+            Conf conf = mediaClient.getPathConfig(name);
+            //RTMP播放地址
+            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
+            info.setRtmpUrl(rtmpUrl);
+            //RTSP播放地址
+            String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
+            info.setRtspUrl(rtspUrl);
+            //WEBRTC播放地址
+            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
+            info.setWebrtcUrl(webrtcUrl);
+            Source source = item.getSource();
+            if (source == null || source.getId().equals("")) {
+                //会话ID
+                info.setId("0");
+                //下行流量
+                long bytesReceived = item.getBytesReceived();
+                String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
+                info.setDownTraffic(formatReceivedSize);
+                //上行流量
+                long bytesSent = item.getBytesSent();
+                String formatSentdSize = ArdTool.formatFileSize(bytesSent);
+                info.setUpTraffic(formatSentdSize);
+                info.setBeginTime(item.getReadyTime());
+            } else {
+                RtspSession rtspSession = getRtspSessionById(source.getId());
+                //会话ID
+                info.setId(rtspSession.getId());
+                //开始推流时间
+                info.setBeginTime(rtspSession.getCreated());
+                //上行流量
+                long bytesReceived = rtspSession.getBytesReceived();
+                String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
+                info.setUpTraffic(formatReceivedSize);
+                //下行流量
+                long bytesSent = rtspSession.getBytesSent();
+                String formatSentSize = ArdTool.formatFileSize(bytesSent);
+                info.setDownTraffic(formatSentSize);
+                //推流服务器
+                info.setRemoteAddr(rtspSession.getRemoteAddr());
+            }
+            //RTSP源地址
+            String runOn = "";
+            if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
+                runOn = conf.getRunOnDemand();
+            }
+            Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
+            if (matcher.find()) {
+                info.setRtspSource(matcher.group());
+            } else {
+                info.setRtspSource(conf.getSource());
+            }
+            //传输协议
+            info.setProtocol(conf.getSourceProtocol());
+            //拉流数量
+            List<Readers> readers = item.getReaders();
+            info.setNum(readers.size());
+
+            PushStreamInfoList.add(info);
+        }
+        tableDataInfo.setRows(PushStreamInfoList);
+        tableDataInfo.setCode(HttpStatus.SUCCESS);
+        tableDataInfo.setMsg("查询成功");
+        return tableDataInfo;
+    }
+
+    /**
+     * 获取webrtc拉流列表
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize) {
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        WebrtcSessions WebrtcSessions = mediaClient.webrtcsessions(pageNum - 1, pageSize);
+        List<WebrtcSession> items = WebrtcSessions.getItems();
+        items.forEach(
+                webrtcSession -> {
+                    webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived()));
+                    webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent()));
+                    webrtcSession.setBeginTime(webrtcSession.getCreated());
+                }
+        );
+        tableDataInfo.setTotal(WebrtcSessions.getItemCount());
+        tableDataInfo.setRows(items);
+        tableDataInfo.setCode(HttpStatus.SUCCESS);
+        tableDataInfo.setMsg("查询成功");
+        return tableDataInfo;
+    }
+
+    /**
+     * 获取rtmp拉流列表
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize) {
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        RtmpSessions rtmpSessions = mediaClient.rtmpsessions(pageNum - 1, pageSize);
+        List<RtmpSession> items = rtmpSessions.getItems();
+        items.stream().forEach(
+                webrtcSession -> {
+                    webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived()));
+                    webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent()));
+                    webrtcSession.setBeginTime(webrtcSession.getCreated());
+                }
+        );
+        tableDataInfo.setTotal(rtmpSessions.getItemCount());
+        tableDataInfo.setRows(items);
+        tableDataInfo.setCode(HttpStatus.SUCCESS);
+        tableDataInfo.setMsg("查询成功");
+        return tableDataInfo;
+    }
+
+    /**
+     * 获取rtsp拉流列表
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize) {
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        RtspSessions rtspSessions = mediaClient.rtspsessions(pageNum - 1, pageSize);
+        List<RtspSession> items = rtspSessions.getItems();
+        items.stream().forEach(
+                webrtcSession -> {
+                    webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived()));
+                    webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent()));
+                    webrtcSession.setBeginTime(webrtcSession.getCreated());
+                }
+        );
+        tableDataInfo.setTotal(rtspSessions.getItemCount());
+        tableDataInfo.setRows(items);
+        tableDataInfo.setCode(HttpStatus.SUCCESS);
+        tableDataInfo.setMsg("查询成功");
+        return tableDataInfo;
+    }
+
+    /**
+     * 踢出rtsp会话
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public Boolean kickRtspSession(String sessionId) {
+        try {
+            mediaClient.kickRtspSessions(sessionId);
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    /**
+     * 踢出rtmp会话
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public Boolean kickRtmpSession(String sessionId) {
+        try {
+            mediaClient.kickRtmpSessions(sessionId);
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    /**
+     * 踢出webrtc会话
+     * 刘苏义
+     * 2023/8/29 9:37:05
+     */
+    @Override
+    public Boolean kickWebrtcSession(String sessionId) {
+        try {
+            mediaClient.kickWebrtcSessions(sessionId);
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取流媒体name列表
+     * 刘苏义
+     * 2023/10/13 14:19:07
+     */
+    @Override
+    public List<String> getNameList() {
+        List<String> nameList = new ArrayList<>();
+        try {
+            Paths paths = mediaClient.paths(0, 1000);
+            List<Item> items = paths.getItems();
+            for (Item item : items) {
+                nameList.add(item.getName());
+            }
+        } catch (Exception ex) {
+            log.error("获取流媒体name列表异常:" + ex.getMessage());
+        }
+        return nameList;
+    }
+
+    /**
+     * 检查name是否存在
+     * 刘苏义
+     * 2023/10/19 15:18:45
+     */
+    @Override
+    public boolean checkNameExist(String name) {
+        boolean result = false;
+        List<String> nameList = getNameList();
+        if (nameList.contains(name)) {
+            result = true;
+        }
+        return result;
+    }
+
+    /**
+     * 清空所有流媒体信息
+     *
+     * @author 刘苏义
+     * @date 2024/8/22 10:18
+     */
+    @Override
+    public void clearAll() {
+        List<String> nameList = getNameList();
+        nameList.stream().forEach(name -> {
+            removePath(name);
+        });
+    }
+
+    /**
+     * 开启录像
+     */
+    @Override
+    public String setRecord(Conf conf) {
+        return mediaClient.editPath(conf.getName(), conf);
+    }
+
+    /**
+     * 获取全部录像分段列表
+     */
+    @Override
+    public Paths recordingList(Integer pageNum, Integer pageSize) {
+        Paths paths = mediaClient.recordingList(pageNum - 1, pageSize);
+        paths.getItems().forEach(item -> {
+            item.getSegments().forEach(segment -> {
+                // 使用 OffsetDateTime 进行解析
+                OffsetDateTime offsetDateTime = OffsetDateTime.parse(segment.getStart());
+                // 转换为 java.util.Date
+                Date date = Date.from(offsetDateTime.toInstant());
+                segment.setStartTime(date);
+            });
+        });
+        return paths;
+    }
+
+    /**
+     * 指定路径名获取录像分段列表
+     */
+    @Override
+    public Item recordingListByName(String name) {
+        Item item = mediaClient.recordingListByName(name);
+        item.getSegments().forEach(segment -> {
+            // 使用 OffsetDateTime 进行解析
+            OffsetDateTime offsetDateTime = OffsetDateTime.parse(segment.getStart());
+            // 转换为 java.util.Date
+            Date date = Date.from(offsetDateTime.toInstant());
+            segment.setStartTime(date);
+        });
+        return item;
+    }
+
+    /**
+     * 通过路径名和分段录像开始时间删除录像分段
+     */
+    @Override
+    public String deleteSegment(String name, String start) {
+        return mediaClient.deleteSegment(name, start);
+    }
+
+    //获取通道配置
+    @Override
+    public Conf getPathConfig(String name) {
+        return mediaClient.getPathConfig(name);
+    }
+
+    //获取所有通道配置列表
+    @Override
+    public PathConfig getPathConfigs(Integer pageNum, Integer pageSize) {
+        return mediaClient.getPathConfigs(pageNum - 1, pageSize);
+    }
+
+    //同步无人机推流到流媒体
+    @Scheduled(cron = "0/20 * * * * ?")
+    public void syncVtdu() {
+        log.info("定时同步MediaMTX任务开始");
+        List<String> nameList = getNameList();
+        List<BoxReceiver> boxList = tyService.getBoxList();
+        List<ArdVtdu> ardVtduList = boxList.stream()
+                .filter(boxReceiver -> boxReceiver.getOnLine() != 0)  // 过滤掉不在线的
+                .map(boxReceiver -> {
+                    PullStreamUrlReceiver boxLiveUrl = tyService.getBoxLiveUrl(boxReceiver.getBoxSn());
+                    return ArdVtdu.builder().name(boxReceiver.getBoxSn()).rtspSource(boxLiveUrl.getRtmp()).build();
+                })
+                .collect(Collectors.toList());
+        asyncMediaMTX(ardVtduList, nameList);
+    }
+
+    /**
+     * @return
+     * @Author 刘苏义
+     * @Description 同步本地vtdu库和流媒体中的数据
+     * @Date 2024/7/10 15:26
+     * @Param vtdus vtdu库的集合
+     * @Param names 流媒体的name集合
+     */
+    public void asyncMediaMTX(List<ArdVtdu> vtdus, List<String> names) {
+        //需要更新的数据,参数顺序注意
+        sameList(vtdus, names).forEach(vtdu -> {
+            Conf conf = getPathConfig(vtdu.getName());
+            if (conf == null || conf.getSource() == null) {
+                return;
+            }
+            String currentRtspSource = vtdu.getRtspSource();  // 提前获取需要比对的RTSP源
+            String confRtspSource = null;
+
+            // 未转码
+            if (vtdu.getIsCode().equals(0)) {
+                //获取source中的rtsp地址
+                confRtspSource = conf.getSource();
+
+            } else {
+                // 转码
+                if (vtdu.getIsRecord().equals(1)) {
+                    //开启录像获取RunOnInit中的rtsp地址
+                    confRtspSource = conf.getRunOnInit() != null ? matchRtsp(conf.getRunOnInit()) : null;
+                } else {
+                    //关闭录像获取RunOnDemand中的rtsp地址
+                    confRtspSource = conf.getRunOnDemand() != null ? matchRtsp(conf.getRunOnDemand()) : null;
+                }
+            }
+            // 比较 录像状态 是否一致
+            boolean isRecordChanged = !Objects.equals(conf.getRecord() != null && conf.getRecord() ? 1 : 0, vtdu.getIsRecord());
+            // 比较 RTSP 源是否一致
+            boolean isRtspSourceChanged = !Objects.equals(confRtspSource, currentRtspSource);
+            // 如果 RTSP 源或录制状态发生变化,则更新路径
+            if (isRecordChanged || isRtspSourceChanged) {
+                editPath(vtdu);
+            }
+        });
+        //需要删除的数据
+        diffListToDel(vtdus, names).forEach(name -> {
+            removePath(name);
+        });
+        //需要新增的数据
+        diffListToAdd(vtdus, names).forEach(vtdu -> {
+            addPath(vtdu);
+        });
+    }
+
+    //需要更新的(流媒体和vtdu相同)
+    public List<ArdVtdu> sameList(List<ArdVtdu> vtduses, List<String> names) {
+        return vtduses.stream().filter(vtdu -> names.contains(vtdu.getName())).collect(Collectors.toList());
+    }
+
+    //需要删除的(流媒体中多的)
+    public List<String> diffListToDel(List<ArdVtdu> vtduses, List<String> names) {
+        if (vtduses.size() >= names.size()) {
+            List<String> nameList = vtduses.stream().map(ArdVtdu::getName)
+                    .filter(item -> !names.contains(item))
+                    .collect(Collectors.toList());
+            return nameList;
+        } else {
+            List<String> nameList = names.stream().filter(item -> !vtduses.stream().map(ArdVtdu::getName).collect(Collectors.toList()).contains(item))
+                    .collect(Collectors.toList());
+            return nameList;
+        }
+    }
+
+    //需要新增的(流媒体中少的)
+    public List<ArdVtdu> diffListToAdd(List<ArdVtdu> vtduses, List<String> names) {
+        return vtduses.stream().filter(vtdu -> !names.contains(vtdu.getName())).collect(Collectors.toList());
+    }
+
+    //匹配rtsp地址
+    private String matchRtsp(String value) {
+        Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(value);
+        if (matcher.find()) {
+            return matcher.group();
+        } else {
+            return "";
+        }
+    }
+}
diff --git a/ard-work/src/main/java/com/ard/vtdu/util/ArdTool.java b/ard-work/src/main/java/com/ard/vtdu/util/ArdTool.java
new file mode 100644
index 0000000..c4fb18b
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/vtdu/util/ArdTool.java
@@ -0,0 +1,214 @@
+package com.ard.vtdu.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.text.DecimalFormat;
+import java.time.LocalTime;
+import java.util.*;
+
+/**
+ * @Description: 工具方法
+ * @ClassName: ArdTool
+ * @Author: 刘苏义
+ * @Date: 2023年06月02日14:37
+ * @Version: 1.0
+ **/
+@Slf4j
+public class ArdTool {
+
+    /**
+     * @描述 实体对象转Map
+     * @参数 [entity]
+     * @返回值 java.util.Map<java.lang.String, java.lang.Object>
+     * @创建人 刘苏义
+     * @创建时间 2023/6/2 14:35
+     * @修改人和其它信息
+     */
+    public static Map<String, Object> convertEntityToMap(Object entity) {
+        Map<String, Object> map = new HashMap<>();
+        Field[] fields = entity.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            try {
+                Object value = field.get(entity);
+                map.put(field.getName(), value);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        return map;
+    }
+
+    /**
+     * 求Map<K,V>中最小 Value 对应的Key值
+     *
+     * @param map
+     * @return
+     */
+    public static String getKeyByMinValue(Map<String, Double> map) {
+        if (map == null || map.size() == 0) return null;
+        List<Map.Entry<String, Double>> list = new ArrayList(map.entrySet());
+        Collections.sort(list, (o1, o2) -> (o1.getValue().intValue() - o2.getValue().intValue()));
+        String min = list.get(0).getKey();
+//        String max = list.get(list.size() - 1).getKey();
+        return min;
+    }
+
+    /**
+     * @描述 通过日夜切换时间判断当前使用通道号
+     * @参数 []
+     * @返回值 java.lang.Integer
+     * @创建人 刘苏义
+     * @创建时间 2023/6/17 13:50
+     * @修改人和其它信息
+     */
+    public static Integer getChannelBydayNightTime(String dayNightTime) {
+        try {
+            // 获取当前时间
+            LocalTime currentTime = LocalTime.now();
+            String[] dayNight = dayNightTime.split("-");
+            // 解析时间字符串
+            LocalTime startTime = LocalTime.parse(dayNight[0]);
+            LocalTime endTime = LocalTime.parse(dayNight[1]);
+
+            // 检查当前时间是否在给定的时间范围内
+            boolean isWithinRange = !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
+            // 输出结果
+            if (isWithinRange) {
+                return 1;
+            } else {
+                return 2;
+            }
+        } catch (Exception ex) {
+            log.error("光电切换获取通道异常:" + ex.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * 文件大小智能转换
+     * 会将文件大小转换为最大满足单位
+     *
+     * @param size(文件大小,单位为B)
+     * @return 文件大小
+     */
+    public static String formatFileSize(Long size) {
+        String sizeName = null;
+        if (1024 * 1024 > size && size >= 1024) {
+            sizeName = String.format("%.2f", size.doubleValue() / 1024) + "KB";
+        } else if (1024 * 1024 * 1024 > size && size >= 1024 * 1024) {
+            sizeName = String.format("%.2f", size.doubleValue() / (1024 * 1024)) + "MB";
+        } else if (size >= 1024 * 1024 * 1024) {
+            sizeName = String.format("%.2f", size.doubleValue() / (1024 * 1024 * 1024)) + "GB";
+        } else {
+            sizeName = size.toString() + "B";
+        }
+        return sizeName;
+    }
+
+    /**
+     * 文件大小智能转换
+     * 会将文件大小转换为最大满足单位
+     *
+     * @param size(文件大小,单位为B)
+     * @return 文件大小
+     */
+    public static String readableFileSize(long size) {
+        if (size <= 0) {
+            return "0";
+        }
+        final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
+        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
+        return new DecimalFormat("#,###.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
+    }
+
+    /**
+     * byte数组转字符串
+     * 刘苏义
+     * 2023/10/18 8:42:59
+     *
+     * @param byteArray
+     */
+    public static String byteArrayToString(byte[] byteArray) {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (byte b : byteArray) {
+            stringBuilder.append(String.valueOf(b));
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * 将数据写入txt文件
+     * 刘苏义
+     * 2024/3/8 12:58:39
+     */
+    public static void writeStringToFile(String data, String fileName) {
+
+        try {
+            File file = new File(fileName);
+            File parentDir = file.getParentFile();
+            if (!parentDir.exists()) {
+                parentDir.mkdirs(); // 创建文件所在的目录,包括父目录
+            }
+            boolean fileExists = file.exists();
+            boolean dataExists = false;
+            if(fileExists) {
+                // 读取文件内容
+                BufferedReader reader = new BufferedReader(new FileReader(fileName));
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    if (line.equals(data)) {
+                        dataExists = true;
+                        break;
+                    }
+                }
+                reader.close();
+            }
+
+            // 如果文件不存在或数据不存在,则追加到文件
+            if (!fileExists || !dataExists) {
+                FileWriter writer = new FileWriter(fileName, true);
+                writer.write(data + System.lineSeparator());
+                writer.close();
+                if (!fileExists) {
+                    log.debug("File created and data has been appended.");
+                } else {
+                    log.debug("Data has been appended to the file.");
+                }
+            } else {
+                log.debug("Data already exists in the file. Not appending.");
+            }
+        } catch (IOException e) {
+            log.error("An error occurred.");
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *  通过反射对象填充
+     * 刘苏义
+     * 2024/4/2 11:50:57
+     */
+    public static void fillNullFields(Object source, Object target) {
+        if (source == null || target == null) {
+            return;
+        }
+        Field[] fields = source.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            try {
+                Object sourceValue = field.get(source);
+                Object targetValue = field.get(target);
+                if (targetValue == null && sourceValue != null) {
+                    field.set(target, sourceValue);
+                }
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+}

--
Gitblit v1.9.3