ソースを参照

优化拉流代理

648540858 1 年間 前
コミット
9f4e66a38b
23 ファイル変更601 行追加345 行削除
  1. 2 1
      src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java
  2. 28 2
      src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java
  3. 95 18
      src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java
  4. 13 0
      src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java
  5. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  6. 1 2
      src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java
  7. 3 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java
  8. 2 2
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  9. 2 2
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
  10. 1 1
      src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java
  11. 71 0
      src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxyParam.java
  12. 41 32
      src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java
  13. 12 42
      src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java
  14. 63 0
      src/main/java/com/genersoft/iot/vmp/streamProxy/dao/provider/StreamProxyProvider.java
  15. 12 15
      src/main/java/com/genersoft/iot/vmp/streamProxy/service/IStreamProxyService.java
  16. 125 98
      src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java
  17. 0 31
      src/main/java/com/genersoft/iot/vmp/streamPush/controller/StreamPushController.java
  18. 58 38
      web_src/src/components/StreamProxyEdit.vue
  19. 58 53
      web_src/src/components/StreamProxyList.vue
  20. 1 1
      web_src/src/components/StreamPushList.vue
  21. 7 2
      web_src/src/components/common/CommonChannelEdit.vue
  22. 2 2
      web_src/src/components/dialog/channelCode.vue
  23. 3 1
      数据库/2.7.2-重构/初始化-mysql-2.7.2.sql

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java

@@ -82,7 +82,8 @@ public class CommonChannelController {
     @Operation(summary = "增加通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
     @ResponseBody
     @PostMapping("/add")
-    public void add(@RequestBody CommonGBChannel channel){
+    public CommonGBChannel add(@RequestBody CommonGBChannel channel){
         channelService.add(channel);
+        return channel;
     }
 }

+ 28 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java

@@ -95,8 +95,8 @@ public class MediaController {
             return  new StreamContent(streamInfo);
         }else {
             //获取流失败,重启拉流后重试一次
-            streamProxyService.stop(app,stream);
-            boolean start = streamProxyService.start(app, stream);
+            streamProxyService.stopByAppAndStream(app,stream);
+            boolean start = streamProxyService.startByAppAndStream(app, stream);
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
@@ -117,4 +117,30 @@ public class MediaController {
             }
         }
     }
+    /**
+     * 获取推流播放地址
+     * @param app 应用名
+     * @param stream 流id
+     * @return
+     */
+    @GetMapping(value = "/getPlayUrl")
+    @ResponseBody
+    @Operation(summary = "获取推流播放地址", security = @SecurityRequirement(name = JwtUtils.HEADER))
+    @Parameter(name = "app", description = "应用名", required = true)
+    @Parameter(name = "stream", description = "流id", required = true)
+    @Parameter(name = "mediaServerId", description = "媒体服务器id")
+    public StreamContent getPlayUrl(@RequestParam String app, @RequestParam String stream,
+                                    @RequestParam(required = false) String mediaServerId){
+        boolean authority = false;
+        // 是否登陆用户, 登陆用户返回完整信息
+        LoginUser userInfo = SecurityUtils.getUserInfo();
+        if (userInfo!= null) {
+            authority = true;
+        }
+        StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority);
+        if (streamInfo == null){
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取播放地址失败");
+        }
+        return new StreamContent(streamInfo);
+    }
 }

+ 95 - 18
src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java

