diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Preset.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Preset.java
new file mode 100755
index 000000000..0bb8eec39
--- /dev/null
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Preset.java
@@ -0,0 +1,12 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+
+import lombok.Data;
+
+@Data
+public class Preset {
+
+    private String presetId;
+
+    private String presetName;
+}
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PresetQuerySipReq.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PresetQuerySipReq.java
deleted file mode 100755
index d1971a2e2..000000000
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PresetQuerySipReq.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.bean;
-
-
-/**
- * @author chenjialing
- */
-public class PresetQuerySipReq {
-
-    private String presetId;
-
-    private String presetName;
-
-    public String getPresetId() {
-        return presetId;
-    }
-
-    public void setPresetId(String presetId) {
-        this.presetId = presetId;
-    }
-
-    public String getPresetName() {
-        return presetName;
-    }
-
-    public void setPresetName(String presetName) {
-        this.presetName = presetName;
-    }
-}
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java
index d2773918a..a2319447a 100755
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java
@@ -119,7 +119,7 @@ public class PtzController {
 	@Parameter(name = "parameter1", description = "数据一", required = true)
 	@Parameter(name = "parameter2", description = "数据二", required = true)
 	@Parameter(name = "combindCode2", description = "组合码二", required = true)
-	@PostMapping("/front_end_command/{deviceId}/{channelId}")
+	@PostMapping("/fi/{deviceId}/{channelId}")
 	public void frontEndCommand(@PathVariable String deviceId,@PathVariable String channelId,int cmdCode, int parameter1, int parameter2, int combindCode2){
 
 		if (log.isDebugEnabled()) {
@@ -136,11 +136,11 @@ public class PtzController {
 	}
 
 
-	@Operation(summary = "预置位查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
+	@Operation(summary = "查询预置位", security = @SecurityRequirement(name = JwtUtils.HEADER))
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
 	@Parameter(name = "channelId", description = "通道国标编号", required = true)
 	@GetMapping("/preset/query/{deviceId}/{channelId}")
-	public DeferredResult<String> presetQueryApi(@PathVariable String deviceId, @PathVariable String channelId) {
+	public DeferredResult<String> queryPreset(@PathVariable String deviceId, @PathVariable String channelId) {
 		if (log.isDebugEnabled()) {
 			log.debug("设备预置位查询API调用");
 		}
@@ -175,4 +175,40 @@ public class PtzController {
 		}
 		return result;
 	}
+
+	@Operation(summary = "新增预置位", security = @SecurityRequirement(name = JwtUtils.HEADER))
+	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
+	@Parameter(name = "channelId", description = "通道国标编号", required = true)
+	@Parameter(name = "presetId", description = "预置位编号", required = true)
+	@GetMapping("/preset/add/{deviceId}/{channelId}")
+	public void addPreset(@PathVariable String deviceId, @PathVariable String channelId, Integer presetId) {
+		if (presetId == null || presetId < 1 || presetId > 255) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为1-255之间的数字");
+		}
+		frontEndCommand(deviceId, channelId, 0x81, 1, presetId, 0);
+	}
+
+	@Operation(summary = "调用预置位", security = @SecurityRequirement(name = JwtUtils.HEADER))
+	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
+	@Parameter(name = "channelId", description = "通道国标编号", required = true)
+	@Parameter(name = "presetId", description = "预置位编号", required = true)
+	@GetMapping("/preset/call/{deviceId}/{channelId}")
+	public void callPreset(@PathVariable String deviceId, @PathVariable String channelId, Integer presetId) {
+		if (presetId == null || presetId < 1 || presetId > 255) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为1-255之间的数字");
+		}
+		frontEndCommand(deviceId, channelId, 0x82, 1, presetId, 0);
+	}
+
+	@Operation(summary = "删除预置位", security = @SecurityRequirement(name = JwtUtils.HEADER))
+	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
+	@Parameter(name = "channelId", description = "通道国标编号", required = true)
+	@Parameter(name = "presetId", description = "预置位编号", required = true)
+	@GetMapping("/preset/delete/{deviceId}/{channelId}")
+	public void deletePreset(@PathVariable String deviceId, @PathVariable String channelId, Integer presetId) {
+		if (presetId == null || presetId < 1 || presetId > 255) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为1-255之间的数字");
+		}
+		frontEndCommand(deviceId, channelId, 0x83, 1, presetId, 0);
+	}
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPTZService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPTZService.java
new file mode 100755
index 000000000..d7c468052
--- /dev/null
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPTZService.java
@@ -0,0 +1,21 @@
+package com.genersoft.iot.vmp.gb28181.service;
+
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.Preset;
+
+import java.util.List;
+
+public interface IPTZService {
+
+
+    List<Preset> queryPresetList(String deviceId, String channelDeviceId);
+
+    void addPreset(Preset preset);
+
+    void deletePreset(Integer qq);
+
+    void ptz(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed);
+
+    void frontEndCommand(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combindCode2);
+}
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
index f731ce70b..d37af47ac 100755
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
@@ -19,7 +19,6 @@ import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
 import com.genersoft.iot.vmp.media.bean.MediaServer;
 import com.genersoft.iot.vmp.media.service.IMediaServerService;
@@ -34,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.util.ObjectUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
@@ -51,9 +49,6 @@ import java.util.concurrent.TimeUnit;
 @DS("master")
 public class DeviceServiceImpl implements IDeviceService {
 
-    @Autowired
-    private SIPCommander cmder;
-
     @Autowired
     private DynamicTask dynamicTask;
 
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PTZServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PTZServiceImpl.java
new file mode 100644
index 000000000..36f60510c
--- /dev/null
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PTZServiceImpl.java
@@ -0,0 +1,62 @@
+package com.genersoft.iot.vmp.gb28181.service.impl;
+
+import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.Preset;
+import com.genersoft.iot.vmp.gb28181.service.IPTZService;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.List;
+
+@Slf4j
+@Service
+public class PTZServiceImpl implements IPTZService {
+
+
+    @Autowired
+    private SIPCommander cmder;
+
+
+    @Override
+    public void ptz(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
+        try {
+            cmder.frontEndCmd(device, channelId, cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            log.error("[命令发送失败] 云台控制: {}", e.getMessage());
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void frontEndCommand(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combindCode2) {
+        try {
+            cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            log.error("[命令发送失败] 前端控制: {}", e.getMessage());
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public List<Preset> queryPresetList(String deviceId, String channelDeviceId) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void addPreset(Preset preset) {
+
+    }
+
+    @Override
+    public void deletePreset(Integer qq) {
+
+    }
+}
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java
index fcc5db8fe..68fba96e0 100755
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java
@@ -488,8 +488,7 @@ public class PlayServiceImpl implements IPlayService {
             log.info("[语音对讲]开始 获取发流端口失败 deviceId: {}, channelId: {},", device.getDeviceId(), channel.getDeviceId());
             return;
         }
-       
-        
+
         sendRtpInfo.setOnlyAudio(true);
         sendRtpInfo.setPt(8);
         sendRtpInfo.setStatus(1);
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
index 119b0c7f0..21626d8bf 100755
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
@@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.respon
 
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.Platform;
-import com.genersoft.iot.vmp.gb28181.bean.PresetQuerySipReq;
+import com.genersoft.iot.vmp.gb28181.bean.Preset;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
@@ -79,11 +79,11 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
                 return;
             }
             int sumNum = Integer.parseInt(presetListNumElement.attributeValue("Num"));
-            List<PresetQuerySipReq> presetQuerySipReqList = new ArrayList<>();
+            List<Preset> presetQuerySipReqList = new ArrayList<>();
             if (sumNum > 0) {
                 for (Iterator<Element> presetIterator = presetListNumElement.elementIterator(); presetIterator.hasNext(); ) {
                     Element itemListElement = presetIterator.next();
-                    PresetQuerySipReq presetQuerySipReq = new PresetQuerySipReq();
+                    Preset presetQuerySipReq = new Preset();
                     for (Iterator<Element> itemListIterator = itemListElement.elementIterator(); itemListIterator.hasNext(); ) {
                         // 遍历item
                         Element itemOne = itemListIterator.next();
diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
index 114e4c720..6b1c1d135 100755
--- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
+++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -299,7 +299,7 @@ public class ZLMHttpHookListener {
     @ResponseBody
     @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8")
     public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4HookParam param) {
-        log.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path());
+        log.info("[ZLM HOOK] 录像完成:时长: {}, {}->{}",param.getTime_len(), param.getMediaServerId(), param.getFile_path());
 
         try {
             MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
diff --git a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
index 359d38145..c8d049529 100644
--- a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
+++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
@@ -4,7 +4,7 @@ import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.PresetQuerySipReq;
+import com.genersoft.iot.vmp.gb28181.bean.Preset;
 import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
 import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -215,13 +215,13 @@ public class ApiDeviceController {
         }
 
         deferredResultEx.setFilter(filterResult->{
-            List<PresetQuerySipReq> presetQuerySipReqList = (List<PresetQuerySipReq>)filterResult;
+            List<Preset> presetQuerySipReqList = (List<Preset>)filterResult;
             HashMap<String, Object> resultMap = new HashMap<>();
             resultMap.put("DeviceID", code);
             resultMap.put("Result", "OK");
             resultMap.put("SumNum", presetQuerySipReqList.size());
             ArrayList<Map<String, Object>> presetItemList = new ArrayList<>(presetQuerySipReqList.size());
-            for (PresetQuerySipReq presetQuerySipReq : presetQuerySipReqList) {
+            for (Preset presetQuerySipReq : presetQuerySipReqList) {
                 Map<String, Object> item = new HashMap<>();
                 item.put("PresetID", presetQuerySipReq.getPresetId());
                 item.put("PresetName", presetQuerySipReq.getPresetName());
diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue
index 0eaec92f9..cb01e06aa 100755
--- a/web_src/src/components/dialog/devicePlayer.vue
+++ b/web_src/src/components/dialog/devicePlayer.vue
@@ -26,6 +26,7 @@
 
       </div>
       <div id="shared" style="text-align: right; margin-top: 1rem;">
+
         <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick">
           <el-tab-pane label="实时视频" name="media">
             <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
@@ -154,7 +155,7 @@
           <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
           <!--遥控界面-->
           <el-tab-pane label="云台控制" name="control" v-if="showPtz">
-            <div style="display: flex; justify-content: left;">
+            <div style="display: grid; grid-template-columns: 200px auto; height: 180px; overflow: auto">
               <div class="control-wrapper">
                 <div class="control-btn control-top" @mousedown="ptzCamera('up')" @mouseup="ptzCamera('stop')">
                   <i class="el-icon-caret-top"></i>
@@ -180,11 +181,39 @@
                                                      style="font-size: 1.875rem;"></i></div>
                 <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;"
                      @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i
-                    class="el-icon-zoom-out control-zoom-btn"></i></div>
+                  class="el-icon-zoom-out control-zoom-btn"></i></div>
                 <div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
                   <el-slider v-model="controSpeed" :max="255"></el-slider>
                 </div>
               </div>
+              <div style="text-align: left" >
+                <el-select
+                  v-model="ptzMethod"
+                  style="width: 100%"
+                  placeholder="请选择云台功能"
+                >
+                  <el-option label="预置点" value="preset"></el-option>
+                  <el-option label="巡航组" value="cruising"></el-option>
+                  <el-option label="线性扫描" value="scan"></el-option>
+                  <el-option label="巡迹" value="cruise"></el-option>
+                </el-select>
+
+                <ptzPreset :channelDeviceId="channelId" :deviceId="deviceId" v-if="ptzMethod === 'preset'" style="margin-top: 1rem"></ptzPreset>
+                <div v-if="ptzMethod === 'cruising'">
+                  111
+                </div>
+                <div v-if="ptzMethod === 'scan'">
+                  111
+                </div>
+                <div v-if="ptzMethod === 'cruise'">
+                  111
+                </div>
+              </div>
+            </div>
+
+
+            <div style="display: flex; justify-content: left;">
+
 
               <div class="control-panel">
                 <el-button-group>
@@ -327,11 +356,13 @@ import rtcPlayer from '../dialog/rtcPlayer.vue'
 import LivePlayer from '@liveqing/liveplayer'
 import crypto from 'crypto'
 import jessibucaPlayer from '../common/jessibuca.vue'
+import PtzPreset from "../common/ptzPreset.vue";
 
 export default {
   name: 'devicePlayer',
   props: {},
   components: {
+    PtzPreset,
     LivePlayer, jessibucaPlayer, rtcPlayer,
   },
   computed: {
@@ -363,6 +394,8 @@ export default {
       },
       showVideoDialog: false,
       streamId: '',
+      ptzMethod: 'preset',
+      ptzPresetId: '',
       app: '',
       mediaServerId: '',
       convertKey: '',