@@ -12,16 +12,12 @@ public interface CommonGBChannelMapper {
 
     @Select("select\n" +
             "    id as gb_id,\n" +
-            "    device_db_id,\n" +
+            "    id as gb_id,\n" +
+            "    device_db_id as gb_device_db_id,\n" +
             "    stream_push_id,\n" +
             "    stream_proxy_id,\n" +
             "    create_time,\n" +
             "    update_time,\n" +
-            "    sub_count,\n" +
-            "    stream_id,\n" +
-            "    has_audio,\n" +
-            "    gps_time,\n" +
-            "    stream_identification,\n" +
             "    coalesce(gb_device_id, device_id) as gb_device_id,\n" +
             "    coalesce(gb_name, name) as gb_name,\n" +
             "    coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" +
@@ -141,6 +137,7 @@ public interface CommonGBChannelMapper {
             "#{gbSvcTimeSupportMode}"+
             ")" +
             " </script>")
+    @Options(useGeneratedKeys = true, keyProperty = "gbId", keyColumn = "id")
     int insert(CommonGBChannel commonGBChannel);
 
     @Select(" select\n" +
@@ -283,16 +280,11 @@ public interface CommonGBChannelMapper {
     @Select(value = {" <script>" +
             " select\n" +
             "    id as gb_id,\n" +
-            "    device_db_id,\n" +
+            "    device_db_id as gb_device_db_id,\n" +
             "    stream_push_id,\n" +
             "    stream_proxy_id,\n" +
             "    create_time,\n" +
             "    update_time,\n" +
-            "    sub_count,\n" +
-            "    stream_id,\n" +
-            "    has_audio,\n" +
-            "    gps_time,\n" +
-            "    stream_identification,\n" +
             "    coalesce(gb_device_id, device_id) as gb_device_id,\n" +
             "    coalesce(gb_name, name) as gb_name,\n" +
             "    coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" +
@@ -462,16 +454,11 @@ public interface CommonGBChannelMapper {
     @Select(value = {" <script>" +
             " select\n" +
             "    id as gb_id,\n" +
-            "    device_db_id,\n" +
+            "    device_db_id as gb_device_db_id,\n" +
             "    stream_push_id,\n" +
             "    stream_proxy_id,\n" +
             "    create_time,\n" +
             "    update_time,\n" +
-            "    sub_count,\n" +
-            "    stream_id,\n" +
-            "    has_audio,\n" +
-            "    gps_time,\n" +
-            "    stream_identification,\n" +
             "    coalesce(gb_device_id, device_id) as gb_device_id,\n" +
             "    coalesce(gb_name, name) as gb_name,\n" +
             "    coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" +
@@ -518,4 +505,94 @@ public interface CommonGBChannelMapper {
             " <foreach collection='channelListInDb'  item='item'  open='(' separator=',' close=')' > #{item.gbId}</foreach>" +
             "</script>"})
     void batchDelete(List<CommonGBChannel> channelListInDb);
+
+    @Select(value = {"select\n" +
+            "    id as gb_id,\n" +
+            "    device_db_id as gb_device_db_id,\n" +
+            "    stream_push_id,\n" +
+            "    stream_proxy_id,\n" +
+            "    create_time,\n" +
+            "    update_time,\n" +
+            "    coalesce(gb_device_id, device_id) as gb_device_id,\n" +
+            "    coalesce(gb_name, name) as gb_name,\n" +
+            "    coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" +
+            "    coalesce(gb_model, model) as gb_model,\n" +
+            "    coalesce(gb_owner, owner) as gb_owner,\n" +
+            "    coalesce(gb_civil_code, civil_code) as gb_civil_code,\n" +
+            "    coalesce(gb_block, block) as gb_block,\n" +
+            "    coalesce(gb_address, address) as gb_address,\n" +
+            "    coalesce(gb_parental, parental) as gb_parental,\n" +
+            "    coalesce(gb_parent_id, parent_id) as gb_parent_id,\n" +
+            "    coalesce(gb_safety_way, safety_way) as gb_safety_way,\n" +
+            "    coalesce(gb_register_way, register_way) as gb_register_way,\n" +
+            "    coalesce(gb_cert_num, cert_num) as gb_cert_num,\n" +
+            "    coalesce(gb_certifiable, certifiable) as gb_certifiable,\n" +
+            "    coalesce(gb_err_code, err_code) as gb_err_code,\n" +
+            "    coalesce(gb_end_time, end_time) as gb_end_time,\n" +
+            "    coalesce(gb_secrecy, secrecy) as gb_secrecy,\n" +
+            "    coalesce(gb_ip_address, ip_address) as gb_ip_address,\n" +
+            "    coalesce(gb_port, port) as gb_port,\n" +
+            "    coalesce(gb_password, password) as gb_password,\n" +
+            "    coalesce(gb_status, status) as gb_status,\n" +
+            "    coalesce(gb_longitude, longitude) as gb_longitude,\n" +
+            "    coalesce(gb_latitude, latitude) as gb_latitude,\n" +
+            "    coalesce(gb_ptz_type, ptz_type) as gb_ptz_type,\n" +
+            "    coalesce(gb_position_type, position_type) as gb_position_type,\n" +
+            "    coalesce(gb_room_type, room_type) as gb_room_type,\n" +
+            "    coalesce(gb_use_type, use_type) as gb_use_type,\n" +
+            "    coalesce(gb_supply_light_type, supply_light_type) as gb_supply_light_type,\n" +
+            "    coalesce(gb_direction_type, direction_type) as gb_direction_type,\n" +
+            "    coalesce(gb_resolution, resolution) as gb_resolution,\n" +
+            "    coalesce(gb_business_group_id, business_group_id) as gb_business_group_id,\n" +
+            "    coalesce(gb_download_speed, download_speed) as gb_download_speed,\n" +
+            "    coalesce(gb_svc_space_support_mod, svc_space_support_mod) as gb_svc_space_support_mod,\n" +
+            "    coalesce(gb_svc_time_support_mode,svc_time_support_mode) as gb_svc_time_support_mode\n" +
+            "from wvp_device_channel \n" +
+            "where stream_push_id = #{streamPushId}"})
+    CommonGBChannel queryByStreamPushId(@Param("streamPushId") Integer streamPushId);
+
+    @Select(value = {"select\n" +
+            "    id as gb_id,\n" +
+            "    device_db_id as gb_device_db_id,\n" +
+            "    stream_push_id,\n" +
+            "    stream_proxy_id,\n" +
+            "    create_time,\n" +
+            "    update_time,\n" +
+            "    coalesce(gb_device_id, device_id) as gb_device_id,\n" +
+            "    coalesce(gb_name, name) as gb_name,\n" +
+            "    coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" +
+            "    coalesce(gb_model, model) as gb_model,\n" +
+            "    coalesce(gb_owner, owner) as gb_owner,\n" +
+            "    coalesce(gb_civil_code, civil_code) as gb_civil_code,\n" +
+            "    coalesce(gb_block, block) as gb_block,\n" +
+            "    coalesce(gb_address, address) as gb_address,\n" +
+            "    coalesce(gb_parental, parental) as gb_parental,\n" +
+            "    coalesce(gb_parent_id, parent_id) as gb_parent_id,\n" +
+            "    coalesce(gb_safety_way, safety_way) as gb_safety_way,\n" +
+            "    coalesce(gb_register_way, register_way) as gb_register_way,\n" +
+            "    coalesce(gb_cert_num, cert_num) as gb_cert_num,\n" +
+            "    coalesce(gb_certifiable, certifiable) as gb_certifiable,\n" +
+            "    coalesce(gb_err_code, err_code) as gb_err_code,\n" +
+            "    coalesce(gb_end_time, end_time) as gb_end_time,\n" +
+            "    coalesce(gb_secrecy, secrecy) as gb_secrecy,\n" +
+            "    coalesce(gb_ip_address, ip_address) as gb_ip_address,\n" +
+            "    coalesce(gb_port, port) as gb_port,\n" +
+            "    coalesce(gb_password, password) as gb_password,\n" +
+            "    coalesce(gb_status, status) as gb_status,\n" +
+            "    coalesce(gb_longitude, longitude) as gb_longitude,\n" +
+            "    coalesce(gb_latitude, latitude) as gb_latitude,\n" +
+            "    coalesce(gb_ptz_type, ptz_type) as gb_ptz_type,\n" +
+            "    coalesce(gb_position_type, position_type) as gb_position_type,\n" +
+            "    coalesce(gb_room_type, room_type) as gb_room_type,\n" +
+            "    coalesce(gb_use_type, use_type) as gb_use_type,\n" +
+            "    coalesce(gb_supply_light_type, supply_light_type) as gb_supply_light_type,\n" +
+            "    coalesce(gb_direction_type, direction_type) as gb_direction_type,\n" +
+            "    coalesce(gb_resolution, resolution) as gb_resolution,\n" +
+            "    coalesce(gb_business_group_id, business_group_id) as gb_business_group_id,\n" +
+            "    coalesce(gb_download_speed, download_speed) as gb_download_speed,\n" +
+            "    coalesce(gb_svc_space_support_mod, svc_space_support_mod) as gb_svc_space_support_mod,\n" +
+            "    coalesce(gb_svc_time_support_mode,svc_time_support_mode) as gb_svc_time_support_mode\n" +
+            "from wvp_device_channel \n" +
+            "where stream_proxy_id = #{streamProxyId}"})
+    CommonGBChannel queryByStreamProxyId(@Param("streamProxyId") Integer streamProxyId);
 }

+ 13 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java

@@ -34,6 +34,18 @@ public class GbChannelServiceImpl implements IGbChannelService {
 
     @Override
     public int add(CommonGBChannel commonGBChannel) {
+        if (commonGBChannel.getStreamPushId() != null && commonGBChannel.getStreamPushId() > 0) {
+            CommonGBChannel commonGBChannelInDb = commonGBChannelMapper.queryByStreamPushId(commonGBChannel.getStreamPushId());
+            if (commonGBChannelInDb != null) {
+                throw new ControllerException(ErrorCode.ERROR100.getCode(), "此推流已经关联通道");
+            }
+        }
+        if (commonGBChannel.getStreamProxyId() != null && commonGBChannel.getStreamProxyId() > 0) {
+            CommonGBChannel commonGBChannelInDb = commonGBChannelMapper.queryByStreamProxyId(commonGBChannel.getStreamProxyId());
+            if (commonGBChannelInDb != null) {
+                throw new ControllerException(ErrorCode.ERROR100.getCode(), "此代理已经关联通道");
+            }
+        }
         return commonGBChannelMapper.insert(commonGBChannel);
     }
 
@@ -74,6 +86,7 @@ public class GbChannelServiceImpl implements IGbChannelService {
             log.warn("[更新通道] 未找到数据库ID,更新失败, {}", commonGBChannel.getGbDeviceDbId());
             return 0;
         }
+        commonGBChannel.setUpdateTime(DateUtil.getNow());
         int result = commonGBChannelMapper.update(commonGBChannel);
         if (result > 0) {
             try {

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java

@@ -743,7 +743,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             log.info("[ app={}, stream={} ] 等待拉流代理流超时", sendRtpItem.getApp(), sendRtpItem.getStream());
             hookSubscribe.removeSubscribe(hook);
         }, userSetting.getPlatformPlayTimeout());
-        boolean start = streamProxyService.start(sendRtpItem.getApp(), sendRtpItem.getStream());
+        boolean start = streamProxyService.startByAppAndStream(sendRtpItem.getApp(), sendRtpItem.getStream());
         if (!start) {
             try {
                 responseAck(request, Response.BUSY_HERE, "channel [" + sendRtpItem.getChannelId() + "] offline");

+ 1 - 2
src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java

@@ -749,13 +749,12 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
     @Override
     public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr, boolean authority) {
-        StreamInfo streamInfo = null;
         if (mediaServerId == null) {
             mediaServerId = mediaConfig.getId();
         }
         MediaServer mediaInfo = getOne(mediaServerId);
         if (mediaInfo == null) {
-            return null;
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到使用的媒体节点");
         }
         String calld = null;
         StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);

+ 3 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java

@@ -210,7 +210,9 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
         streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app,  stream, callIdParam, isPlay);
 
         streamInfoResult.setMediaInfo(mediaInfo);
-        streamInfoResult.setOriginType(mediaInfo.getOriginType());
+        if (mediaInfo != null) {
+            streamInfoResult.setOriginType(mediaInfo.getOriginType());
+        }
         return streamInfoResult;
     }
 

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -266,14 +266,14 @@ public class ZLMRESTfulUtils {
         return sendPost(mediaServerItem, "getRtpInfo",param, null);
     }
 
-    public JSONObject addFFmpegSource(MediaServer mediaServerItem, String src_url, String dst_url, Integer timeout_ms,
+    public JSONObject addFFmpegSource(MediaServer mediaServerItem, String src_url, String dst_url, Integer timeout_sec,
                                       boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
         log.info(src_url);
         log.info(dst_url);
         Map<String, Object> param = new HashMap<>();
         param.put("src_url", src_url);
         param.put("dst_url", dst_url);
-        param.put("timeout_ms", timeout_ms);
+        param.put("timeout_ms", timeout_sec*1000);
         param.put("enable_mp4", enable_mp4);
         param.put("ffmpeg_cmd_key", ffmpeg_cmd_key);
         return sendPost(mediaServerItem, "addFFmpegSource",param, null);

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java

@@ -284,13 +284,13 @@ public class MediaServiceImpl implements IMediaService {
                 if (streamProxy.isEnableRemoveNoneReader()) {
                     // 无人观看自动移除
                     result = true;
-                    streamProxyService.del(app, stream);
+                    streamProxyService.delteByAppAndStream(app, stream);
                     log.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, stream, streamProxy.getSrcUrl());
                 } else if (streamProxy.isEnableDisableNoneReader()) {
                     // 无人观看停用
                     result = true;
                     // 修改数据
-                    streamProxyService.stop(app, stream);
+                    streamProxyService.stopByAppAndStream(app, stream);
                 } else {
                     // 无人观看不做处理
                     result = false;

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java

@@ -35,7 +35,7 @@ public class StreamProxy extends CommonGBChannel {
     @Schema(description = "拉流地址")
     private String srcUrl;
 
-    @Schema(description = "超时时间")
+    @Schema(description = "超时时间:秒")
     private int timeout;
 
     @Schema(description = "ffmpeg模板KEY")

+ 71 - 0
src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxyParam.java

@@ -0,0 +1,71 @@
+package com.genersoft.iot.vmp.streamProxy.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author lin
+ */
+@Data
+@Schema(description = "拉流代理的信息")
+public class StreamProxyParam {
+
+    @Schema(description = "类型,取值,default: 流媒体直接拉流(默认),ffmpeg: ffmpeg实现拉流")
+    private String type;
+
+    @Schema(description = "应用名")
+    private String app;
+
+    @Schema(description = "流ID")
+    private String stream;
+
+    @Schema(description = "流媒体服务ID")
+    private String mediaServerId;
+
+    @Schema(description = "拉流地址")
+    private String url;
+
+    @Schema(description = "超时时间:秒")
+    private int timeoutMs;
+
+    @Schema(description = "ffmpeg模板KEY")
+    private String ffmpegCmdKey;
+
+    @Schema(description = "rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播")
+    private String rtpType;
+
+    @Schema(description = "是否启用")
+    private boolean enable;
+
+    @Schema(description = "是否启用音频")
+    private boolean enableAudio;
+
+    @Schema(description = "是否启用MP4")
+    private boolean enableMp4;
+
+    @Schema(description = "是否 无人观看时删除")
+    private boolean enableRemoveNoneReader;
+
+    @Schema(description = "是否 无人观看时自动停用")
+    private boolean enableDisableNoneReader;
+
+
+    public StreamProxy buildStreamProxy() {
+        StreamProxy streamProxy = new StreamProxy();
+        streamProxy.setApp(app);
+        streamProxy.setStream(stream);
+        streamProxy.setMediaServerId(mediaServerId);
+        streamProxy.setSrcUrl(url);
+        streamProxy.setTimeout(timeoutMs/1000);
+        streamProxy.setRtspType(rtpType);
+        streamProxy.setEnable(enable);
+        streamProxy.setEnableAudio(enableAudio);
+        streamProxy.setEnableMp4(enableMp4);
+        streamProxy.setEnableRemoveNoneReader(enableRemoveNoneReader);
+        streamProxy.setEnableDisableNoneReader(enableDisableNoneReader);
+        streamProxy.setFfmpegCmdKey(ffmpegCmdKey);
+
+        return streamProxy;
+
+    }
+}

+ 41 - 32
src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java

@@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.conf.security.JwtUtils;
 import com.genersoft.iot.vmp.media.bean.MediaServer;
 import com.genersoft.iot.vmp.media.service.IMediaServerService;
 import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
+import com.genersoft.iot.vmp.streamProxy.bean.StreamProxyParam;
 import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
@@ -44,7 +45,6 @@ public class StreamProxyController {
     @Parameter(name = "page", description = "当前页")
     @Parameter(name = "count", description = "每页查询数量")
     @Parameter(name = "query", description = "查询内容")
-    @Parameter(name = "query", description = "查询内容")
     @Parameter(name = "pulling", description = "是否正在拉流")
     @Parameter(name = "mediaServerId", description = "流媒体ID")
     @GetMapping(value = "/list")
@@ -55,6 +55,12 @@ public class StreamProxyController {
                                       @RequestParam(required = false)Boolean pulling,
                                       @RequestParam(required = false)String mediaServerId){
 
+        if (ObjectUtils.isEmpty(mediaServerId)) {
+            mediaServerId = null;
+        }
+        if (ObjectUtils.isEmpty(query)) {
+            query = null;
+        }
         return streamProxyService.getAll(page, count, query, pulling, mediaServerId);
     }
 
@@ -73,7 +79,7 @@ public class StreamProxyController {
     })
     @PostMapping(value = "/save")
     @ResponseBody
-    public StreamContent save(@RequestBody StreamProxy param){
+    public StreamContent save(@RequestBody StreamProxyParam param){
         log.info("添加代理: " + JSONObject.toJSONString(param));
         if (ObjectUtils.isEmpty(param.getMediaServerId())) {
             param.setMediaServerId("auto");
@@ -81,13 +87,6 @@ public class StreamProxyController {
         if (ObjectUtils.isEmpty(param.getType())) {
             param.setType("default");
         }
-        if (ObjectUtils.isEmpty(param.getGbId())) {
-            param.setGbDeviceId(null);
-        }
-        StreamProxy streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
-        if (streamProxyItem  != null) {
-            streamProxyService.del(param.getApp(), param.getStream());
-        }
 
         StreamInfo streamInfo =  streamProxyService.save(param);
         if (param.isEnable()) {
@@ -107,10 +106,10 @@ public class StreamProxyController {
     })
     @PostMapping(value = "/add")
     @ResponseBody
-    public StreamContent add(@RequestBody StreamProxy param){
+    public StreamProxy add(@RequestBody StreamProxy param){
         log.info("添加代理: " + JSONObject.toJSONString(param));
         if (ObjectUtils.isEmpty(param.getMediaServerId())) {
-            param.setMediaServerId("auto");
+            param.setMediaServerId(null);
         }
         if (ObjectUtils.isEmpty(param.getType())) {
             param.setType("default");
@@ -118,22 +117,24 @@ public class StreamProxyController {
         if (ObjectUtils.isEmpty(param.getGbId())) {
             param.setGbDeviceId(null);
         }
-        StreamProxy streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
-        if (streamProxyItem  != null) {
-            streamProxyService.del(param.getApp(), param.getStream());
-        }
+        streamProxyService.add(param);
+        return param;
+    }
 
-        StreamInfo streamInfo =  streamProxyService.add(param);
-        if (param.isEnable()) {
-            if (streamInfo == null) {
-                throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg());
-            }else {
-                return new StreamContent(streamInfo);
-            }
-        }else {
-            return null;
+    @Operation(summary = "更新代理", security = @SecurityRequirement(name = JwtUtils.HEADER), parameters = {
+            @Parameter(name = "param", description = "代理参数", required = true),
+    })
+    @PostMapping(value = "/update")
+    @ResponseBody
+    public void update(@RequestBody StreamProxy param){
+        log.info("更新代理: " + JSONObject.toJSONString(param));
+        if (param.getId() == 0) {
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "缺少代理信息的ID");
         }
-
+        if (ObjectUtils.isEmpty(param.getGbId())) {
+            param.setGbDeviceId(null);
+        }
+        streamProxyService.update(param);
     }
 
     @GetMapping(value = "/ffmpeg_cmd/list")
@@ -160,18 +161,26 @@ public class StreamProxyController {
         if (app == null || stream == null) {
             throw new ControllerException(ErrorCode.ERROR400.getCode(), app == null ?"app不能为null":"stream不能为null");
         }else {
-            streamProxyService.del(app, stream);
+            streamProxyService.delteByAppAndStream(app, stream);
         }
     }
 
+    @DeleteMapping(value = "/delte")
+    @ResponseBody
+    @Operation(summary = "移除代理", security = @SecurityRequirement(name = JwtUtils.HEADER))
+    @Parameter(name = "id", description = "代理ID", required = true)
+    public void delte(int id){
+        log.info("移除代理: " + id );
+        streamProxyService.delte(id);
+    }
+
     @GetMapping(value = "/start")
     @ResponseBody
     @Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER))
-    @Parameter(name = "app", description = "应用名", required = true)
-    @Parameter(name = "stream", description = "流id", required = true)
-    public void start(String app, String stream){
-        log.info("启用代理: " + app + "/" + stream);
-        boolean result = streamProxyService.start(app, stream);
+    @Parameter(name = "id", description = "代理Id", required = true)
+    public void start(int id){
+        log.info("启用代理: " + id);
+        boolean result = streamProxyService.start(id);
         if (!result) {
             throw new ControllerException(ErrorCode.ERROR100);
         }
@@ -184,6 +193,6 @@ public class StreamProxyController {
     @Parameter(name = "stream", description = "流id", required = true)
     public void stop(String app, String stream){
         log.info("停用代理: " + app + "/" + stream);
-        streamProxyService.stop(app, stream);
+        streamProxyService.stopByAppAndStream(app, stream);
     }
 }

+ 12 - 42
src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.streamProxy.dao;
 
 import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
-import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
+import com.genersoft.iot.vmp.streamProxy.dao.provider.StreamProxyProvider;
 import org.apache.ibatis.annotations.*;
 import org.springframework.stereotype.Repository;
 
@@ -11,10 +11,10 @@ import java.util.List;
 @Repository
 public interface StreamProxyMapper {
 
-    @Insert("INSERT INTO wvp_stream_proxy (type, name, app, stream,media_server_id, src_url, " +
+    @Insert("INSERT INTO wvp_stream_proxy (type, app, stream,media_server_id, src_url, " +
             "timeout, ffmpeg_cmd_key, rtsp_type, enable_audio, enable_mp4, enable, pulling, stream_key, " +
             "enable_remove_none_reader, enable_disable_none_reader, create_time) VALUES" +
-            "(#{type}, #{name}, #{app}, #{stream}, #{mediaServerId}, #{srcUrl}, " +
+            "(#{type}, #{app}, #{stream}, #{mediaServerId}, #{srcUrl}, " +
             "#{timeout}, #{ffmpegCmdKey}, #{rtspType}, #{enableAudio}, #{enableMp4}, #{enable}, #{pulling}, #{streamKey}, " +
             "#{enableRemoveNoneReader}, #{enableDisableNoneReader}, #{createTime} )")
     @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@@ -24,10 +24,8 @@ public interface StreamProxyMapper {
             "SET type=#{type}, " +
             "app=#{app}," +
             "stream=#{stream}," +
-            "name=#{name}," +
             "app=#{app}," +
             "stream=#{stream}," +
-            "url=#{url}, " +
             "media_server_id=#{mediaServerId}, " +
             "src_url=#{srcUrl}," +
             "timeout=#{timeout}, " +
@@ -46,45 +44,14 @@ public interface StreamProxyMapper {
     @Delete("DELETE FROM wvp_stream_proxy WHERE app=#{app} AND stream=#{stream}")
     int delByAppAndStream(String app, String stream);
 
-    @Select("SELECT " +
-            " st.*, " +
-            " st.id as stream_proxy_id, " +
-            " wdc.*, " +
-            " wdc.id as gb_id" +
-            " FROM wvp_stream_proxy st " +
-            " LEFT join wvp_device_channel wdc " +
-            " on st.id = wdc.stream_proxy_id " +
-            " WHERE " +
-            " 1=1 " +
-            " <if test='query != null'> AND (st.app LIKE concat('%',#{query},'%') OR st.stream LIKE concat('%',#{query},'%') " +
-            " OR wdc.gb_device_id LIKE concat('%',#{query},'%') OR wdc.gb_name LIKE concat('%',#{query},'%'))</if> " +
-            " <if test='pulling == true' > AND st.pulling=1</if>" +
-            " <if test='pulling == false' > AND st.pulling=0 </if>" +
-            " <if test='mediaServerId != null' > AND st.media_server_id=#{mediaServerId} </if>" +
-            "order by st.create_time desc")
-    List<StreamProxy> selectAll(@Param("query") String query, @Param("pushing") Boolean pushing, @Param("mediaServerId") String mediaServerId);
-
-    @Select("SELECT " +
-            " st.*, " +
-            " st.id as stream_proxy_id, " +
-            " wdc.*, " +
-            " wdc.id as gb_id" +
-            " FROM wvp_stream_proxy st " +
-            " LEFT join wvp_device_channel wdc " +
-            " on st.id = wdc.stream_proxy_id " +
-            " WHERE st.app=#{app} AND st.stream=#{stream} order by st.create_time desc")
+    @SelectProvider(type = StreamProxyProvider.class, method = "selectAll")
+    List<StreamProxy> selectAll(@Param("query") String query, @Param("pulling") Boolean pulling, @Param("mediaServerId") String mediaServerId);
+
+    @SelectProvider(type = StreamProxyProvider.class, method = "selectOneByAppAndStream")
     StreamProxy selectOneByAppAndStream(@Param("app") String app, @Param("stream") String stream);
 
-    @Select("SELECT " +
-            " st.*, " +
-            " st.id as stream_proxy_id, " +
-            " wdc.*, " +
-            " wdc.id as gb_id" +
-            " FROM wvp_stream_proxy st " +
-            " LEFT join wvp_device_channel wdc " +
-            " on st.id = wdc.stream_proxy_id " +
-            "WHERE st.enable=#{enable} and st.media_server_id= #{id} order by st.create_time desc")
-    List<StreamProxy> selectForEnableInMediaServer(@Param("id")  String id, @Param("enable") boolean enable);
+    @SelectProvider(type = StreamProxyProvider.class, method = "selectForEnableInMediaServer")
+    List<StreamProxy> selectForEnableInMediaServer(@Param("mediaServerId")  String mediaServerId, @Param("enable") boolean enable);
 
 
     @Select("select count(1) from wvp_stream_proxy")
@@ -114,4 +81,7 @@ public interface StreamProxyMapper {
             "SET pulling=false " +
             "WHERE id=#{id}")
     int offline(@Param("id") int id);
+
+    @SelectProvider(type = StreamProxyProvider.class, method = "select")
+    StreamProxy select(@Param("id") int id);
 }

+ 63 - 0
src/main/java/com/genersoft/iot/vmp/streamProxy/dao/provider/StreamProxyProvider.java

@@ -0,0 +1,63 @@
+package com.genersoft.iot.vmp.streamProxy.dao.provider;
+
+import java.util.Map;
+
+public class StreamProxyProvider {
+
+    public String getBaseSelectSql(){
+        return "SELECT " +
+                " st.*, " +
+                " st.id as stream_proxy_id, " +
+                " wdc.*, " +
+                " wdc.id as gb_id" +
+                " FROM wvp_stream_proxy st " +
+                " LEFT join wvp_device_channel wdc " +
+                " on st.id = wdc.stream_proxy_id ";
+    }
+
+    public String select(Map<String, Object> params ){
+        return getBaseSelectSql() + " WHERE st.id = " + params.get("id");
+    }
+
+    public String selectForEnableInMediaServer(Map<String, Object> params ){
+        return getBaseSelectSql() + String.format(" WHERE st.enable=%s and st.media_server_id= %s order by st.create_time desc",
+                params.get("enable"), params.get("mediaServerId"));
+    }
+
+    public String selectOneByAppAndStream(Map<String, Object> params ){
+        return getBaseSelectSql() + String.format(" WHERE st.app=%s AND st.stream=%s order by st.create_time desc",
+                params.get("app"), params.get("stream"));
+    }
+
+    public String selectAll(Map<String, Object> params ){
+        StringBuilder sqlBuild = new StringBuilder();
+        sqlBuild.append(getBaseSelectSql());
+        sqlBuild.append(" WHERE 1=1 ");
+        if (params.get("query") != null) {
+            sqlBuild.append(" AND ")
+                    .append(" (")
+                    .append(" st.app LIKE ").append("'%").append(params.get("query")).append("%'")
+                    .append(" OR")
+                    .append(" st.stream LIKE ").append("'%").append(params.get("query")).append("%'")
+                    .append(" OR")
+                    .append(" wdc.gb_device_id LIKE ").append("'%").append(params.get("query")).append("%'")
+                    .append(" OR")
+                    .append(" wdc.gb_name LIKE ").append("'%").append(params.get("query")).append("%'")
+                    .append(" )")
+            ;
+        }
+        Object pulling = params.get("pulling");
+        if (pulling != null) {
+            if ((Boolean) pulling) {
+                sqlBuild.append(" AND st.pulling=1 ");
+            }else {
+                sqlBuild.append(" AND st.pulling=0 ");
+            }
+        }
+        if (params.get("mediaServerId") != null) {
+            sqlBuild.append(" AND st.media_server_id='").append(params.get("mediaServerId")).append("'");
+        }
+        sqlBuild.append(" order by st.create_time desc");
+        return sqlBuild.toString();
+    }
+}

+ 12 - 15
src/main/java/com/genersoft/iot/vmp/streamProxy/service/IStreamProxyService.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.streamProxy.service;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.media.bean.MediaServer;
 import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
+import com.genersoft.iot.vmp.streamProxy.bean.StreamProxyParam;
 import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
 import com.github.pagehelper.PageInfo;
 
@@ -14,7 +15,7 @@ public interface IStreamProxyService {
      * 保存视频代理
      * @param param
      */
-    StreamInfo save(StreamProxy param);
+    StreamInfo save(StreamProxyParam param);
 
     /**
      * 分页查询
@@ -29,7 +30,7 @@ public interface IStreamProxyService {
      * @param app
      * @param stream
      */
-    void del(String app, String stream);
+    void delteByAppAndStream(String app, String stream);
 
     /**
      * 启用视频代理
@@ -37,17 +38,7 @@ public interface IStreamProxyService {
      * @param stream
      * @return
      */
-    boolean start(String app, String stream);
-
-    /**
-     * 更新状态
-     * @param status 状态
-     * @param app
-     * @param stream
-     */
-    int updateStatusByAppAndStream(String app, String stream, boolean status);
-
-
+    boolean startByAppAndStream(String app, String stream);
 
     /**
      * 停用用视频代理
@@ -55,7 +46,7 @@ public interface IStreamProxyService {
      * @param stream
      * @return
      */
-    void stop(String app, String stream);
+    void stopByAppAndStream(String app, String stream);
 
     /**
      * 获取ffmpeg.cmd模板
@@ -88,7 +79,7 @@ public interface IStreamProxyService {
     /**
      * 更新代理流
      */
-    boolean updateStreamProxy(StreamProxy streamProxyItem);
+    boolean update(StreamProxy streamProxyItem);
 
     /**
      * 获取统计信息
@@ -97,4 +88,10 @@ public interface IStreamProxyService {
     ResourceBaseInfo getOverview();
 
     StreamInfo add(StreamProxy streamProxy);
+
+    StreamProxy getStreamProxy(int id);
+
+    void delte(int id);
+
+    boolean start(int id);
 }

+ 125 - 98
src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java

@@ -17,6 +17,7 @@ import com.genersoft.iot.vmp.media.service.IMediaServerService;
 import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
+import com.genersoft.iot.vmp.streamProxy.bean.StreamProxyParam;
 import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper;
 import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService;
 import com.genersoft.iot.vmp.utils.DateUtil;
@@ -72,10 +73,11 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
      * 流到来的处理
      */
     @Async("taskExecutor")
+    @Transactional
     @org.springframework.context.event.EventListener
     public void onApplicationEvent(MediaArrivalEvent event) {
         if ("rtsp".equals(event.getSchema())) {
-            updateStatusByAppAndStream(event.getApp(), event.getStream(), true);
+            streamChangeHandler(event.getApp(), event.getStream(), event.getMediaServer().getId(), true);
         }
     }
 
@@ -84,9 +86,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
      */
     @Async("taskExecutor")
     @EventListener
+    @Transactional
     public void onApplicationEvent(MediaDepartureEvent event) {
         if ("rtsp".equals(event.getSchema())) {
-            updateStatusByAppAndStream(event.getApp(), event.getStream(), false);
+            streamChangeHandler(event.getApp(), event.getStream(), event.getMediaServer().getId(), false);
         }
     }
 
@@ -102,7 +105,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
         // 拉流代理
         StreamProxy streamProxyByAppAndStream = getStreamProxyByAppAndStream(event.getApp(), event.getStream());
         if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) {
-            start(event.getApp(), event.getStream());
+            startByAppAndStream(event.getApp(), event.getStream());
         }
     }
 
@@ -129,58 +132,80 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
 
     @Override
     @Transactional
-    public StreamInfo save(StreamProxy streamProxy) {
-        MediaServer mediaServer;
-        if (ObjectUtils.isEmpty(streamProxy.getMediaServerId()) || "auto".equals(streamProxy.getMediaServerId())){
-            mediaServer = mediaServerService.getMediaServerForMinimumLoad(null);
+    public StreamInfo save(StreamProxyParam param) {
+        // 兼容旧接口
+        StreamProxy streamProxyInDb = getStreamProxyByAppAndStream(param.getApp(), param.getStream());
+        if (streamProxyInDb  != null && streamProxyInDb.getPulling()) {
+            stopProxy(streamProxyInDb);
+        }
+        if (streamProxyInDb  == null){
+            return add(param.buildStreamProxy());
         }else {
-            mediaServer = mediaServerService.getOne(streamProxy.getMediaServerId());
-        }
-        if (mediaServer == null) {
-            log.warn("保存代理未找到在线的ZLM...");
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "保存代理未找到在线的ZLM");
+            stopProxy(streamProxyInDb);
+            streamProxyMapper.delete(streamProxyInDb.getId());
+            return add(param.buildStreamProxy());
         }
+    }
 
-        streamProxy.setMediaServerId(mediaServer.getId());
-        boolean saveResult;
-        // 更新
-        if (streamProxyMapper.selectOneByAppAndStream(streamProxy.getApp(), streamProxy.getStream()) != null) {
-            saveResult = updateStreamProxy(streamProxy);
-        }else { // 新增
-            saveResult = addStreamProxy(streamProxy);
+    @Override
+    @Transactional
+    public StreamInfo add(StreamProxy streamProxy) {
+        StreamProxy streamProxyInDb = streamProxyMapper.selectOneByAppAndStream(streamProxy.getApp(), streamProxy.getStream());
+        if (streamProxyInDb != null) {
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "APP+STREAM已经存在");
         }
-        if (!saveResult) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "保存失败");
+        if (streamProxy.getGbDeviceId() != null) {
+            gbChannelService.add(streamProxy.buildCommonGBChannel());
         }
-
+        streamProxy.setCreateTime(DateUtil.getNow());
+        streamProxy.setUpdateTime(DateUtil.getNow());
+        streamProxyMapper.add(streamProxy);
+        streamProxy.setStreamProxyId(streamProxy.getId());
         if (streamProxy.isEnable()) {
-            return mediaServerService.startProxy(mediaServer, streamProxy);
+            return startProxy(streamProxy);
         }
         return null;
     }
 
-    /**
-     * 新增代理流
-     */
-    @Transactional
-    public boolean addStreamProxy(StreamProxy streamProxy) {
-        String now = DateUtil.getNow();
-        streamProxy.setCreateTime(now);
-        streamProxy.setUpdateTime(now);
+    @Override
+    public void delte(int id) {
+        StreamProxy streamProxy = getStreamProxy(id);
+        if (streamProxy == null) {
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "代理不存在");
+        }
+        delte(streamProxy);
+    }
 
-        if (streamProxyMapper.add(streamProxy) > 0 && !ObjectUtils.isEmpty(streamProxy.getGbDeviceId())) {
-            gbChannelService.add(streamProxy.buildCommonGBChannel());
+    private void delte(StreamProxy streamProxy) {
+        if (streamProxy.getPulling()) {
+            stopProxy(streamProxy);
         }
-        return true;
+        if(streamProxy.getGbId() > 0) {
+            gbChannelService.delete(streamProxy.getGbId());
+        }
+        streamProxyMapper.delete(streamProxy.getId());
+    }
+
+    @Override
+    @Transactional
+    public void delteByAppAndStream(String app, String stream) {
+        StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream);
+        if (streamProxy == null) {
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "代理不存在");
+        }
+        delte(streamProxy);
     }
 
     /**
      * 更新代理流
      */
     @Override
-    public boolean updateStreamProxy(StreamProxy streamProxy) {
+    public boolean update(StreamProxy streamProxy) {
         streamProxy.setUpdateTime(DateUtil.getNow());
-
+        StreamProxy streamProxyInDb = streamProxyMapper.select(streamProxy.getId());
+        if (streamProxyInDb  == null) {
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "代理不存在");
+        }
         if (streamProxyMapper.update(streamProxy) > 0 && !ObjectUtils.isEmpty(streamProxy.getGbDeviceId())) {
             if (streamProxy.getGbId() > 0) {
                 gbChannelService.update(streamProxy.buildCommonGBChannel());
@@ -188,6 +213,15 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
                 gbChannelService.add(streamProxy.buildCommonGBChannel());
             }
         }
+        // 判断是否需要重启代理
+        if (!streamProxyInDb.getApp().equals(streamProxy.getApp())
+                || !streamProxyInDb.getStream().equals(streamProxy.getStream())
+                || !streamProxyInDb.getMediaServerId().equals(streamProxy.getMediaServerId())
+        ) {
+            // app/stream 变化则重启代理
+            stopProxy(streamProxyInDb);
+            startProxy(streamProxy);
+        }
         return true;
     }
 
@@ -198,63 +232,47 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
         return new PageInfo<>(all);
     }
 
+
     @Override
-    @Transactional
-    public void del(String app, String stream) {
+    public boolean startByAppAndStream(String app, String stream) {
         StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream);
         if (streamProxy == null) {
-            return;
-        }
-        if (streamProxy.getStreamKey() != null) {
-            MediaServer mediaServer = mediaServerService.getOne(streamProxy.getMediaServerId());
-            if (mediaServer != null) {
-                mediaServerService.stopProxy(mediaServer, streamProxy.getStreamKey());
-            }
-        }
-        if (streamProxy.getGbId() > 0) {
-            gbChannelService.delete(streamProxy.getGbId());
+            throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
         }
-        streamProxyMapper.delete(streamProxy.getId());
+        StreamInfo streamInfo = startProxy(streamProxy);
+        return streamInfo != null;
     }
 
     @Override
-    public boolean start(String app, String stream) {
+    public void stopByAppAndStream(String app, String stream) {
         StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream);
         if (streamProxy == null) {
             throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
         }
+        stopProxy(streamProxy);
+    }
+
+    private void stopProxy(StreamProxy streamProxy){
+
         MediaServer mediaServer;
-        if (ObjectUtils.isEmpty(streamProxy.getMediaServerId()) || "auto".equals(streamProxy.getMediaServerId())){
+        String mediaServerId = streamProxy.getMediaServerId();
+        if (mediaServerId == null) {
             mediaServer = mediaServerService.getMediaServerForMinimumLoad(null);
         }else {
-            mediaServer = mediaServerService.getOne(streamProxy.getMediaServerId());
+            mediaServer = mediaServerService.getOne(mediaServerId);
         }
         if (mediaServer == null) {
-            log.warn("[启用代理] 未找到可用的媒体节点");
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的媒体节点");
         }
-        StreamInfo streamInfo = mediaServerService.startProxy(mediaServer, streamProxy);
-        if (streamInfo == null) {
-            log.warn("[启用代理] 失败");
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "失败");
-        }
-        if (!streamProxy.isEnable()) {
-            updateStreamProxy(streamProxy);
-        }
-        return true;
-    }
-
-    @Override
-    public void stop(String app, String stream) {
-        StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream);
-        if (streamProxy == null) {
-            throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
-        }
-        MediaServer mediaServer = mediaServerService.getOne(streamProxy.getMediaServerId());
-        if (mediaServer == null) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到启用时使用的媒体节点");
+        if (ObjectUtils.isEmpty(streamProxy.getStreamKey())) {
+            mediaServerService.closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream());
+        }else {
+            mediaServerService.stopProxy(mediaServer, streamProxy.getStreamKey());
         }
-        mediaServerService.stopProxy(mediaServer, streamProxy.getStreamKey());
+        streamProxy.setMediaServerId(mediaServer.getId());
+        streamProxy.setStreamKey(null);
+        streamProxy.setPulling(false);
+        streamProxyMapper.update(streamProxy);
     }
 
     @Override
@@ -264,8 +282,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
 
 
     @Override
-    public StreamProxy getStreamProxyByAppAndStream(String app, String streamId) {
-        return streamProxyMapper.selectOneByAppAndStream(app, streamId);
+    public StreamProxy getStreamProxyByAppAndStream(String app, String stream) {
+        return streamProxyMapper.selectOneByAppAndStream(app, stream);
     }
 
     @Override
@@ -387,16 +405,19 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
         }
     }
 
-    @Override
     @Transactional
-    public int updateStatusByAppAndStream(String app, String stream, boolean status) {
+    public void streamChangeHandler(String app, String stream, String mediaServerId, boolean status) {
         // 状态变化时推送到国标上级
         StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream);
         if (streamProxy == null) {
-            return 0;
+            return;
         }
-        streamProxy.setPulling(true);
-        streamProxyMapper.online(streamProxy.getId());
+        streamProxy.setPulling(status);
+        if (!mediaServerId.equals(streamProxy.getMediaServerId())) {
+            streamProxy.setMediaServerId(mediaServerId);
+        }
+        streamProxy.setUpdateTime(DateUtil.getNow());
+        streamProxyMapper.update(streamProxy);
         streamProxy.setGbStatus(status?"ON":"OFF");
         if (streamProxy.getGbId() > 0) {
             if (status) {
@@ -405,7 +426,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
                 gbChannelService.offline(streamProxy.buildCommonGBChannel());
             }
         }
-        return 1;
     }
 
     @Override
@@ -417,21 +437,16 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
         return new ResourceBaseInfo(total, online);
     }
 
+
+
     @Override
-    @Transactional
-    public StreamInfo add(StreamProxy streamProxy) {
-        StreamProxy streamProxyInDb = streamProxyMapper.selectOneByAppAndStream(streamProxy.getApp(), streamProxy.getStream());
-        if (streamProxyInDb != null) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "APP+STREAM已经存在");
-        }
-        if (streamProxy.getGbDeviceId() != null) {
-            gbChannelService.add(streamProxy.buildCommonGBChannel());
-        }
-        streamProxyMapper.add(streamProxy);
-        if (streamProxy.isEnable()) {
-            return startProxy(streamProxy);
+    public boolean start(int id) {
+        StreamProxy streamProxy = streamProxyMapper.select(id);
+        if (streamProxy == null) {
+            throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
         }
-        return null;
+        StreamInfo streamInfo = startProxy(streamProxy);
+        return streamInfo != null;
     }
 
     private StreamInfo startProxy(StreamProxy streamProxy){
@@ -440,7 +455,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
         }
         MediaServer mediaServer;
         String mediaServerId = streamProxy.getMediaServerId();
-        if (mediaServerId == null || "auto".equals(mediaServerId)) {
+        if (mediaServerId == null) {
             mediaServer = mediaServerService.getMediaServerForMinimumLoad(null);
         }else {
             mediaServer = mediaServerService.getOne(mediaServerId);
@@ -448,10 +463,22 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
         if (mediaServer == null) {
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的媒体节点");
         }
+        StreamInfo streamInfo = mediaServerService.startProxy(mediaServer, streamProxy);
+        if (mediaServerId == null) {
+            streamProxy.setMediaServerId(mediaServer.getId());
+            update(streamProxy);
+        }
+        return streamInfo;
+    }
 
-        return mediaServerService.startProxy(mediaServer, streamProxy);
-
+    @Override
+    public StreamProxy getStreamProxy(int id) {
+        return streamProxyMapper.select(id);
     }
+
+
+
+
     //    @Scheduled(cron = "* 0/10 * * * ?")
 //    public void asyncCheckStreamProxyStatus() {
 //

+ 0 - 31
src/main/java/com/genersoft/iot/vmp/streamPush/controller/StreamPushController.java

@@ -3,12 +3,9 @@ package com.genersoft.iot.vmp.streamPush.controller;
 import com.alibaba.excel.EasyExcel;
 import com.alibaba.excel.ExcelReader;
 import com.alibaba.excel.read.metadata.ReadSheet;
-import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.security.JwtUtils;
-import com.genersoft.iot.vmp.conf.security.SecurityUtils;
-import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.media.service.IMediaServerService;
@@ -19,7 +16,6 @@ import com.genersoft.iot.vmp.streamPush.bean.StreamPushExcelDto;
 import com.genersoft.iot.vmp.streamPush.enent.StreamPushUploadFileHandler;
 import com.genersoft.iot.vmp.streamPush.service.IStreamPushService;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
-import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.github.pagehelper.PageInfo;
 import io.swagger.v3.oas.annotations.Operation;
@@ -193,33 +189,6 @@ public class StreamPushController {
         return result;
     }
 
-    /**
-     * 获取推流播放地址
-     * @param app 应用名
-     * @param stream 流id
-     * @return
-     */
-    @GetMapping(value = "/getPlayUrl")
-    @ResponseBody
-    @Operation(summary = "获取推流播放地址", security = @SecurityRequirement(name = JwtUtils.HEADER))
-    @Parameter(name = "app", description = "应用名", required = true)
-    @Parameter(name = "stream", description = "流id", required = true)
-    @Parameter(name = "mediaServerId", description = "媒体服务器id")
-    public StreamContent getPlayUrl(@RequestParam String app, @RequestParam String stream,
-                                    @RequestParam(required = false) String mediaServerId){
-        boolean authority = false;
-        // 是否登陆用户, 登陆用户返回完整信息
-        LoginUser userInfo = SecurityUtils.getUserInfo();
-        if (userInfo!= null) {
-            authority = true;
-        }
-        StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority);
-        if (streamInfo == null){
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取播放地址失败");
-        }
-        return new StreamContent(streamInfo);
-    }
-
     /**
      * 添加推流信息
      * @param stream 推流信息

+ 58 - 38
web_src/src/components/StreamProxyEdit.vue

@@ -12,7 +12,7 @@
         </div>
       </div>
     </div>
-    <el-tabs tab-position="left">
+    <el-tabs tab-position="left" style="background-color: #FFFFFF; padding-top: 1rem">
       <el-tab-pane label="拉流代理信息">
         <el-form ref="streamProxy" :rules="rules" :model="streamProxy" label-width="140px" style="width: 50%; margin: 0 auto">
           <el-form-item label="类型" prop="type">
@@ -34,7 +34,7 @@
           <el-form-item label="拉流地址" prop="url">
             <el-input v-model="streamProxy.srcUrl" clearable></el-input>
           </el-form-item>
-          <el-form-item label="超时时间:毫秒" prop="timeoutMs" v-if="streamProxy.type=='ffmpeg'">
+          <el-form-item label="超时时间(秒)" prop="timeoutMs">
             <el-input v-model="streamProxy.timeout" clearable></el-input>
           </el-form-item>
           <el-form-item label="节点选择" prop="rtpType">
@@ -44,6 +44,7 @@
               style="width: 100%"
               placeholder="请选择拉流节点"
             >
+              <el-option key="auto" label="自动选择" value=""></el-option>
               <el-option
                 v-for="item in mediaServerList"
                 :key="item.id"
@@ -66,7 +67,7 @@
               </el-option>
             </el-select>
           </el-form-item>
-          <el-form-item label="拉流方式" prop="rtpType" v-if="streamProxy.type ==='default'">
+          <el-form-item label="拉流方式(RTSP)" prop="rtpType" v-if="streamProxy.type ==='default'">
             <el-select
               v-model="streamProxy.rtspType"
               style="width: 100%"
@@ -93,7 +94,7 @@
           </el-form-item>
           <el-form-item>
             <div style="float: right;">
-              <el-button type="primary" @click="onSubmit" :loading="dialogLoading" >保存</el-button>
+              <el-button type="primary" @click="onSubmit" :loading="locading" >保存</el-button>
               <el-button @click="close">取消</el-button>
             </div>
 
@@ -109,6 +110,7 @@
 
 <script>
 import CommonChannelEdit from './common/CommonChannelEdit'
+import MediaServer from "./service/MediaServer";
 
 export default {
   name: "channelEdit",
@@ -117,59 +119,65 @@ export default {
     CommonChannelEdit,
   },
   created() {
-    console.log(this.streamPush)
+    console.log(this.streamProxy)
+    this.mediaServer.getOnlineMediaServerList((data)=>{
+      this.mediaServerList = data.data;
+    })
   },
   data() {
     return {
       locading: false,
+      mediaServer: new MediaServer(),
+      mediaServerList:{},
+      ffmpegCmdList:{},
       rules: {
         name: [{ required: true, message: "请输入名称", trigger: "blur" }],
         app: [{ required: true, message: "请输入应用名", trigger: "blur" }],
         stream: [{ required: true, message: "请输入流ID", trigger: "blur" }],
-        url: [{ required: true, message: "请输入要代理的流", trigger: "blur" }],
         srcUrl: [{ required: true, message: "请输入要代理的流", trigger: "blur" }],
-        timeoutMs: [{ required: true, message: "请输入FFmpeg推流成功超时时间", trigger: "blur" }],
+        timeout: [{ required: true, message: "请输入FFmpeg推流成功超时时间", trigger: "blur" }],
         ffmpegCmdKey: [{ required: false, message: "请输入FFmpeg命令参数模板(可选)", trigger: "blur" }],
       },
     };
   },
   methods: {
     onSubmit: function () {
-      console.log(this.streamPush)
-      this.locading = true
-      if (this.streamPush.id) {
+      this.locading = true;
+      this.noneReaderHandler();
+      if (this.streamProxy.id) {
         this.$axios({
           method: 'post',
-          url: "/api/push/update",
-          data: this.streamPush
-        }).then((res) => {
-          if (res.data.code === 0) {
+          url:`/api/proxy/update`,
+          data: this.streamProxy
+        }).then((res)=> {
+          if (typeof (res.data.code) != "undefined" && res.data.code === 0) {
             this.$message.success("保存成功");
+            this.streamProxy = res.data.data
           }else {
-            this.$message.error(res.data.msg)
+            this.$message.error(res.data.msg);
           }
-        }).catch((error) => {
-          this.$message.error(error)
-        }).finally(()=>[
-          this.locading = false
-        ])
+        }).catch((error) =>{
+          this.$message.error(res.data.error);
+        }).finally(()=>{
+          this.locading = false;
+        })
       }else {
         this.$axios({
           method: 'post',
-          url: "/api/push/add",
-          data: this.streamPush
-        }).then((res) => {
-          if (res.data.code === 0) {
+          url:`/api/proxy/add`,
+          data: this.streamProxy
+        }).then((res)=> {
+          if (typeof (res.data.code) != "undefined" && res.data.code === 0) {
             this.$message.success("保存成功");
-            this.streamPush = res.data.data
+            this.streamProxy = res.data.data
           }else {
-            this.$message.error(res.data.msg)
+            this.$message.error(res.data.msg);
           }
-        }).catch((error) => {
-          this.$message.error(error)
-        }).finally(()=>[
-          this.locading = false
-        ])
+        }).catch((error) =>{
+          this.$message.error(res.data.error);
+        }).finally(()=>{
+          this.locading = false;
+        })
       }
 
     },
@@ -177,23 +185,35 @@ export default {
       this.closeEdit()
     },
     mediaServerIdChange:function (){
-      let that = this;
-      if (that.proxyParam.mediaServerId !== "auto"){
-        that.$axios({
+      if (this.streamProxy.mediaServerId !== "auto"){
+        this.$axios({
           method: 'get',
           url:`/api/proxy/ffmpeg_cmd/list`,
           params: {
-            mediaServerId: that.proxyParam.mediaServerId
+            mediaServerId: this.streamProxy.mediaServerId
           }
-        }).then(function (res) {
-          that.ffmpegCmdList = res.data.data;
-          that.proxyParam.ffmpegCmdKey = Object.keys(res.data.data)[0];
+        }).then((res)=> {
+          this.ffmpegCmdList = res.data.data;
+          this.streamProxy.ffmpegCmdKey = Object.keys(res.data.data)[0];
         }).catch(function (error) {
           console.log(error);
         });
       }
 
     },
+    noneReaderHandler: function() {
+      console.log(this.streamProxy)
+      if (this.streamProxy.noneReader === null || this.streamProxy.noneReader === "0" || !this.streamProxy.noneReader) {
+        this.streamProxy.enableDisableNoneReader = false;
+        this.streamProxy.enableRemoveNoneReader = false;
+      }else if (this.streamProxy.noneReader === "1"){
+        this.streamProxy.enableDisableNoneReader = true;
+        this.streamProxy.enableRemoveNoneReader = false;
+      }else if (this.streamProxy.noneReader ==="2"){
+        this.streamProxy.enableDisableNoneReader = false;
+        this.streamProxy.enableRemoveNoneReader = true;
+      }
+    },
   },
 };
 </script>

+ 58 - 53
web_src/src/components/StreamProxyList.vue

@@ -5,6 +5,27 @@
       <div class="page-header">
         <div class="page-title">拉流代理列表</div>
         <div class="page-header-btn">
+          搜索:
+          <el-input @input="getStreamProxyList" style="margin-right: 1rem; width: auto;" size="mini" placeholder="关键字"
+                    prefix-icon="el-icon-search" v-model="searchSrt" clearable></el-input>
+          流媒体:
+          <el-select size="mini" @change="getStreamProxyList" style="margin-right: 1rem;" v-model="mediaServerId"
+                     placeholder="请选择" default-first-option>
+            <el-option label="全部" value=""></el-option>
+            <el-option
+              v-for="item in mediaServerList"
+              :key="item.id"
+              :label="item.id"
+              :value="item.id">
+            </el-option>
+          </el-select>
+          推流状态:
+          <el-select size="mini" style="margin-right: 1rem;" @change="getStreamProxyList" v-model="pulling" placeholder="请选择"
+                     default-first-option>
+            <el-option label="全部" value=""></el-option>
+            <el-option label="正在拉流" value="true"></el-option>
+            <el-option label="拉流未进行" value="false"></el-option>
+          </el-select>
           <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addStreamProxy">添加代理</el-button>
           <el-button v-if="false" icon="el-icon-search" size="mini" style="margin-right: 1rem;" type="primary" @click="addOnvif">搜索ONVIF</el-button>
           <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
@@ -12,18 +33,12 @@
       </div>
       <devicePlayer ref="devicePlayer"></devicePlayer>
       <el-table :data="streamProxyList" style="width: 100%" :height="winHeight">
-        <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip/>
         <el-table-column prop="app" label="流应用名" min-width="120" show-overflow-tooltip/>
         <el-table-column prop="stream" label="流ID" min-width="120" show-overflow-tooltip/>
-        <el-table-column label="流地址" min-width="400"  show-overflow-tooltip >
+        <el-table-column label="流地址" min-width="250"  show-overflow-tooltip >
           <template slot-scope="scope">
             <div slot="reference" class="name-wrapper">
-
-              <el-tag size="medium" v-if="scope.row.type == 'default'">
-                <i class="cpoy-btn el-icon-document-copy"  title="点击拷贝" v-clipboard="scope.row.url" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
-                {{scope.row.url}}
-              </el-tag>
-              <el-tag size="medium" v-if="scope.row.type != 'default'">
+              <el-tag size="medium">
                 <i class="cpoy-btn el-icon-document-copy"  title="点击拷贝" v-clipboard="scope.row.srcUrl" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
                 {{scope.row.srcUrl}}
               </el-tag>
@@ -31,20 +46,20 @@
           </template>
         </el-table-column>
         <el-table-column prop="mediaServerId" label="流媒体" min-width="180" ></el-table-column>
-        <el-table-column label="类型" width="100" >
+        <el-table-column label="代理方式" width="100" >
           <template slot-scope="scope">
             <div slot="reference" class="name-wrapper">
-              <el-tag size="medium">{{scope.row.type === "default"? "直接代理":"FFMPEG代理"}}</el-tag>
+              {{scope.row.type === "default"? "默认":"FFMPEG代理"}}
             </div>
           </template>
         </el-table-column>
 
-        <el-table-column prop="gbId" label="国标编码" min-width="180"  show-overflow-tooltip/>
-        <el-table-column label="状态" min-width="120" >
+        <el-table-column prop="gbDeviceId" label="国标编码" min-width="180"  show-overflow-tooltip/>
+        <el-table-column label="拉流状态" min-width="120" >
           <template slot-scope="scope">
             <div slot="reference" class="name-wrapper">
-              <el-tag size="medium" v-if="scope.row.status">在线</el-tag>
-              <el-tag size="medium" type="info" v-if="!scope.row.status">离线</el-tag>
+              <el-tag size="medium" v-if="scope.row.pulling">在线</el-tag>
+              <el-tag size="medium" type="info" v-if="!scope.row.pulling">离线</el-tag>
             </div>
           </template>
         </el-table-column>
@@ -57,47 +72,19 @@
           </template>
         </el-table-column>
         <el-table-column prop="createTime" label="创建时间"  min-width="150" show-overflow-tooltip/>
-        <el-table-column label="音频" min-width="120" >
-          <template slot-scope="scope">
-            <div slot="reference" class="name-wrapper">
-              <el-tag size="medium" v-if="scope.row.enableAudio">已启用</el-tag>
-              <el-tag size="medium" type="info" v-if="!scope.row.enableAudio">未启用</el-tag>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="录制" min-width="120" >
-          <template slot-scope="scope">
-            <div slot="reference" class="name-wrapper">
-              <el-tag size="medium" v-if="scope.row.enableMp4">已启用</el-tag>
-              <el-tag size="medium" type="info" v-if="!scope.row.enableMp4">未启用</el-tag>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="无人观看" min-width="160" >
-          <template slot-scope="scope">
-            <div slot="reference" class="name-wrapper">
-              <el-tag size="medium" v-if="scope.row.enableRemoveNoneReader">移除</el-tag>
-              <el-tag size="medium" v-if="scope.row.enableDisableNoneReader">停用</el-tag>
-              <el-tag size="medium" type="info" v-if="!scope.row.enableRemoveNoneReader && !scope.row.enableDisableNoneReader">不做处理</el-tag>
-            </div>
-          </template>
-        </el-table-column>
-
-
         <el-table-column label="操作" width="360"  fixed="right">
           <template slot-scope="scope">
             <el-button size="medium" icon="el-icon-video-play" type="text" v-if="scope.row.enable" @click="play(scope.row)">播放</el-button>
             <el-divider direction="vertical"></el-divider>
-            <el-button size="medium" icon="el-icon-position" type="text" @click="edit(scope.row)">
+            <el-button size="medium" icon="el-icon-edit" type="text" @click="edit(scope.row)">
               编辑
             </el-button>
-            <el-button size="medium" icon="el-icon-switch-button" type="text" v-if="scope.row.enable" @click="stop(scope.row)">停用</el-button>
             <el-divider direction="vertical"></el-divider>
             <el-button size="medium" icon="el-icon-check" type="text" :loading="scope.row.startBtnLoading" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button>
             <el-divider v-if="!scope.row.enable" direction="vertical"></el-divider>
+            <el-button size="medium" icon="el-icon-cloudy" type="text" @click="queryCloudRecords(scope.row)">云端录像</el-button>
+            <el-divider direction="vertical"></el-divider>
             <el-button size="medium" icon="el-icon-delete" type="text" style="color: #f56c6c" @click="deleteStreamProxy(scope.row)">删除</el-button>
-            <el-button size="medium" icon="el-icon-cloudy" type="text" @click="queryCloudRecords(scope.row)">云端录像
-            </el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -124,6 +111,7 @@
 	import devicePlayer from './dialog/devicePlayer.vue'
 	import uiHeader from '../layout/UiHeader.vue'
   import StreamProxyEdit from "./StreamProxyEdit";
+  import MediaServer from "./service/MediaServer";
 	export default {
 		name: 'streamProxyList',
 		components: {
@@ -144,7 +132,12 @@
 				count:15,
 				total:0,
         startBtnLoading: false,
-        streamProxy: null
+        streamProxy: null,
+        searchSrt: "",
+        mediaServerId: "",
+        pulling: "",
+        mediaServerObj: new MediaServer(),
+        mediaServerList: [],
 			};
 		},
 		computed: {
@@ -160,12 +153,15 @@
 		methods: {
 			initData: function() {
 				this.getStreamProxyList();
+        this.mediaServerObj.getOnlineMediaServerList((data) => {
+          this.mediaServerList = data.data;
+        })
 			},
       stopUpdateList: function (){
         window.clearInterval(this.updateLooper)
       },
       startUpdateList: function (){
-        this.updateLooper = setInterval(this.initData, 1000);
+        this.updateLooper = setInterval(this.getStreamProxyList, 1000);
       },
 			currentChange: function(val){
 				this.currentPage = val;
@@ -182,7 +178,10 @@
 					url:`/api/proxy/list`,
 					params: {
 						page: that.currentPage,
-						count: that.count
+						count: that.count,
+            query: this.searchSrt,
+            pulling: this.pulling,
+            mediaServerId: this.mediaServerId,
 					}
 				}).then(function (res) {
           if (res.data.code === 0) {
@@ -198,7 +197,14 @@
 			},
 			addStreamProxy: function(){
 				// this.$refs.streamProxyEdit.openDialog(null, this.initData)
-        this.streamProxy = {}
+        this.streamProxy = {
+          type: "default",
+          noneReader: "1",
+          enable: true,
+          enableAudio: true,
+          mediaServerId: "",
+          timeout: 10,
+        }
 			},
       addOnvif: function(){
         this.$axios({
@@ -235,7 +241,7 @@
 				let that = this;
 				this.$axios({
 					method: 'get',
-					url:`/api/push/getPlayUrl`,
+					url:`/api/media/getPlayUrl`,
 					params: {
 						app: row.app,
 						stream: row.stream,
@@ -273,10 +279,9 @@
         }).then(() => {
           that.$axios({
             method:"delete",
-            url:"/api/proxy/del",
+            url:"/api/proxy/delete",
             params:{
-              app: row.app,
-              stream: row.stream
+              id: row.id,
             }
           }).then((res)=>{
             that.initData()

+ 1 - 1
web_src/src/components/StreamPushList.vue

@@ -197,7 +197,7 @@ export default {
       this.getListLoading = true;
       this.$axios({
         method: 'get',
-        url: '/api/push/getPlayUrl',
+        url: '/api/media/getPlayUrl',
         params: {
           app: row.app,
           stream: row.stream,

+ 7 - 2
web_src/src/components/common/CommonChannelEdit.vue

@@ -249,9 +249,11 @@ export default {
             if (this.saveSuccess) {
               this.saveSuccess()
             }
+          }else {
+            this.$message.error(res.data.msg);
           }
         }).catch((error) => {
-          console.error(error)
+          this.$message.error(error);
         }).finally(()=>[
           this.locading = false
         ])
@@ -263,12 +265,15 @@ export default {
         }).then((res) => {
           if (res.data.code === 0) {
             this.$message.success("保存成功");
+            this.form = res.data.data
             if (this.saveSuccess) {
               this.saveSuccess()
             }
+          }else {
+            this.$message.error(res.data.msg);
           }
         }).catch((error) => {
-          console.error(error)
+          this.$message.error(error);
         }).finally(()=>[
           this.locading = false
         ])

+ 2 - 2
web_src/src/components/dialog/channelCode.vue

@@ -1,14 +1,14 @@
 <template>
   <el-dialog
     title="生成国标编码"
-    width="60%"
+    width="65rem"
     top="2rem"
     center
     :close-on-click-modal="false"
     :visible.sync="showVideoDialog"
     :destroy-on-close="false"
   >
-    <el-tabs v-model="activeKey" style="padding: 0 1rem" @tab-click="getRegionList">
+    <el-tabs v-model="activeKey" style="padding: 0 1rem; margin: auto 0" @tab-click="getRegionList">
       <el-tab-pane name="0" >
         <div slot="label" >
           <div class="show-code-item">{{ allVal[0].val }}</div>

+ 3 - 1
数据库/2.7.2-重构/初始化-mysql-2.7.2.sql

@@ -196,7 +196,9 @@ create table wvp_device_channel (
                                     stream_push_id integer,
                                     stream_proxy_id integer,
                                     constraint uk_wvp_device_channel_unique_device_channel unique (device_db_id, device_id),
-                                    constraint uk_wvp_unique_channel unique (gb_device_id)
+                                    constraint uk_wvp_unique_channel unique (gb_device_id),
+                                    constraint uk_wvp_unique_stream_push_id unique (stream_push_id),
+                                    constraint uk_wvp_unique_stream_proxy_id unique (stream_proxy_id)
 );
 
 create table wvp_media_server (