64850858
2021-07-16 89a9ab4534f10a224f70e546db838423e84a1965
添加zlm集群支持
66个文件已修改
7个文件已添加
2个文件已删除
3345 ■■■■ 已修改文件
pom.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql.sql 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 213 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/SIPRequestAbstractProcessor.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java 85 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java 196 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerManger.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IMediaServerItem.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IMediaService.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecoderProxyController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/all-application.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/wvp.sqlite 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/CloudRecord.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/PushVideoList.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/StreamProxyList.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/control.vue 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/StreamProxyEdit.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/devicePlayer.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/service/MediaServer.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -196,6 +196,16 @@
            <version>1.12</version>
        </dependency>
<!--        &lt;!&ndash; 检测文件编码 &ndash;&gt;-->
<!--        &lt;!&ndash; https://mvnrepository.com/artifact/cpdetector/cpdetector &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>cpdetector</groupId>-->
<!--            <artifactId>cpdetector</artifactId>-->
<!--            <version>1.0.8</version>-->
<!--        </dependency>-->
        <!-- onvif协议栈 -->
        <dependency>
            <groupId>be.teletask</groupId>
sql/mysql.sql
@@ -138,6 +138,7 @@
    timeout_ms     int          null,
    ffmpeg_cmd_key varchar(255) null,
    rtp_type       varchar(50) null,
    mediaServerId       varchar(50) null,
    enable_hls     bit(1)   null,
    enable_mp4     bit(1)   null,
    enable         bit(1)   not null,
@@ -166,4 +167,28 @@
    create_time varchar(50) not null
);
insert into user (username, password, roleId, create_time) values ('admin', '21232f297a57a5a743894a0e4a801fc3', '0', '2021-04-13 14:14:57');
insert into user (username, password, roleId, create_time) values ('admin', '21232f297a57a5a743894a0e4a801fc3', '0', '2021-04-13 14:14:57');
create table media_server (
      id          varchar(255)
          primary key,
      ip varchar(50) NOT NULL,
      hookIp varchar(50) NOT NULL,
      sdpIp varchar(50) NOT NULL,
      streamIp varchar(50) NOT NULL,
      httpPort int NOT NULL,
      httpSSlPort int NOT NULL,
      rtmpPort int NOT NULL,
      rtmpSSlPort int NOT NULL,
      rtpProxyPort int NOT NULL,
      rtspPort int NOT NULL,
      rtspSSLPort int NOT NULL,
      autoConfig int NOT NULL,
      secret varchar(50) NOT NULL,
      streamNoneReaderDelayMS int NOT NULL,
      rtpEnable int NOT NULL,
      rtpPortRange varchar(50) NOT NULL,
      recordAssistPort int NOT NULL,
      createTime  varchar(50) not null,
      updateTime  varchar(50) not null
);
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
@@ -19,6 +19,7 @@
    private String rtmp;
    private String rtsp;
    private String rtc;
    private String mediaServerId;
    private JSONArray tracks;
    public static class TransactionInfo{
@@ -165,4 +166,12 @@
    public void setTransactionInfo(TransactionInfo transactionInfo) {
        this.transactionInfo = transactionInfo;
    }
    public String getMediaServerId() {
        return mediaServerId;
    }
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
}
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -10,33 +10,31 @@
    
    public static final String WVP_SERVER_PREFIX = "VMP_wvp_server";
    public static final String MEDIA_SERVER_PREFIX = "VMP_media_server";
    public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
    public static final String MEDIA_STREAM_PREFIX = "VMP_media_stream";
    public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
    public static final String DEVICE_PREFIX = "VMP_device_";
    public static final String DEVICE_PREFIX = "VMP_DEVICE_";
    public static final String CACHEKEY_PREFIX = "VMP_channel_";
    public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_";
    public static final String KEEPLIVEKEY_PREFIX = "VMP_keeplive_";
    public static final String PLAYER_PREFIX = "VMP_player_";
    public static final String PLAYER_PREFIX = "VMP_PLAYER_";
    public static final String PLAY_BLACK_PREFIX = "VMP_playback_";
    public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_";
    public static final String PLATFORM_PREFIX = "VMP_platform";
    public static final String PLATFORM_KEEPLIVEKEY_PREFIX = "VMP_PLATFORM_KEEPLIVE_";
    public static final String PLATFORM_KEEPLIVEKEY_PREFIX = "VMP_platform_keeplive_";
    public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_";
    public static final String PLATFORM_CATCH_PREFIX = "VMP_platform_catch_";
    public static final String PLATFORM_REGISTER_PREFIX = "VMP_PLATFORM_REGISTER_";
    public static final String PLATFORM_REGISTER_PREFIX = "VMP_platform_register_";
    public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_";
    public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_platform_register_info_";
    public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_PLATFORM_SEND_RTP_INFO_";
    public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_platform_send_rtp_info_";
    public static final String Pattern_Topic = "VMP_keeplive_platform_";
    public static final String Pattern_Topic = "VMP_KEEPLIVE_PLATFORM_";
    public static final String EVENT_ONLINE_REGISTER = "1";
    
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
@@ -1,11 +1,16 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
@Configuration("mediaConfig")
public class MediaConfig {
public class MediaConfig implements IMediaServerItem {
    @Value("${media.id:}")
    private String id;
    @Value("${media.ip}")
    private String ip;
@@ -25,22 +30,22 @@
    @Value("${media.http-port}")
    private Integer httpPort;
    @Value("${media.http-ssl-port:}")
    @Value("${media.http-ssl-port:0}")
    private Integer httpSSlPort;
    @Value("${media.rtmp-port:}")
    @Value("${media.rtmp-port:0}")
    private Integer rtmpPort;
    @Value("${media.rtmp-ssl-port:}")
    @Value("${media.rtmp-ssl-port:0}")
    private Integer rtmpSSlPort;
    @Value("${media.rtp-proxy-port:}")
    @Value("${media.rtp-proxy-port:0}")
    private Integer rtpProxyPort;
    @Value("${media.rtsp-port:}")
    @Value("${media.rtsp-port:0}")
    private Integer rtspPort;
    @Value("${media.rtsp-ssl-port:}")
    @Value("${media.rtsp-ssl-port:0}")
    private Integer rtspSSLPort;
    @Value("${media.auto-config:true}")
@@ -61,6 +66,23 @@
    @Value("${media.record-assist-port:0}")
    private Integer recordAssistPort;
    private String updateTime;
    private String createTime;
    private boolean docker = false;
    private int count;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getIp() {
        return ip;
    }
@@ -82,80 +104,112 @@
        this.hookIp = hookIp;
    }
    public String getSdpIp() {
        if (StringUtils.isEmpty(sdpIp)){
            return ip;
        }else {
            return sdpIp;
        }
    public String getSipIp() {
        return sipIp;
    }
    public void setSipIp(String sipIp) {
        this.sipIp = sipIp;
    }
    public void setSdpIp(String sdpIp) {
        this.sdpIp = sdpIp;
    }
    public String getStreamIp() {
        if (StringUtils.isEmpty(streamIp)){
            return ip;
        }else {
            return streamIp;
        }
    }
    public void setStreamIp(String streamIp) {
        this.streamIp = streamIp;
    }
    public Integer getHttpPort() {
    public int getHttpPort() {
        return httpPort;
    }
    @Override
    public void setHttpPort(int httpPort) {
    }
    public void setHttpPort(Integer httpPort) {
        this.httpPort = httpPort;
    }
    public Integer getHttpSSlPort() {
    public int getHttpSSlPort() {
        return httpSSlPort;
    }
    @Override
    public void setHttpSSlPort(int httpSSlPort) {
    }
    public void setHttpSSlPort(Integer httpSSlPort) {
        this.httpSSlPort = httpSSlPort;
    }
    public Integer getRtmpPort() {
    public int getRtmpPort() {
        return rtmpPort;
    }
    @Override
    public void setRtmpPort(int rtmpPort) {
    }
    public void setRtmpPort(Integer rtmpPort) {
        this.rtmpPort = rtmpPort;
    }
    public Integer getRtmpSSlPort() {
    public int getRtmpSSlPort() {
        return rtmpSSlPort;
    }
    @Override
    public void setRtmpSSlPort(int rtmpSSlPort) {
    }
    public void setRtmpSSlPort(Integer rtmpSSlPort) {
        this.rtmpSSlPort = rtmpSSlPort;
    }
    public Integer getRtpProxyPort() {
        return rtpProxyPort;
    public int getRtpProxyPort() {
        if (rtpProxyPort == null) {
            return 0;
        }else {
            return rtpProxyPort;
        }
    }
    @Override
    public void setRtpProxyPort(int rtpProxyPort) {
    }
    public void setRtpProxyPort(Integer rtpProxyPort) {
        this.rtpProxyPort = rtpProxyPort;
    }
    public Integer getRtspPort() {
    public int getRtspPort() {
        return rtspPort;
    }
    @Override
    public void setRtspPort(int rtspPort) {
    }
    public void setRtspPort(Integer rtspPort) {
        this.rtspPort = rtspPort;
    }
    public Integer getRtspSSLPort() {
    public int getRtspSSLPort() {
        return rtspSSLPort;
    }
    @Override
    public void setRtspSSLPort(int rtspSSLPort) {
    }
    public void setRtspSSLPort(Integer rtspSSLPort) {
@@ -202,11 +256,101 @@
        this.rtpPortRange = rtpPortRange;
    }
    public Integer getRecordAssistPort() {
    public int getRecordAssistPort() {
        return recordAssistPort;
    }
    @Override
    public void setRecordAssistPort(int recordAssistPort) {
    }
    public void setRecordAssistPort(Integer recordAssistPort) {
        this.recordAssistPort = recordAssistPort;
    }
    @Override
    public boolean isDocker() {
        return docker;
    }
    @Override
    public void setDocker(boolean docker) {
        this.docker = docker;
    }
    public String getSdpIp() {
        if (StringUtils.isEmpty(sdpIp)){
            return ip;
        }else {
            return sdpIp;
        }
    }
    public String getStreamIp() {
        if (StringUtils.isEmpty(streamIp)){
            return ip;
        }else {
            return streamIp;
        }
    }
    public MediaServerItem getMediaSerItem(){
        MediaServerItem mediaServerItem = new MediaServerItem();
        mediaServerItem.setId(id);
        mediaServerItem.setIp(ip);
        mediaServerItem.setDocker(true);
        mediaServerItem.setHookIp(hookIp);
        mediaServerItem.setSdpIp(sdpIp);
        mediaServerItem.setStreamIp(streamIp);
        mediaServerItem.setHttpPort(httpPort);
        mediaServerItem.setHttpSSlPort(httpSSlPort);
        mediaServerItem.setRtmpPort(rtmpPort);
        mediaServerItem.setRtmpSSlPort(rtmpSSlPort);
        mediaServerItem.setRtpProxyPort(rtpProxyPort);
        mediaServerItem.setRtspPort(rtspPort);
        mediaServerItem.setRtspSSLPort(rtspSSLPort);
        mediaServerItem.setAutoConfig(autoConfig);
        mediaServerItem.setSecret(secret);
        mediaServerItem.setStreamNoneReaderDelayMS(streamNoneReaderDelayMS);
        mediaServerItem.setRtpEnable(rtpEnable);
        mediaServerItem.setRtpPortRange(rtpPortRange);
        mediaServerItem.setRecordAssistPort(recordAssistPort);
        mediaServerItem.setCreateTime(createTime);
        mediaServerItem.setUpdateTime(updateTime);
        mediaServerItem.setCount(count);
        return mediaServerItem;
    }
    @Override
    public String getUpdateTime() {
        return updateTime;
    }
    @Override
    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }
    @Override
    public String getCreateTime() {
        return createTime;
    }
    @Override
    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
    @Override
    public int getCount() {
        return count;
    }
    @Override
    public void setCount(int count) {
        this.count = count;
    }
}
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
@@ -1,12 +1,16 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.catalina.connector.ClientAbortException;
import org.mitre.dsmiley.httpproxy.ProxyServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -24,13 +28,16 @@
    private final static Logger logger = LoggerFactory.getLogger(ProxyServletConfig.class);
    @Autowired
    private MediaConfig mediaConfig;
    private IMediaServerService mediaServerService;
    @Value("${server.port}")
    private int serverPort;
    @Bean
    public ServletRegistrationBean zlmServletRegistrationBean(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new ZLMProxySerlet(),"/zlm/*");
        servletRegistrationBean.setName("zlm_Proxy");
        servletRegistrationBean.addInitParameter("targetUri", String.format("http://%s:%s", mediaConfig.getIp(), mediaConfig.getHttpPort()));
        servletRegistrationBean.addInitParameter("targetUri", "http://127.0.0.1:6080");
        servletRegistrationBean.addUrlMappings();
        if (logger.isDebugEnabled()) {
            servletRegistrationBean.addInitParameter("log", "true");
@@ -38,24 +45,26 @@
        return servletRegistrationBean;
    }
    class  ZLMProxySerlet extends ProxyServlet{
    class ZLMProxySerlet extends ProxyServlet{
        @Override
        protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) {
            String queryStr = super.rewriteQueryStringFromRequest(servletRequest, queryString);
            if (!StringUtils.isEmpty(queryStr)) {
                queryStr += "&secret=" + mediaConfig.getSecret();
            }else {
                queryStr = "secret=" + mediaConfig.getSecret();
            IMediaServerItem mediaInfo = getMediaInfoByUri(servletRequest.getRequestURI());
            if (mediaInfo != null) {
                if (!StringUtils.isEmpty(queryStr)) {
                    queryStr += "&secret=" + mediaInfo.getSecret();
                }else {
                    queryStr = "secret=" + mediaInfo.getSecret();
                }
            }
            return queryStr;
        }
        /**
         * 异常处理
         */
        @Override
        protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResonse, Exception e){
            //System.out.println(e.getMessage());
            try {
                super.handleRequestException(proxyRequest, proxyResonse, e);
            } catch (ServletException servletException) {
@@ -72,6 +81,64 @@
                logger.error("zlm 代理失败: ", e);
            }
        }
        /**
         * 对于为按照格式请求的可以直接返回404
         */
        @Override
        protected String getTargetUri(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            String uri = null;
            if (mediaInfo != null) {
//                String realRequestURI = requestURI.substring(requestURI.indexOf(mediaInfo.getId())+ mediaInfo.getId().length());
                uri = String.format("http://%s:%s", mediaInfo.getIp(), mediaInfo.getHttpPort());
            }else {
                uri = "http://127.0.0.1:" + serverPort +"/index/hook/null"; // 只是一个能返回404的请求而已, 其他的也可以
            }
            return uri;
        }
        /**
         * 动态替换请求目标
         */
        @Override
        protected HttpHost getTargetHost(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            HttpHost host;
            if (mediaInfo != null) {
                host = new HttpHost(mediaInfo.getIp(), mediaInfo.getHttpPort());
            }else {
                host = new HttpHost("127.0.0.1", serverPort);
            }
            return host;
        }
        /**
         * 根据uri获取流媒体信息
         */
        IMediaServerItem getMediaInfoByUri(String uri){
            String[] split = uri.split("/");
            String mediaServerId = split[2];
            return mediaServerService.getOne(mediaServerId);
        }
        /**
         * 去掉url中的标志信息
         */
        @Override
        protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            String url = super.rewriteUrlFromRequest(servletRequest);
            if (mediaInfo == null) {
                return  url;
            }
            return url.replace(mediaInfo.getId() + "/", "");
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -95,20 +95,43 @@
            tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "TCP");
            tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
            tcpSipProvider.addSipListener(this);
            logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}");
        } catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) {
            logger.error(String.format("创建SIP服务失败: %s", e.getMessage()));
            logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getMonitorIp() + ":" + sipConfig.getSipPort() + "}");
//        } catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) {
//            logger.error(String.format("创建SIP服务失败: %s", e.getMessage()));
//        }
        } catch (TransportNotSupportedException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
            logger.error("无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
                    , sipConfig.getMonitorIp(), sipConfig.getSipPort());
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        } catch (ObjectInUseException e) {
            e.printStackTrace();
        }
        return tcpSipProvider;
    }
    
    @Bean("udpSipProvider")
    @DependsOn("sipStack")
    private SipProvider startUdpListener() throws Exception {
        ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "UDP");
        SipProvider udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
        udpSipProvider.addSipListener(this);
        logger.info("Sip Server UDP 启动成功 port {" + sipConfig.getSipPort() + "}");
    private SipProvider startUdpListener() {
        ListeningPoint udpListeningPoint = null;
        SipProvider udpSipProvider = null;
        try {
            udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "UDP");
            udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
            udpSipProvider.addSipListener(this);
        } catch (TransportNotSupportedException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
            logger.error("无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
                    , sipConfig.getMonitorIp(), sipConfig.getSipPort());
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        } catch (ObjectInUseException e) {
            e.printStackTrace();
        }
        logger.info("Sip Server UDP 启动成功 port [" + sipConfig.getMonitorIp() + ":" + sipConfig.getSipPort() + "]");
        return udpSipProvider;
    }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
@@ -94,6 +94,11 @@
     */
    private String updateTime;
    /**
     * 设备使用的媒体id, 默认为null
     */
    private String mediaServerId;
    public String getDeviceId() {
        return deviceId;
    }
@@ -229,4 +234,12 @@
    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }
    public String getMediaServerId() {
        return mediaServerId;
    }
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java
@@ -9,6 +9,7 @@
    private String stream;
    private String gbId;
    private String name;
    private String mediaServerId;
    private double longitude;
    private double latitude;
    private String streamType;
@@ -77,4 +78,12 @@
    public void setStatus(boolean status) {
        this.status = status;
    }
    public String getMediaServerId() {
        return mediaServerId;
    }
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
@@ -66,6 +66,11 @@
     */
    private int localPort;
    /**
     * 使用的流媒体
     */
    private String mediaServerId;
    public String getIp() {
        return ip;
    }
@@ -161,4 +166,12 @@
    public void setTcpActive(boolean tcpActive) {
        this.tcpActive = tcpActive;
    }
    public String getMediaServerId() {
        return mediaServerId;
    }
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java
@@ -6,6 +6,9 @@
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
@@ -32,6 +35,8 @@
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private SIPCommanderFroPlatform sipCommanderFroPlatform;
@@ -62,22 +67,24 @@
            logger.info("停止[ {} ]的所有推流", event.getPlatformGbID());
            StringBuilder app = new StringBuilder();
            StringBuilder stream = new StringBuilder();
            for (int i = 0; i < sendRtpItems.size(); i++) {
            for (SendRtpItem sendRtpItem : sendRtpItems) {
                if (app.length() != 0) {
                    app.append(",");
                }
                app.append(sendRtpItems.get(i).getApp());
                app.append(sendRtpItem.getApp());
                if (stream.length() != 0) {
                    stream.append(",");
                }
                stream.append(sendRtpItems.get(i).getStreamId());
                redisCatchStorage.deleteSendRTPServer(event.getPlatformGbID(), sendRtpItems.get(i).getChannelId());
                stream.append(sendRtpItem.getStreamId());
                redisCatchStorage.deleteSendRTPServer(event.getPlatformGbID(), sendRtpItem.getChannelId());
                IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                Map<String, Object> param = new HashMap<>();
                param.put("vhost", "__defaultVhost__");
                param.put("app", app.toString());
                param.put("stream", stream.toString());
                zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
            }
            Map<String, Object> param = new HashMap<>();
            param.put("vhost","__defaultVhost__");
            param.put("app", app.toString());
            param.put("stream", stream.toString());
            zlmrtpServerFactory.stopSendRtpStream(param);
        }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
@@ -9,6 +9,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
import com.genersoft.iot.vmp.service.IPlayService;
@@ -104,6 +105,9 @@
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private IMediaServerService mediaServerService;
    // 注:这里使用注解会导致循环依赖注入,暂用springBean
    private SipProvider tcpSipProvider;
@@ -128,6 +132,7 @@
            processor.setStorager(storager);
            processor.setRedisCatchStorage(redisCatchStorage);
            processor.setZlmrtpServerFactory(zlmrtpServerFactory);
            processor.setMediaServerService(mediaServerService);
            return processor;
        } else if (Request.REGISTER.equals(method)) {
            RegisterRequestProcessor processor = new RegisterRequestProcessor();
@@ -148,6 +153,7 @@
            processor.setRequestEvent(evt);
            processor.setRedisCatchStorage(redisCatchStorage);
            processor.setZlmrtpServerFactory(zlmrtpServerFactory);
            processor.setMediaServerService(mediaServerService);
            return processor;
        } else if (Request.BYE.equals(method)) {
            ByeRequestProcessor processor = new ByeRequestProcessor();
@@ -155,6 +161,7 @@
            processor.setRedisCatchStorage(redisCatchStorage);
            processor.setZlmrtpServerFactory(zlmrtpServerFactory);
            processor.setSIPCommander(cmder);
            processor.setMediaServerService(mediaServerService);
            return processor;
        } else if (Request.CANCEL.equals(method)) {
            CancelRequestProcessor processor = new CancelRequestProcessor();
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -3,6 +3,8 @@
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
/**    
 * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -90,7 +92,7 @@
     * @param device  视频设备
     * @param channelId  预览通道
     */
    void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    void playStreamCmd(IMediaServerItem mediaServerItem, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    
    /**
     * 请求回放视频流
@@ -100,7 +102,7 @@
     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */
    void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    
    /**
     * 视频流停止
@@ -288,12 +290,4 @@
     * @return                true = 命令发送成功
     */
    boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime);
    /**
     * 释放rtpserver
     * @param device
     * @param channelId
     */
    void closeRTPServer(Device device, String channelId);
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -13,11 +13,10 @@
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import gov.nist.javax.sip.message.SIPRequest;
@@ -37,6 +36,7 @@
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import org.springframework.util.StringUtils;
/**    
 * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -76,12 +76,6 @@
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private MediaConfig mediaConfig;
    @Autowired
    private UserSetup userSetup;
@@ -340,48 +334,45 @@
      * @param errorEvent sip错误订阅
      */
    @Override
    public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
    public void playStreamCmd(IMediaServerItem mediaServerItem, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
        String streamId = null;
        try {
            if (device == null) return;
            String streamMode = device.getStreamMode().toUpperCase();
            String ssrc = streamSession.createPlaySsrc();
            if (mediaConfig.isRtpEnable()) {
            if (mediaServerItem.isRtpEnable()) {
                streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            }else {
                streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
            }
            String streamMode = device.getStreamMode().toUpperCase();
            ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
            if (mediaInfo == null) {
                logger.warn("点播时发现ZLM尚未连接...");
                return;
            }
            Integer mediaPort = null;
            // 使用动态udp端口
            if (mediaConfig.isRtpEnable()) {
                mediaPort = zlmrtpServerFactory.createRTPServer(streamId);
            if (mediaServerItem.isRtpEnable()) {
                mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId);
            }else {
                mediaPort = mediaInfo.getRtpProxyPort();
                mediaPort = mediaServerItem.getRtpProxyPort();
            }
            logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), mediaPort);
            // 添加订阅
            JSONObject subscribeKey = new JSONObject();
            subscribeKey.put("app", "rtp");
            subscribeKey.put("stream", streamId);
            subscribeKey.put("regist", true);
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, json->{
            subscribeKey.put("mediaServerId", mediaServerItem.getId());
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
                    (IMediaServerItem mediaServerItemInUse, JSONObject json)->{
                if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
                event.response(json);
                event.response(mediaServerItemInUse, json);
                subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
            });
            //
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
//            content.append("o=" + sipConfig.getSipId() + " 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
            content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
            content.append("o="+"00000"+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
            content.append("s=Play\r\n");
            content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
            content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
            content.append("t=0 0\r\n");
            if (userSetup.isSeniorSdp()) {
@@ -459,21 +450,32 @@
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */ 
    @Override
    public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
    public void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
            , SipSubscribe.Event errorEvent) {
        try {
            ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
            String ssrc = streamSession.createPlayBackSsrc();
            String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
            Integer mediaPort = null;
            // 使用动态udp端口
            if (mediaServerItem.isRtpEnable()) {
                mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId);
            }else {
                mediaPort = mediaServerItem.getRtpProxyPort();
            }
            logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), mediaPort);
            // 添加订阅
            JSONObject subscribeKey = new JSONObject();
            subscribeKey.put("app", "rtp");
            subscribeKey.put("stream", streamId);
            subscribeKey.put("regist", true);
            subscribeKey.put("mediaServerId", mediaServerItem.getId());
            logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString());
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, json->{
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
                    (IMediaServerItem mediaServerItemInUse, JSONObject json)->{
                if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
                event.response(json);
                event.response(mediaServerItemInUse, json);
                subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
            });
@@ -482,16 +484,12 @@
            content.append("o="+sipConfig.getSipId()+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
            content.append("s=Playback\r\n");
            content.append("u="+channelId+":0\r\n");
            content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
            content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
            content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "
                    +DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
            Integer mediaPort = null;
            // 使用动态udp端口
            if (mediaConfig.isRtpEnable()) {
                mediaPort = zlmrtpServerFactory.createRTPServer(streamId);
            }else {
                mediaPort = mediaInfo.getRtpProxyPort();
            }
            String streamMode = device.getStreamMode().toUpperCase();
            if (userSetup.isSeniorSdp()) {
@@ -560,56 +558,63 @@
    /**
     * 视频流停止
     *
     * 视频流停止, 不使用回调
     */
    @Override
    public void streamByeCmd(String deviceId, String channelId) {
        streamByeCmd(deviceId, channelId, null);
    }
    /**
     * 视频流停止
     */
    @Override
    public void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent) {
        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
        try {
            ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId);
            // 服务重启后, 无法直接发送bye, 通过手动构建发送
//            if (transaction == null) {
//
//                if (streamInfo != null) {
//                    MediaServerItem mediaServerItem = redisCatchStorage.getMediaInfo(streamInfo.getMediaServerId());
//                    JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaServerItem,streamInfo.getApp(), streamInfo.getStreamId());
//                    if (mediaList != null) { // 仍在推流才发送
//                        if (mediaList.getInteger("code") == 0) {
//                            JSONArray data = mediaList.getJSONArray("data");
//                            if (data != null && data.size() > 0) {
//                                Device device = storager.queryVideoDevice(deviceId);
//                                if (device != null) {
//                                    StreamInfo.TransactionInfo transactionInfo = streamInfo.getTransactionInfo();
//                                    try {
//                                        Request byteRequest = headerProvider.createByteRequest(device, channelId,
//                                                transactionInfo.branch,
//                                                transactionInfo.localTag,
//                                                transactionInfo.remoteTag,
//                                                transactionInfo.callId);
//                                        transmitRequest(device, byteRequest);
//                                    } catch (InvalidArgumentException e) {
//                                        e.printStackTrace();
//                                    }
//                                }
//                            }
//                        }
//                    }
//                    redisCatchStorage.stopPlay(streamInfo);
//                }
//
//                if (okEvent != null) {
//                    okEvent.response(null);
//                }
//                return;
//            }
            if (transaction == null) {
                StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
                if (streamInfo != null) {
                    JSONObject mediaList = zlmresTfulUtils.getMediaList(streamInfo.getApp(), streamInfo.getStreamId());
                    if (mediaList != null) { // 仍在推流才发送
                        if (mediaList.getInteger("code") == 0) {
                            JSONArray data = mediaList.getJSONArray("data");
                            if (data != null && data.size() > 0) {
                                Device device = storager.queryVideoDevice(deviceId);
                                if (device != null) {
                                    StreamInfo.TransactionInfo transactionInfo = streamInfo.getTransactionInfo();
                                    try {
                                        Request byteRequest = headerProvider.createByteRequest(device, channelId,
                                                transactionInfo.branch,
                                                transactionInfo.localTag,
                                                transactionInfo.remoteTag,
                                                transactionInfo.callId);
                                        transmitRequest(device, byteRequest);
                                    } catch (InvalidArgumentException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    }
                    redisCatchStorage.stopPlay(streamInfo);
                }
                if (okEvent != null) {
                    okEvent.response(null);
                }
                logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
                return;
            }
            Dialog dialog = transaction.getDialog();
            if (dialog == null) {
                logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
                return;
            }
            Request byeRequest = dialog.createRequest(Request.BYE);
@@ -632,7 +637,7 @@
            }
            dialog.sendRequest(clientTransaction);
            zlmrtpServerFactory.closeRTPServer(streamSession.getStreamId(deviceId, channelId));
            streamSession.remove(deviceId, channelId);
        } catch (SipException | ParseException e) {
            e.printStackTrace();
@@ -721,7 +726,7 @@
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
            if (StringUtils.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -821,16 +826,16 @@
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
            if (!XmlUtil.isEmpty(alarmMethod) || !XmlUtil.isEmpty(alarmType)) {
            if (!StringUtils.isEmpty(alarmMethod) || !StringUtils.isEmpty(alarmType)) {
                cmdXml.append("<Info>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod)) {
            if (!StringUtils.isEmpty(alarmMethod)) {
                cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmType)) {
            if (!StringUtils.isEmpty(alarmType)) {
                cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod) || !XmlUtil.isEmpty(alarmType)) {
            if (!StringUtils.isEmpty(alarmMethod) || !StringUtils.isEmpty(alarmType)) {
                cmdXml.append("</Info>\r\n");
            }
            cmdXml.append("</Control>\r\n");
@@ -863,7 +868,7 @@
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
            if (StringUtils.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -901,7 +906,7 @@
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
            if (StringUtils.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -969,13 +974,13 @@
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
            if (StringUtils.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("<BasicParam>\r\n");
            if (!XmlUtil.isEmpty(name)) {
            if (!StringUtils.isEmpty(name)) {
                cmdXml.append("<Name>" + name + "</Name>\r\n");
            }
            if (NumericUtil.isInteger(expiration)) {
@@ -1169,22 +1174,22 @@
            cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            if (!XmlUtil.isEmpty(startPriority)) {
            if (!StringUtils.isEmpty(startPriority)) {
                cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(endPriority)) {
            if (!StringUtils.isEmpty(endPriority)) {
                cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod)) {
            if (!StringUtils.isEmpty(alarmMethod)) {
                cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmType)) {
            if (!StringUtils.isEmpty(alarmType)) {
                cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
            }
            if (!XmlUtil.isEmpty(startTime)) {
            if (!StringUtils.isEmpty(startTime)) {
                cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
            }
            if (!XmlUtil.isEmpty(endTime)) {
            if (!StringUtils.isEmpty(endTime)) {
                cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
            }
            cmdXml.append("</Query>\r\n");
@@ -1218,7 +1223,7 @@
            cmdXml.append("<Query>\r\n");
            cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
            if (StringUtils.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -1253,7 +1258,7 @@
            cmdXml.append("<Query>\r\n");
            cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
            if (StringUtils.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -1365,22 +1370,22 @@
            cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            if (!XmlUtil.isEmpty(startPriority)) {
            if (!StringUtils.isEmpty(startPriority)) {
                cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(endPriority)) {
            if (!StringUtils.isEmpty(endPriority)) {
                cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod)) {
            if (!StringUtils.isEmpty(alarmMethod)) {
                cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmType)) {
            if (!StringUtils.isEmpty(alarmType)) {
                cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
            }
            if (!XmlUtil.isEmpty(startTime)) {
            if (!StringUtils.isEmpty(startTime)) {
                cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
            }
            if (!XmlUtil.isEmpty(endTime)) {
            if (!StringUtils.isEmpty(endTime)) {
                cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
            }
            cmdXml.append("</Query>\r\n");
@@ -1430,17 +1435,5 @@
        clientTransaction.sendRequest();
        return clientTransaction;
    }
    @Override
    public void closeRTPServer(Device device, String channelId) {
        if (mediaConfig.isRtpEnable()) {
            String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            zlmrtpServerFactory.closeRTPServer(streamId);
        }
        streamSession.remove(device.getDeviceId(), channelId);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/SIPRequestAbstractProcessor.java
@@ -16,6 +16,8 @@
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.stack.SIPServerTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**    
 * @Description:处理接收IPCamera发来的SIP协议请求消息
@@ -23,6 +25,8 @@
 * @date:   2020年5月3日 下午4:42:22     
 */
public abstract class SIPRequestAbstractProcessor implements ISIPRequestProcessor {
    private final static Logger logger = LoggerFactory.getLogger(SIPRequestAbstractProcessor.class);
    protected RequestEvent evt;
    
@@ -64,9 +68,9 @@
                    }
                }
            } catch (TransactionAlreadyExistsException e) {
                e.printStackTrace();
                logger.error(e.getMessage());
            } catch (TransactionUnavailableException e) {
                e.printStackTrace();
                logger.error(e.getMessage());
            }
        }
        return serverTransaction;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java
@@ -13,6 +13,9 @@
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,6 +32,8 @@
    private IRedisCatchStorage redisCatchStorage;
    private ZLMRTPServerFactory zlmrtpServerFactory;
    private IMediaServerService mediaServerService;
    /**   
     * 处理  ACK请求
@@ -76,18 +81,22 @@
            while (!rtpPushed) {
                try {
                    if (System.currentTimeMillis() - startTime < 30 * 1000) {
                        if (zlmrtpServerFactory.isStreamReady(streamInfo.getApp(), streamInfo.getStreamId())) {
                        IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                        if (zlmrtpServerFactory.isStreamReady(mediaInfo, streamInfo.getApp(), streamInfo.getStreamId())) {
                            rtpPushed = true;
                            logger.info("已获取设备推流,开始向上级推流");
                            zlmrtpServerFactory.startSendRtpStream(param);
                            logger.info("已获取设备推流[{}/{}],开始向上级推流[{}:{}]",
                                    streamInfo.getApp() ,streamInfo.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort());
                            zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
                        } else {
                            logger.info("等待设备推流.......");
                            logger.info("等待设备推流[{}/{}].......",
                                    streamInfo.getApp() ,streamInfo.getStreamId());
                            Thread.sleep(1000);
                            continue;
                        }
                    } else {
                        rtpPushed = true;
                        logger.info("设备推流超时,终止向上级推流");
                        logger.info("设备推流[{}/{}]超时,终止向上级推流",
                                streamInfo.getApp() ,streamInfo.getStreamId());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
@@ -123,4 +132,12 @@
    public void setZlmrtpServerFactory(ZLMRTPServerFactory zlmrtpServerFactory) {
        this.zlmrtpServerFactory = zlmrtpServerFactory;
    }
    public IMediaServerService getMediaServerService() {
        return mediaServerService;
    }
    public void setMediaServerService(IMediaServerService mediaServerService) {
        this.mediaServerService = mediaServerService;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
@@ -15,6 +15,9 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,6 +41,8 @@
    private ZLMRTPServerFactory zlmrtpServerFactory;
    private IMediaServerService mediaServerService;
    /**
     * 处理BYE请求
     * @param evt
@@ -60,9 +65,10 @@
                param.put("stream",streamId);
                param.put("ssrc",sendRtpItem.getSsrc());
                logger.info("停止向上级推流:" + streamId);
                zlmrtpServerFactory.stopSendRtpStream(param);
                IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
                redisCatchStorage.deleteSendRTPServer(platformGbId, channelId);
                if (zlmrtpServerFactory.totalReaderCount(sendRtpItem.getApp(), streamId) == 0) {
                if (zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId) == 0) {
                    logger.info(streamId + "无其它观看者,通知设备停止推流");
                    cmder.streamByeCmd(sendRtpItem.getDeviceId(), channelId);
                }
@@ -112,4 +118,11 @@
        this.cmder = cmder;
    }
    public IMediaServerService getMediaServerService() {
        return mediaServerService;
    }
    public void setMediaServerService(IMediaServerService mediaServerService) {
        this.mediaServerService = mediaServerService;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
@@ -11,12 +11,16 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
@@ -50,6 +54,8 @@
    private IPlayService playService;
    private ZLMRTPServerFactory zlmrtpServerFactory;
    private IMediaServerService mediaServerService;
    public ZLMRTPServerFactory getZlmrtpServerFactory() {
        return zlmrtpServerFactory;
@@ -91,6 +97,7 @@
                // 查询平台下是否有该通道
                DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
                GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId);
                IMediaServerItem mediaServerItem = null;
                // 不是通道可能是直播流
                if (channel != null && gbStream == null ) {
                    if (channel.getStatus() == 0) {
@@ -100,8 +107,15 @@
                    }
                    responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
                }else if(channel == null && gbStream != null){
                    Boolean streamReady = zlmrtpServerFactory.isStreamReady(gbStream.getApp(), gbStream.getStream());
                    if (!streamReady) {
                    String mediaServerId = gbStream.getMediaServerId();
                    mediaServerItem = mediaServerService.getOne(mediaServerId);
                    if (mediaServerItem == null) {
                        logger.info("[ app={}, stream={} ]zlm找不到,返回410",gbStream.getApp(), gbStream.getStream());
                        responseAck(evt, Response.GONE, "media server not found");
                        return;
                    }
                    Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
                    if (!streamReady ) {
                        logger.info("[ app={}, stream={} ]通道离线,返回400",gbStream.getApp(), gbStream.getStream());
                        responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
                        return;
@@ -130,8 +144,8 @@
                //boolean recvonly = false;
                boolean mediaTransmissionTCP = false;
                Boolean tcpActive = null;
                for (int i = 0; i < mediaDescriptions.size(); i++) {
                    MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
                for (Object description : mediaDescriptions) {
                    MediaDescription mediaDescription = (MediaDescription) description;
                    Media media = mediaDescription.getMedia();
                    Vector mediaFormats = media.getMediaFormats(false);
@@ -147,7 +161,7 @@
                                mediaTransmissionTCP = true;
                                if ("active".equals(setup)) {
                                    tcpActive = true;
                                }else if ("passive".equals(setup)) {
                                } else if ("passive".equals(setup)) {
                                    tcpActive = false;
                                }
                            }
@@ -174,7 +188,13 @@
                        responseAck(evt, Response.SERVER_INTERNAL_ERROR);
                        return;
                    }
                    SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId,
                    mediaServerItem = playService.getNewMediaServerItem(device);
                    if (mediaServerItem == null) {
                        logger.warn("未找到可用的zlm");
                        responseAck(evt, Response.BUSY_HERE);
                        return;
                    }
                    SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                            device.getDeviceId(), channelId,
                            mediaTransmissionTCP);
                    if (tcpActive != null) {
@@ -189,18 +209,18 @@
                    // 写入redis, 超时时回复
                    redisCatchStorage.updateSendRTPSever(sendRtpItem);
                    // 通知下级推流,
                    PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{
                    PlayResult playResult = playService.play(mediaServerItem,device.getDeviceId(), channelId, (mediaServerItemInUSe, responseJSON)->{
                        // 收到推流, 回复200OK, 等待ack
                        // if (sendRtpItem == null) return;
                        sendRtpItem.setStatus(1);
                        redisCatchStorage.updateSendRTPSever(sendRtpItem);
                        // TODO 添加对tcp的支持
                        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
                        StringBuffer content = new StringBuffer(200);
                        content.append("v=0\r\n");
                        content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
                        content.append("o="+"00000"+" 0 0 IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n");
                        content.append("s=Play\r\n");
                        content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
                        content.append("c=IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n");
                        content.append("t=0 0\r\n");
                        content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
                        content.append("a=sendonly\r\n");
@@ -217,7 +237,7 @@
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    } ,(event -> {
                    } ,((event) -> {
                        // 未知错误。直接转发设备点播的错误
                        Response response = null;
                        try {
@@ -232,7 +252,7 @@
                    }
                }else if (gbStream != null) {
                    SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId,
                    SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                            gbStream.getApp(), gbStream.getStream(), channelId,
                            mediaTransmissionTCP);
@@ -251,12 +271,11 @@
                    sendRtpItem.setStatus(1);
                    redisCatchStorage.updateSendRTPSever(sendRtpItem);
                    // TODO 添加对tcp的支持
                    ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
                    StringBuffer content = new StringBuffer(200);
                    content.append("v=0\r\n");
                    content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
                    content.append("o="+"00000"+" 0 0 IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
                    content.append("s=Play\r\n");
                    content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
                    content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
                    content.append("t=0 0\r\n");
                    content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
                    content.append("a=sendonly\r\n");
@@ -444,4 +463,12 @@
    public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
        this.redisCatchStorage = redisCatchStorage;
    }
    public IMediaServerService getMediaServerService() {
        return mediaServerService;
    }
    public void setMediaServerService(IMediaServerService mediaServerService) {
        this.mediaServerService = mediaServerService;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -282,7 +282,7 @@
            //String result = XmlUtil.getText(rootElement, "Result");
            // 回复200 OK
            responseAck(evt);
            if (rootElement.getName().equals("Response")) {//} !XmlUtil.isEmpty(result)) {
            if (rootElement.getName().equals("Response")) {//} !StringUtils.isEmpty(result)) {
                // 此处是对本平台发出DeviceControl指令的应答
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
@@ -299,7 +299,7 @@
                String platformId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
                String targetGBId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
                // 远程启动功能
                if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
                if (!StringUtils.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
                    if (deviceId.equals(targetGBId)) {
                        // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
                        logger.info("执行远程启动本平台命令");
@@ -337,7 +337,7 @@
                    }
                }
                // 云台/前端控制命令
                if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
                if (!StringUtils.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
                    String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
                    Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
                    cmder.fronEndCmd(device, deviceId, cmdString);
@@ -421,7 +421,7 @@
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);
            if (rootElement.getName().equals("Response")) {//   !XmlUtil.isEmpty(result)) {
            if (rootElement.getName().equals("Response")) {//   !StringUtils.isEmpty(result)) {
                // 此处是对本平台发出DeviceControl指令的应答
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
@@ -718,7 +718,7 @@
                    deviceAlarm.setLatitude(0.00);
                }
    
                if (!XmlUtil.isEmpty(deviceAlarm.getAlarmMethod())) {
                if (!StringUtils.isEmpty(deviceAlarm.getAlarmMethod())) {
                    if ( deviceAlarm.getAlarmMethod().equals("4")) {
                        MobilePosition mobilePosition = new MobilePosition();
                        mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
@@ -17,6 +17,7 @@
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
 * 基于dom4j的工具包
@@ -114,12 +115,12 @@
        // 如果是属性
        for (Object o : element.attributes()) {
            Attribute attr = (Attribute) o;
            if (!isEmpty(attr.getValue())) {
            if (!StringUtils.isEmpty(attr.getValue())) {
                json.put("@" + attr.getName(), attr.getValue());
            }
        }
        List<Element> chdEl = element.elements();
        if (chdEl.isEmpty() && !isEmpty(element.getText())) {// 如果没有子元素,只有一个值
        if (chdEl.isEmpty() && !StringUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值
            json.put(element.getName(), element.getText());
        }
@@ -150,7 +151,7 @@
            } else { // 子元素没有子元素
                for (Object o : element.attributes()) {
                    Attribute attr = (Attribute) o;
                    if (!isEmpty(attr.getValue())) {
                    if (!StringUtils.isEmpty(attr.getValue())) {
                        json.put("@" + attr.getName(), attr.getValue());
                    }
                }
@@ -159,12 +160,5 @@
                }
            }
        }
    }
    public static boolean isEmpty(String str) {
        if (str == null || str.trim().isEmpty() || "null".equals(str)) {
            return true;
        }
        return false;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
File was deleted
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -9,6 +9,9 @@
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.service.IPlayService;
@@ -53,10 +56,10 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    private IMediaServerService mediaServerService;
    @Autowired
    private ZLMServerManger zlmServerManger;
    private ZLMRESTfulUtils zlmresTfulUtils;
     @Autowired
     private ZLMMediaListManager zlmMediaListManager;
@@ -81,6 +84,7 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_flow_report API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -98,6 +102,7 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_http_access API 调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("err", "");
@@ -117,9 +122,14 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_play API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_play, json);
        if (subscribe != null ) {
            subscribe.response(json);
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
@@ -133,20 +143,25 @@
     */
    @ResponseBody
    @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
    public ResponseEntity<String> onPublish(@RequestBody JSONObject json){
    public ResponseEntity<String> onPublish(@RequestBody JSONObject json) {
        logger.debug("ZLM HOOK on_publish API调用,参数:" + json.toString());
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
        if (subscribe != null) subscribe.response(json);
        if (subscribe != null) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        ret.put("enableHls", true);
        ret.put("enableMP4", userSetup.isRecordPushLive());
        ret.put("enableRtxp", true);
        return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
        return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
    }
    
    /**
@@ -160,6 +175,7 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_record_mp4 API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -177,6 +193,7 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_rtsp_realm API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("realm", "");
@@ -195,6 +212,7 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_rtsp_auth API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("encrypted", false);
@@ -216,9 +234,15 @@
        // TODO 如果是带有rtpstream则开启按需拉流
        // String app = json.getString("app");
        // String stream = json.getString("stream");
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_shell_login, json);
        if (subscribe != null) subscribe.response(json);
        if (subscribe != null ) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
@@ -237,9 +261,15 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
        if (subscribe != null) subscribe.response(json);
        if (subscribe != null ) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
        }
        // 流消失移除redis play
        String app = json.getString("app");
@@ -251,6 +281,11 @@
            logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema);
        }
        if ("rtmp".equals(schema)){
            if (regist) {
                mediaServerService.addCount(mediaServerId);
            }else {
                mediaServerService.removeCount(mediaServerId);
            }
            if ("rtp".equals(app) && !regist ) {
                StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
                if (streamInfo!=null){
@@ -262,10 +297,11 @@
                }
            }else {
                if (!"rtp".equals(app) ){
                    IMediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
                    if (regist) {
                        zlmMediaListManager.addMedia(app, streamId);
                        zlmMediaListManager.addMedia(mediaServerItem, app, streamId);
                    }else {
                        zlmMediaListManager.removeMedia(app, streamId);
                        zlmMediaListManager.removeMedia( app, streamId);
                    }
                }
            }
@@ -288,7 +324,7 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        String streamId = json.getString("stream");
        String app = json.getString("app");
@@ -329,11 +365,12 @@
    @ResponseBody
    @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
    public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json){
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
        }
        if (userSetup.isAutoApplyPlay()) {
        String mediaServerId = json.getString("mediaServerId");
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (userSetup.isAutoApplyPlay() && mediaInfo != null) {
            String app = json.getString("app");
            String streamId = json.getString("stream");
            if ("rtp".equals(app) && streamId.contains("gb_play") ) {
@@ -344,9 +381,9 @@
                    Device device = storager.queryVideoDevice(deviceId);
                    if (device != null) {
                        UUID uuid = UUID.randomUUID();
                        cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
                        cmder.playStreamCmd(mediaInfo, device, channelId, (IMediaServerItem mediaServerItemInuse, JSONObject response) -> {
                            logger.info("收到订阅消息: " + response.toJSONString());
                            playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
                            playService.onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString());
                        }, null);
                    }
@@ -367,26 +404,19 @@
     */
    @ResponseBody
    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
    public ResponseEntity<String> onServerStarted(HttpServletRequest request, @RequestBody JSONObject json){
    public ResponseEntity<String> onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
        
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_server_started API调用,参数:" + json.toString());
            logger.debug("ZLM HOOK on_server_started API调用,参数:" + jsonObject.toString());
        }
//        String data = json.getString("data");
//        List<MediaServerConfig> mediaServerConfigs = JSON.parseArray(JSON.toJSONString(json), MediaServerConfig.class);
//        MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0);
        String remoteAddr = request.getRemoteAddr();
        jsonObject.put("ip", remoteAddr);
        List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_started);
        if (subscribes != null && subscribes.size() > 0) {
        if (subscribes != null  && subscribes.size() > 0) {
            for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
                subscribe.response(json);
                subscribe.response(null, jsonObject);
            }
        }
        ZLMServerConfig ZLMServerConfig = JSON.toJavaObject(json, ZLMServerConfig.class);
        zlmServerManger.updateServerCatch(ZLMServerConfig);
        // 重新发起代理
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
@@ -1,6 +1,8 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import org.springframework.stereotype.Component;
import java.util.*;
@@ -30,7 +32,7 @@
    }
    public interface Event{
        void response(JSONObject response);
        void response(IMediaServerItem mediaServerItem, JSONObject response);
    }
    private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
@@ -2,6 +2,8 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
@@ -45,11 +47,11 @@
    private ZLMHttpHookSubscribe subscribe;
    public void updateMediaList() {
    public void updateMediaList(MediaServerItem mediaServerItem) {
        storager.clearMediaList();
        // 使用异步的当时更新媒体流列表
        zlmresTfulUtils.getMediaList((mediaList ->{
        zlmresTfulUtils.getMediaList(mediaServerItem, (mediaList ->{
            if (mediaList == null) return;
            String dataStr = mediaList.getString("data");
@@ -57,10 +59,10 @@
            Map<String, StreamPushItem> result = new HashMap<>();
            List<StreamPushItem> streamPushItems = null;
            // 获取所有的国标关联
            List<GbStream> gbStreams = gbStreamMapper.selectAll();
//            List<GbStream> gbStreams = gbStreamMapper.selectAllByMediaServerId(mediaServerItem.getId());
            if (code == 0 ) {
                if (dataStr != null) {
                    streamPushItems = streamPushService.handleJSON(dataStr);
                    streamPushItems = streamPushService.handleJSON(dataStr, mediaServerItem);
                }
            }else {
                logger.warn("更新视频流失败,错误code: " + code);
@@ -72,24 +74,27 @@
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("app", streamPushItem.getApp());
                    jsonObject.put("stream", streamPushItem.getStream());
                    subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_play,jsonObject,(response)->{
                        updateMedia(response.getString("app"), response.getString("stream"));
                    });
                    jsonObject.put("mediaServerId", mediaServerItem.getId());
                    subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_play,jsonObject,
                            (IMediaServerItem mediaServerItemInuse, JSONObject response)->{
                                updateMedia(mediaServerItem, response.getString("app"), response.getString("stream"));
                            }
                    );
                }
            }
        }));
    }
    public void addMedia(String app, String streamId) {
    public void addMedia(IMediaServerItem mediaServerItem, String app, String streamId) {
        //使用异步更新推流
        updateMedia(app, streamId);
        updateMedia(mediaServerItem, app, streamId);
    }
    public void updateMedia(String app, String streamId) {
    public void updateMedia(IMediaServerItem mediaServerItem, String app, String streamId) {
        //使用异步更新推流
        zlmresTfulUtils.getMediaList(app, streamId, "rtmp", json->{
        zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId, "rtmp", json->{
            if (json == null) return;
            String dataStr = json.getString("data");
@@ -99,7 +104,7 @@
            List<StreamPushItem> streamPushItems = null;
            if (code == 0 ) {
                if (dataStr != null) {
                    streamPushItems = streamPushService.handleJSON(dataStr);
                    streamPushItems = streamPushService.handleJSON(dataStr, mediaServerItem);
                }
            }else {
                logger.warn("更新视频流失败,错误code: " + code);
@@ -122,32 +127,32 @@
        }
    }
    public void clearAllSessions() {
        logger.info("清空所有国标相关的session");
        JSONObject allSessionJSON = zlmresTfulUtils.getAllSession();
        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
        HashSet<String> allLocalPorts = new HashSet();
        if (allSessionJSON.getInteger("code") == 0) {
            JSONArray data = allSessionJSON.getJSONArray("data");
            if (data.size() > 0) {
                for (int i = 0; i < data.size(); i++) {
                    JSONObject sessionJOSN = data.getJSONObject(i);
                    Integer local_port = sessionJOSN.getInteger("local_port");
                    if (!local_port.equals(Integer.valueOf(mediaInfo.getHttpPort())) &&
                        !local_port.equals(Integer.valueOf(mediaInfo.getHttpSSLport())) &&
                        !local_port.equals(Integer.valueOf(mediaInfo.getRtmpPort())) &&
                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspPort())) &&
                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspSSlport())) &&
                        !local_port.equals(Integer.valueOf(mediaInfo.getHookOnFlowReport()))){
                        allLocalPorts.add(sessionJOSN.getInteger("local_port") + "");
                     }
                }
            }
        }
        if (allLocalPorts.size() > 0) {
            List<String> result = new ArrayList<>(allLocalPorts);
            String localPortSStr = String.join(",", result);
            zlmresTfulUtils.kickSessions(localPortSStr);
        }
    }
//    public void clearAllSessions() {
//        logger.info("清空所有国标相关的session");
//        JSONObject allSessionJSON = zlmresTfulUtils.getAllSession();
//        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
//        HashSet<String> allLocalPorts = new HashSet();
//        if (allSessionJSON.getInteger("code") == 0) {
//            JSONArray data = allSessionJSON.getJSONArray("data");
//            if (data.size() > 0) {
//                for (int i = 0; i < data.size(); i++) {
//                    JSONObject sessionJOSN = data.getJSONObject(i);
//                    Integer local_port = sessionJOSN.getInteger("local_port");
//                    if (!local_port.equals(Integer.valueOf(mediaInfo.getHttpPort())) &&
//                        !local_port.equals(Integer.valueOf(mediaInfo.getHttpSSLport())) &&
//                        !local_port.equals(Integer.valueOf(mediaInfo.getRtmpPort())) &&
//                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspPort())) &&
//                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspSSlport())) &&
//                        !local_port.equals(Integer.valueOf(mediaInfo.getHookOnFlowReport()))){
//                        allLocalPorts.add(sessionJOSN.getInteger("local_port") + "");
//                     }
//                }
//            }
//        }
//        if (allLocalPorts.size() > 0) {
//            List<String> result = new ArrayList<>(allLocalPorts);
//            String localPortSStr = String.join(",", result);
//            zlmresTfulUtils.kickSessions(localPortSStr);
//        }
//    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
@@ -3,6 +3,8 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -21,23 +23,18 @@
    private final static Logger logger = LoggerFactory.getLogger(ZLMRESTfulUtils.class);
    @Autowired
    private MediaConfig mediaConfig;
    public interface RequestCallback{
        void run(JSONObject response);
    }
    public JSONObject sendPost(String api, Map<String, Object> param, RequestCallback callback) {
    public JSONObject sendPost(IMediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
        OkHttpClient client = new OkHttpClient();
        String url = String.format("http://%s:%s/index/api/%s",  mediaConfig.getIp(), mediaConfig.getHttpPort(), api);
        String url = String.format("http://%s:%s/index/api/%s",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
        JSONObject responseJSON = null;
        logger.debug(url);
        FormBody.Builder builder = new FormBody.Builder();
        builder.add("secret",mediaConfig.getSecret());
        builder.add("secret",mediaServerItem.getSecret());
        if (param != null && param.keySet().size() > 0) {
            for (String key : param.keySet()){
                if (param.get(key) != null) {
@@ -96,14 +93,14 @@
    }
    public void sendPostForImg(String api, Map<String, Object> param, String targetPath, String fileName) {
    public void sendPostForImg(IMediaServerItem mediaServerItem, String api, Map<String, Object> param, String targetPath, String fileName) {
        OkHttpClient client = new OkHttpClient();
        String url = String.format("http://%s:%s/index/api/%s",  mediaConfig.getIp(), mediaConfig.getHttpPort(), api);
        String url = String.format("http://%s:%s/index/api/%s",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
        JSONObject responseJSON = null;
        logger.debug(url);
        FormBody.Builder builder = new FormBody.Builder();
        builder.add("secret",mediaConfig.getSecret());
        builder.add("secret",mediaServerItem.getSecret());
        if (param != null && param.keySet().size() > 0) {
            for (String key : param.keySet()){
                if (param.get(key) != null) {
@@ -142,39 +139,39 @@
    }
    public JSONObject getMediaList(String app, String stream, String schema, RequestCallback callback){
    public JSONObject getMediaList(IMediaServerItem mediaServerItem,String app, String stream, String schema, RequestCallback callback){
        Map<String, Object> param = new HashMap<>();
        if (app != null) param.put("app",app);
        if (stream != null) param.put("stream",stream);
        if (schema != null) param.put("schema",schema);
        param.put("vhost","__defaultVhost__");
        return sendPost("getMediaList",param, callback);
        return sendPost(mediaServerItem, "getMediaList",param, callback);
    }
    public JSONObject getMediaList(String app, String stream){
        return getMediaList(app, stream,null,  null);
    public JSONObject getMediaList(IMediaServerItem mediaServerItem,String app, String stream){
        return getMediaList(mediaServerItem, app, stream,null,  null);
    }
    public JSONObject getMediaList(RequestCallback callback){
        return sendPost("getMediaList",null, callback);
    public JSONObject getMediaList(IMediaServerItem mediaServerItem,RequestCallback callback){
        return sendPost(mediaServerItem, "getMediaList",null, callback);
    }
    public JSONObject getMediaInfo(String app, String schema, String stream){
    public JSONObject getMediaInfo(IMediaServerItem mediaServerItem,String app, String schema, String stream){
        Map<String, Object> param = new HashMap<>();
        param.put("app",app);
        param.put("schema",schema);
        param.put("stream",stream);
        param.put("vhost","__defaultVhost__");
        return sendPost("getMediaInfo",param, null);
        return sendPost(mediaServerItem, "getMediaInfo",param, null);
    }
    public JSONObject getRtpInfo(String stream_id){
    public JSONObject getRtpInfo(IMediaServerItem mediaServerItem,String stream_id){
        Map<String, Object> param = new HashMap<>();
        param.put("stream_id",stream_id);
        return sendPost("getRtpInfo",param, null);
        return sendPost(mediaServerItem, "getRtpInfo",param, null);
    }
    public JSONObject addFFmpegSource(String src_url, String dst_url, String timeout_ms,
    public JSONObject addFFmpegSource(IMediaServerItem mediaServerItem,String src_url, String dst_url, String timeout_ms,
                                      boolean enable_hls, boolean enable_mp4, String ffmpeg_cmd_key){
        logger.info(src_url);
        logger.info(dst_url);
@@ -185,44 +182,44 @@
        param.put("enable_hls", enable_hls);
        param.put("enable_mp4", enable_mp4);
        param.put("ffmpeg_cmd_key", ffmpeg_cmd_key);
        return sendPost("addFFmpegSource",param, null);
        return sendPost(mediaServerItem, "addFFmpegSource",param, null);
    }
    public JSONObject delFFmpegSource(String key){
    public JSONObject delFFmpegSource(IMediaServerItem mediaServerItem,String key){
        Map<String, Object> param = new HashMap<>();
        param.put("key", key);
        return sendPost("delFFmpegSource",param, null);
        return sendPost(mediaServerItem, "delFFmpegSource",param, null);
    }
    public JSONObject getMediaServerConfig(){
        return sendPost("getServerConfig",null, null);
    public JSONObject getMediaServerConfig(IMediaServerItem mediaServerItem){
        return sendPost(mediaServerItem, "getServerConfig",null, null);
    }
    public JSONObject setServerConfig(Map<String, Object> param){
        return sendPost("setServerConfig",param, null);
    public JSONObject setServerConfig(IMediaServerItem mediaServerItem, Map<String, Object> param){
        return sendPost(mediaServerItem,"setServerConfig",param, null);
    }
    public JSONObject openRtpServer(Map<String, Object> param){
        return sendPost("openRtpServer",param, null);
    public JSONObject openRtpServer(IMediaServerItem mediaServerItem,Map<String, Object> param){
        return sendPost(mediaServerItem, "openRtpServer",param, null);
    }
    public JSONObject closeRtpServer(Map<String, Object> param) {
        return sendPost("closeRtpServer",param, null);
    public JSONObject closeRtpServer(IMediaServerItem mediaServerItem,Map<String, Object> param) {
        return sendPost(mediaServerItem, "closeRtpServer",param, null);
    }
    public JSONObject listRtpServer() {
        return sendPost("listRtpServer",null, null);
    public JSONObject listRtpServer(IMediaServerItem mediaServerItem) {
        return sendPost(mediaServerItem, "listRtpServer",null, null);
    }
    public JSONObject startSendRtp(Map<String, Object> param) {
        return sendPost("startSendRtp",param, null);
    public JSONObject startSendRtp(IMediaServerItem mediaServerItem,Map<String, Object> param) {
        return sendPost(mediaServerItem, "startSendRtp",param, null);
    }
    public JSONObject stopSendRtp(Map<String, Object> param) {
        return sendPost("stopSendRtp",param, null);
    public JSONObject stopSendRtp(IMediaServerItem mediaServerItem,Map<String, Object> param) {
        return sendPost(mediaServerItem, "stopSendRtp",param, null);
    }
    public JSONObject addStreamProxy(String app, String stream, String url, boolean enable_hls, boolean enable_mp4, String rtp_type) {
    public JSONObject addStreamProxy(IMediaServerItem mediaServerItem,String app, String stream, String url, boolean enable_hls, boolean enable_mp4, String rtp_type) {
        Map<String, Object> param = new HashMap<>();
        param.put("vhost", "__defaultVhost__");
        param.put("app", app);
@@ -231,33 +228,33 @@
        param.put("enable_hls", enable_hls?1:0);
        param.put("enable_mp4", enable_mp4?1:0);
        param.put("rtp_type", rtp_type);
        return sendPost("addStreamProxy",param, null);
        return sendPost(mediaServerItem, "addStreamProxy",param, null);
    }
    public JSONObject closeStreams(String app, String stream) {
    public JSONObject closeStreams(IMediaServerItem mediaServerItem,String app, String stream) {
        Map<String, Object> param = new HashMap<>();
        param.put("vhost", "__defaultVhost__");
        param.put("app", app);
        param.put("stream", stream);
        param.put("force", 1);
        return sendPost("close_streams",param, null);
        return sendPost(mediaServerItem, "close_streams",param, null);
    }
    public JSONObject getAllSession() {
        return sendPost("getAllSession",null, null);
    public JSONObject getAllSession(IMediaServerItem mediaServerItem) {
        return sendPost(mediaServerItem, "getAllSession",null, null);
    }
    public void kickSessions(String localPortSStr) {
    public void kickSessions(IMediaServerItem mediaServerItem, String localPortSStr) {
        Map<String, Object> param = new HashMap<>();
        param.put("local_port", localPortSStr);
        sendPost("kick_sessions",param, null);
        sendPost(mediaServerItem, "kick_sessions",param, null);
    }
    public void getSnap(String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
    public void getSnap(IMediaServerItem mediaServerItem, String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
        Map<String, Object> param = new HashMap<>();
        param.put("url", flvUrl);
        param.put("timeout_sec", timeout_sec);
        param.put("expire_sec", expire_sec);
        sendPostForImg("getSnap",param, targetPath, fileName);
        sendPostForImg(mediaServerItem, "getSnap",param, targetPath, fileName);
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
@@ -5,6 +5,8 @@
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.session.SsrcUtil;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -30,10 +32,10 @@
    private Map<String, Integer> currentStreams = null;
    public int createRTPServer(String streamId) {
    public int createRTPServer(IMediaServerItem mediaServerItem, String streamId) {
        if (currentStreams == null) {
            currentStreams = new HashMap<>();
            JSONObject jsonObject = zlmresTfulUtils.listRtpServer();
            JSONObject jsonObject = zlmresTfulUtils.listRtpServer(mediaServerItem);
            if (jsonObject != null) {
                JSONArray data = jsonObject.getJSONArray("data");
                if (data != null) {
@@ -48,7 +50,7 @@
        if (currentStreams.get(streamId) != null) {
            Map<String, Object> closeRtpServerParam = new HashMap<>();
            closeRtpServerParam.put("stream_id", streamId);
            zlmresTfulUtils.closeRtpServer(closeRtpServerParam);
            zlmresTfulUtils.closeRtpServer(mediaServerItem, closeRtpServerParam);
            currentStreams.remove(streamId);
        }
@@ -58,7 +60,7 @@
        param.put("port", newPort);
        param.put("enable_tcp", 1);
        param.put("stream_id", streamId);
        JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param);
        JSONObject jsonObject = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
        if (jsonObject != null) {
            switch (jsonObject.getInteger("code")){
@@ -68,11 +70,11 @@
                case -300: // id已经存在, 可能已经在其他端口推流
                    Map<String, Object> closeRtpServerParam = new HashMap<>();
                    closeRtpServerParam.put("stream_id", streamId);
                    zlmresTfulUtils.closeRtpServer(closeRtpServerParam);
                    zlmresTfulUtils.closeRtpServer(mediaServerItem, closeRtpServerParam);
                    result = newPort;
                    break;
                case -400: // 端口占用
                    result= createRTPServer(streamId);
                    result= createRTPServer(mediaServerItem, streamId);
                    break;
                default:
                    logger.error("创建RTP Server 失败 {}: " + jsonObject.getString("msg"), newPort);
@@ -85,20 +87,22 @@
        return result;
    }
    public boolean closeRTPServer(String streamId) {
    public boolean closeRTPServer(IMediaServerItem serverItem, String streamId) {
        boolean result = false;
        Map<String, Object> param = new HashMap<>();
        param.put("stream_id", streamId);
        JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(param);
        if (jsonObject != null ) {
            if (jsonObject.getInteger("code") == 0) {
                result = jsonObject.getInteger("hit") == 1;
        if (serverItem !=null){
            Map<String, Object> param = new HashMap<>();
            param.put("stream_id", streamId);
            JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
            if (jsonObject != null ) {
                if (jsonObject.getInteger("code") == 0) {
                    result = jsonObject.getInteger("hit") == 1;
                }else {
                    logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
                }
            }else {
                logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
                //  检查ZLM状态
                logger.error("关闭RTP Server 失败: 请检查ZLM服务");
            }
        }else {
            //  检查ZLM状态
            logger.error("关闭RTP Server 失败: 请检查ZLM服务");
        }
        return result;
    }
@@ -131,11 +135,11 @@
     * @param tcp 是否为tcp
     * @return SendRtpItem
     */
    public SendRtpItem createSendRtpItem(String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){
    public SendRtpItem createSendRtpItem(IMediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){
        String playSsrc = SsrcUtil.getPlaySsrc();
        int localPort = createRTPServer(SsrcUtil.getPlaySsrc());
        int localPort = createRTPServer(serverItem, SsrcUtil.getPlaySsrc());
        if (localPort != -1) {
            closeRTPServer(playSsrc);
            closeRTPServer(serverItem, playSsrc);
        }else {
            logger.error("没有可用的端口");
            return null;
@@ -150,6 +154,7 @@
        sendRtpItem.setTcp(tcp);
        sendRtpItem.setApp("rtp");
        sendRtpItem.setLocalPort(localPort);
        sendRtpItem.setMediaServerId(serverItem.getId());
        return sendRtpItem;
    }
@@ -163,11 +168,11 @@
     * @param tcp 是否为tcp
     * @return SendRtpItem
     */
    public SendRtpItem createSendRtpItem(String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){
    public SendRtpItem createSendRtpItem(IMediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){
        String playSsrc = SsrcUtil.getPlaySsrc();
        int localPort = createRTPServer(SsrcUtil.getPlaySsrc());
        int localPort = createRTPServer(serverItem, SsrcUtil.getPlaySsrc());
        if (localPort != -1) {
            closeRTPServer(playSsrc);
            closeRTPServer(serverItem, playSsrc);
        }else {
            logger.error("没有可用的端口");
            return null;
@@ -182,21 +187,21 @@
        sendRtpItem.setChannelId(channelId);
        sendRtpItem.setTcp(tcp);
        sendRtpItem.setLocalPort(localPort);
        sendRtpItem.setMediaServerId(serverItem.getId());
        return sendRtpItem;
    }
    /**
     * 调用zlm RESTful API —— startSendRtp
     */
    public Boolean startSendRtpStream(Map<String, Object>param) {
    public Boolean startSendRtpStream(IMediaServerItem mediaServerItem, Map<String, Object>param) {
        Boolean result = false;
        JSONObject jsonObject = zlmresTfulUtils.startSendRtp(param);
        logger.info(jsonObject.toJSONString());
        JSONObject jsonObject = zlmresTfulUtils.startSendRtp(mediaServerItem, param);
        if (jsonObject == null) {
            logger.error("RTP推流失败: 请检查ZLM服务");
        } else if (jsonObject.getInteger("code") == 0) {
            result= true;
            logger.info("RTP推流请求成功,本地推流端口:" + jsonObject.getString("local_port"));
            logger.info("RTP推流[ {}/{} ]请求成功,本地推流端口:{}" ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"));
        } else {
            logger.error("RTP推流失败: " + jsonObject.getString("msg"));
        }
@@ -206,16 +211,16 @@
    /**
     * 查询待转推的流是否就绪
     */
    public Boolean isRtpReady(String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
    public Boolean isRtpReady(MediaServerItem mediaServerItem, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtmp", streamId);
        return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
    }
    /**
     * 查询待转推的流是否就绪
     */
    public Boolean isStreamReady(String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(app, "rtmp", streamId);
    public Boolean isStreamReady(IMediaServerItem mediaServerItem, String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
        return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
    }
@@ -224,18 +229,17 @@
     * @param streamId
     * @return
     */
    public int totalReaderCount(String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(app, "rtmp", streamId);
    public int totalReaderCount(IMediaServerItem mediaServerItem, String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
        return mediaInfo.getInteger("totalReaderCount");
    }
    /**
     * 调用zlm RESTful API —— stopSendRtp
     */
    public Boolean stopSendRtpStream(Map<String, Object>param) {
    public Boolean stopSendRtpStream(IMediaServerItem mediaServerItem,Map<String, Object>param) {
        Boolean result = false;
        JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(param);
        logger.info(jsonObject.toJSONString());
        JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
        if (jsonObject == null) {
            logger.error("停止RTP推流失败: 请检查ZLM服务");
        } else if (jsonObject.getInteger("code") == 0) {
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -4,7 +4,10 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import org.slf4j.Logger;
@@ -14,10 +17,10 @@
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.print.attribute.standard.Media;
import java.util.*;
@Component
@Order(value=1)
@@ -25,140 +28,131 @@
    private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class);
     @Autowired
     private IVideoManagerStorager storager;
    @Autowired
    private MediaConfig mediaConfig;
    @Value("${server.port}")
    private String serverPort;
    @Value("${server.ssl.enabled:false}")
    private boolean sslEnabled;
    private boolean startGetMedia = false;
    private Map<String, Boolean> startGetMedia;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private ZLMMediaListManager zlmMediaListManager;
    @Autowired
    private ZLMHttpHookSubscribe hookSubscribe;
    @Autowired
    private ZLMServerManger zlmServerManger;
    @Autowired
    private IStreamProxyService streamProxyService;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private MediaConfig mediaConfig;
    @Override
    public void run(String... strings) throws Exception {
        // 订阅 zlm启动事件
        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,(response)->{
            ZLMServerConfig ZLMServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
            zLmRunning(ZLMServerConfig);
        IMediaServerItem presetMediaServer = mediaServerService.getOneByHostAndPort(
                mediaConfig.getIp(), mediaConfig.getHttpPort());
        if (presetMediaServer  != null) {
            mediaConfig.setId(presetMediaServer.getId());
            mediaServerService.update(mediaConfig);
        }
        // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,
                (IMediaServerItem mediaServerItem, JSONObject response)->{
            ZLMServerConfig zlmServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
            if (zlmServerConfig !=null ) {
                startGetMedia.remove(zlmServerConfig.getGeneralMediaServerId());
                mediaServerService.handLeZLMServerConfig(zlmServerConfig);
//                zLmRunning(zlmServerConfig);
            }
        });
        // 获取zlm信息
        logger.info("等待zlm接入...");
        startGetMedia = true;
        ZLMServerConfig ZLMServerConfig = getMediaServerConfig();
        logger.info("等待默认zlm接入...");
        if (ZLMServerConfig != null) {
            zLmRunning(ZLMServerConfig);
        // 获取所有的zlm, 并开启主动连接
        List<IMediaServerItem> all = mediaServerService.getAll();
        if (presetMediaServer == null) {
            all.add(mediaConfig.getMediaSerItem());
        }
        for (IMediaServerItem mediaServerItem : all) {
            if (startGetMedia == null) startGetMedia = new HashMap<>();
            startGetMedia.put(mediaServerItem.getId(), true);
            new Thread(() -> {
                ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem);
                if (zlmServerConfig != null) {
                    startGetMedia.remove(mediaServerItem.getId());
                    mediaServerService.handLeZLMServerConfig(zlmServerConfig);
                }
            }).start();
        }
        Timer timer = new Timer();
        // 1分钟后未连接到则不再去主动连接
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
            if (startGetMedia != null) {
                Set<String> allZlmId = startGetMedia.keySet();
                for (String id : allZlmId) {
                    logger.error("[ {} ]]主动连接失败,不再主动连接", id);
                    startGetMedia.put(id, false);
                }
            }
            }
        }, 60 * 1000 * 2);
    }
    public ZLMServerConfig getMediaServerConfig() {
        if (!startGetMedia) return null;
        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig();
    public ZLMServerConfig getMediaServerConfig(IMediaServerItem mediaServerItem) {
        if ( startGetMedia.get(mediaServerItem.getId()) == null || !startGetMedia.get(mediaServerItem.getId())) return null;
        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
        ZLMServerConfig ZLMServerConfig = null;
        if (responseJSON != null) {
            JSONArray data = responseJSON.getJSONArray("data");
            if (data != null && data.size() > 0) {
                ZLMServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
                ZLMServerConfig.setIp(mediaServerItem.getIp());
            }
        } else {
            logger.error("getMediaServerConfig失败, 1s后重试");
            logger.error("[ {} ]-[ {}:{} ]主动连接失败失败, 2s后重试",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
            try {
                Thread.sleep(1000);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ZLMServerConfig = getMediaServerConfig();
            ZLMServerConfig = getMediaServerConfig(mediaServerItem);
        }
        return ZLMServerConfig;
    }
    private void saveZLMConfig() {
        logger.info("设置zlm...");
        String protocol = sslEnabled ? "https" : "http";
        String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaConfig.getHookIp(), serverPort);
        String recordHookPrex = null;
        if (mediaConfig.getRecordAssistPort() != 0) {
            recordHookPrex = String.format("http://127.0.0.1:%s/api/record", mediaConfig.getRecordAssistPort());
        }
        Map<String, Object> param = new HashMap<>();
        param.put("api.secret",mediaConfig.getSecret()); // -profile:v Baseline
        param.put("ffmpeg.cmd","%s -fflags nobuffer -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264  -f flv %s");
        param.put("hook.enable","1");
        param.put("hook.on_flow_report","");
        param.put("hook.on_play",String.format("%s/on_play", hookPrex));
        param.put("hook.on_http_access","");
        param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
        param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
        param.put("hook.on_record_ts","");
        param.put("hook.on_rtsp_auth","");
        param.put("hook.on_rtsp_realm","");
        param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
        param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
        param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));
        param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
        param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
        param.put("hook.timeoutSec","20");
        param.put("general.streamNoneReaderDelayMS",mediaConfig.getStreamNoneReaderDelayMS());
        JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param);
        if (responseJSON != null && responseJSON.getInteger("code") == 0) {
            logger.info("设置zlm成功");
        }else {
            logger.info("设置zlm失败");
        }
    }
    /**
     * zlm 连接成功或者zlm重启后
     */
    private void zLmRunning(ZLMServerConfig zlmServerConfig){
        logger.info( "[ id: " + zlmServerConfig.getGeneralMediaServerId() + "] zlm接入成功...");
        // 关闭循环获取zlm配置
        startGetMedia = false;
        if (mediaConfig.isAutoConfig()) saveZLMConfig();
        zlmServerManger.updateServerCatch(zlmServerConfig);
        // 清空所有session
//        zlmMediaListManager.clearAllSessions();
        // 更新流列表
        zlmMediaListManager.updateMediaList();
        // 恢复流代理
        List<StreamProxyItem> streamProxyListForEnable = storager.getStreamProxyListForEnable(true);
        for (StreamProxyItem streamProxyDto : streamProxyListForEnable) {
            logger.info("恢复流代理," + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
            JSONObject jsonObject = streamProxyService.addStreamProxyToZlm(streamProxyDto);
            if (jsonObject == null) {
                // 设置为未启用
                logger.info("恢复流代理失败,请检查流地址后重新启用" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
            }else if (jsonObject.getInteger("code") != 0){ // TODO 将错误信息存入数据库, 前端展示
                logger.info("恢复流代理失败:" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream() + "[ " + JSONObject.toJSONString(jsonObject) + " ]");
                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
            }
        }
    }
//    private void zLmRunning(ZLMServerConfig zlmServerConfig){
//        logger.info( "[ id: " + zlmServerConfig.getGeneralMediaServerId() + "] zlm接入成功...");
//        // 关闭循环获取zlm配置
//        startGetMedia = false;
//        MediaServerItem mediaServerItem = new MediaServerItem(zlmServerConfig, sipIp);
//        storager.updateMediaServer(mediaServerItem);
//
//        if (mediaServerItem.isAutoConfig()) setZLMConfig(mediaServerItem);
//        zlmServerManger.updateServerCatchFromHook(zlmServerConfig);
//
//        // 清空所有session
////        zlmMediaListManager.clearAllSessions();
//
//        // 更新流列表
//        zlmMediaListManager.updateMediaList(mediaServerItem);
//        // 恢复流代理, 只查找这个这个流媒体
//        List<StreamProxyItem> streamProxyListForEnable = storager.getStreamProxyListForEnableInMediaServer(
//                mediaServerItem.getId(), true);
//        for (StreamProxyItem streamProxyDto : streamProxyListForEnable) {
//            logger.info("恢复流代理," + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
//            JSONObject jsonObject = streamProxyService.addStreamProxyToZlm(streamProxyDto);
//            if (jsonObject == null) {
//                // 设置为未启用
//                logger.info("恢复流代理失败,请检查流地址后重新启用" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
//                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
//            }
//        }
//    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java
@@ -34,11 +34,14 @@
    @JSONField(name = "general.streamNoneReaderDelayMS")
    private String generalStreamNoneReaderDelayMS;
    @JSONField(name = "ip")
    private String ip;
    private String sdpIp;
    private String streamIp;
    private String hookIp;
    private String updateTime;
@@ -66,7 +69,7 @@
    private String hookEnable;
    @JSONField(name = "hook.on_flow_report")
    private Integer hookOnFlowReport;
    private String hookOnFlowReport;
    @JSONField(name = "hook.on_http_access")
    private String hookOnHttpAccess;
@@ -117,7 +120,7 @@
    private String httpNotFound;
    @JSONField(name = "http.port")
    private Integer httpPort;
    private int httpPort;
    @JSONField(name = "http.rootPath")
    private String httpRootPath;
@@ -126,7 +129,7 @@
    private String httpSendBufSize;
    @JSONField(name = "http.sslport")
    private Integer httpSSLport;
    private int httpSSLport;
    @JSONField(name = "multicast.addrMax")
    private String multicastAddrMax;
@@ -159,10 +162,10 @@
    private String rtmpModifyStamp;
    @JSONField(name = "rtmp.port")
    private Integer rtmpPort;
    private int rtmpPort;
    @JSONField(name = "rtmp.sslport")
    private Integer rtmpSslPort;
    private int rtmpSslPort;
    @JSONField(name = "rtp.audioMtuSize")
    private String rtpAudioMtuSize;
@@ -186,7 +189,7 @@
    private String rtpProxyDumpDir;
    @JSONField(name = "rtp_proxy.port")
    private Integer rtpProxyPort;
    private int rtpProxyPort;
    @JSONField(name = "rtp_proxy.timeoutSec")
    private String rtpProxyTimeoutSec;
@@ -201,16 +204,25 @@
    private String rtspKeepAliveSecond;
    @JSONField(name = "rtsp.port")
    private Integer rtspPort;
    private int rtspPort;
    @JSONField(name = "rtsp.sslport")
    private Integer rtspSSlport;
    private int rtspSSlport;
    @JSONField(name = "shell.maxReqSize")
    private String shellMaxReqSize;
    @JSONField(name = "shell.shell")
    private String shellPhell;
    public String getHookIp() {
        return hookIp;
    }
    public void setHookIp(String hookIp) {
        this.hookIp = hookIp;
    }
    public String getApiDebug() {
        return apiDebug;
@@ -388,11 +400,11 @@
        this.hookEnable = hookEnable;
    }
    public Integer getHookOnFlowReport() {
    public String getHookOnFlowReport() {
        return hookOnFlowReport;
    }
    public void setHookOnFlowReport(Integer hookOnFlowReport) {
    public void setHookOnFlowReport(String hookOnFlowReport) {
        this.hookOnFlowReport = hookOnFlowReport;
    }
@@ -524,11 +536,11 @@
        this.httpNotFound = httpNotFound;
    }
    public Integer getHttpPort() {
    public int getHttpPort() {
        return httpPort;
    }
    public void setHttpPort(Integer httpPort) {
    public void setHttpPort(int httpPort) {
        this.httpPort = httpPort;
    }
@@ -548,11 +560,11 @@
        this.httpSendBufSize = httpSendBufSize;
    }
    public Integer getHttpSSLport() {
    public int getHttpSSLport() {
        return httpSSLport;
    }
    public void setHttpSSLport(Integer httpSSLport) {
    public void setHttpSSLport(int httpSSLport) {
        this.httpSSLport = httpSSLport;
    }
@@ -636,19 +648,19 @@
        this.rtmpModifyStamp = rtmpModifyStamp;
    }
    public Integer getRtmpPort() {
    public int getRtmpPort() {
        return rtmpPort;
    }
    public void setRtmpPort(Integer rtmpPort) {
    public void setRtmpPort(int rtmpPort) {
        this.rtmpPort = rtmpPort;
    }
    public Integer getRtmpSslPort() {
    public int getRtmpSslPort() {
        return rtmpSslPort;
    }
    public void setRtmpSslPort(Integer rtmpSslPort) {
    public void setRtmpSslPort(int rtmpSslPort) {
        this.rtmpSslPort = rtmpSslPort;
    }
@@ -708,11 +720,11 @@
        this.rtpProxyDumpDir = rtpProxyDumpDir;
    }
    public Integer getRtpProxyPort() {
    public int getRtpProxyPort() {
        return rtpProxyPort;
    }
    public void setRtpProxyPort(Integer rtpProxyPort) {
    public void setRtpProxyPort(int rtpProxyPort) {
        this.rtpProxyPort = rtpProxyPort;
    }
@@ -748,19 +760,19 @@
        this.rtspKeepAliveSecond = rtspKeepAliveSecond;
    }
    public Integer getRtspPort() {
    public int getRtspPort() {
        return rtspPort;
    }
    public void setRtspPort(Integer rtspPort) {
    public void setRtspPort(int rtspPort) {
        this.rtspPort = rtspPort;
    }
    public Integer getRtspSSlport() {
    public int getRtspSSlport() {
        return rtspSSlport;
    }
    public void setRtspSSlport(Integer rtspSSlport) {
    public void setRtspSSlport(int rtspSSlport) {
        this.rtspSSlport = rtspSSlport;
    }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerManger.java
File was deleted
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IMediaServerItem.java
New file
@@ -0,0 +1,92 @@
package com.genersoft.iot.vmp.media.zlm.dto;
public interface IMediaServerItem {
    String getId();
    void setId(String id);
    String getIp();
    void setIp(String ip);
    String getHookIp();
    void setHookIp(String hookIp);
    String getSdpIp();
    void setSdpIp(String sdpIp);
    String getStreamIp();
    void setStreamIp(String streamIp);
    int getHttpPort();
    void setHttpPort(int httpPort);
    int getHttpSSlPort();
    void setHttpSSlPort(int httpSSlPort);
    int getRtmpPort();
    void setRtmpPort(int rtmpPort);
    int getRtmpSSlPort();
    void setRtmpSSlPort(int rtmpSSlPort);
    int getRtpProxyPort();
    void setRtpProxyPort(int rtpProxyPort);
    int getRtspPort();
    void setRtspPort(int rtspPort);
    int getRtspSSLPort();
    void setRtspSSLPort(int rtspSSLPort);
    boolean isAutoConfig();
    void setAutoConfig(boolean autoConfig);
    String getSecret();
    void setSecret(String secret);
    String getStreamNoneReaderDelayMS();
    void setStreamNoneReaderDelayMS(String streamNoneReaderDelayMS);
    boolean isRtpEnable();
    void setRtpEnable(boolean rtpEnable);
    String getRtpPortRange();
    void setRtpPortRange(String rtpPortRange);
    int getRecordAssistPort();
    void setRecordAssistPort(int recordAssistPort);
    boolean isDocker();
    void setDocker(boolean docker);
    String getUpdateTime();
    void setUpdateTime(String updateTime);
    String getCreateTime();
    void setCreateTime(String createTime);
    int getCount();
    void setCount(int count);
}
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java
@@ -78,6 +78,10 @@
     */
    private String vhost;
    /**
     * 是否是docker部署, docker部署不会自动更新zlm使用的端口,需要自己手动修改
     */
    private boolean docker;
    public static class MediaTrack {
        /**
@@ -364,4 +368,12 @@
    public OriginSock getOriginSock() {
        return originSock;
    }
    public boolean isDocker() {
        return docker;
    }
    public void setDocker(boolean docker) {
        this.docker = docker;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java
New file
@@ -0,0 +1,254 @@
package com.genersoft.iot.vmp.media.zlm.dto;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import org.springframework.util.StringUtils;
public class MediaServerItem implements IMediaServerItem{
    private String id;
    private String ip;
    private String hookIp;
    private String sdpIp;
    private String streamIp;
    private int httpPort;
    private int httpSSlPort;
    private int rtmpPort;
    private int rtmpSSlPort;
    private int rtpProxyPort;
    private int rtspPort;
    private int rtspSSLPort;
    private boolean autoConfig;
    private String secret;
    private String streamNoneReaderDelayMS;
    private boolean rtpEnable;
    private String rtpPortRange;
    private int recordAssistPort;
    private String createTime;
    private String updateTime;
    private boolean docker;
    private int count;
    public MediaServerItem() {
    }
    public MediaServerItem(ZLMServerConfig zlmServerConfig, String sipIp) {
        id = zlmServerConfig.getGeneralMediaServerId();
        ip = zlmServerConfig.getIp();
        hookIp = StringUtils.isEmpty(zlmServerConfig.getHookIp())? sipIp: zlmServerConfig.getHookIp();
        sdpIp = StringUtils.isEmpty(zlmServerConfig.getSdpIp())? zlmServerConfig.getIp(): zlmServerConfig.getSdpIp();
        streamIp = StringUtils.isEmpty(zlmServerConfig.getStreamIp())? zlmServerConfig.getIp(): zlmServerConfig.getStreamIp();
        httpPort = zlmServerConfig.getHttpPort();
        httpSSlPort = zlmServerConfig.getHttpSSLport();
        rtmpPort = zlmServerConfig.getRtmpPort();
        rtmpSSlPort = zlmServerConfig.getRtmpSslPort();
        rtpProxyPort = zlmServerConfig.getRtpProxyPort();
        rtspPort = zlmServerConfig.getRtspPort();
        rtspSSLPort = zlmServerConfig.getRtspSSlport();
        autoConfig = true; // 默认值true;
        secret = zlmServerConfig.getApiSecret();
        streamNoneReaderDelayMS = zlmServerConfig.getGeneralStreamNoneReaderDelayMS();
        rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
        recordAssistPort = 0; // 默认关闭
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getIp() {
        return ip;
    }
    public void setIp(String ip) {
        this.ip = ip;
    }
    public String getHookIp() {
        return hookIp;
    }
    public void setHookIp(String hookIp) {
        this.hookIp = hookIp;
    }
    public String getSdpIp() {
        return sdpIp;
    }
    public void setSdpIp(String sdpIp) {
        this.sdpIp = sdpIp;
    }
    public String getStreamIp() {
        return streamIp;
    }
    public void setStreamIp(String streamIp) {
        this.streamIp = streamIp;
    }
    public int getHttpPort() {
        return httpPort;
    }
    public void setHttpPort(int httpPort) {
        this.httpPort = httpPort;
    }
    public int getHttpSSlPort() {
        return httpSSlPort;
    }
    public void setHttpSSlPort(int httpSSlPort) {
        this.httpSSlPort = httpSSlPort;
    }
    public int getRtmpPort() {
        return rtmpPort;
    }
    public void setRtmpPort(int rtmpPort) {
        this.rtmpPort = rtmpPort;
    }
    public int getRtmpSSlPort() {
        return rtmpSSlPort;
    }
    public void setRtmpSSlPort(int rtmpSSlPort) {
        this.rtmpSSlPort = rtmpSSlPort;
    }
    public int getRtpProxyPort() {
        return rtpProxyPort;
    }
    public void setRtpProxyPort(int rtpProxyPort) {
        this.rtpProxyPort = rtpProxyPort;
    }
    public int getRtspPort() {
        return rtspPort;
    }
    public void setRtspPort(int rtspPort) {
        this.rtspPort = rtspPort;
    }
    public int getRtspSSLPort() {
        return rtspSSLPort;
    }
    public void setRtspSSLPort(int rtspSSLPort) {
        this.rtspSSLPort = rtspSSLPort;
    }
    public boolean isAutoConfig() {
        return autoConfig;
    }
    public void setAutoConfig(boolean autoConfig) {
        this.autoConfig = autoConfig;
    }
    public String getSecret() {
        return secret;
    }
    public void setSecret(String secret) {
        this.secret = secret;
    }
    public String getStreamNoneReaderDelayMS() {
        return streamNoneReaderDelayMS;
    }
    public void setStreamNoneReaderDelayMS(String streamNoneReaderDelayMS) {
        this.streamNoneReaderDelayMS = streamNoneReaderDelayMS;
    }
    public boolean isRtpEnable() {
        return rtpEnable;
    }
    public void setRtpEnable(boolean rtpEnable) {
        this.rtpEnable = rtpEnable;
    }
    public String getRtpPortRange() {
        return rtpPortRange;
    }
    public void setRtpPortRange(String rtpPortRange) {
        this.rtpPortRange = rtpPortRange;
    }
    public int getRecordAssistPort() {
        return recordAssistPort;
    }
    public void setRecordAssistPort(int recordAssistPort) {
        this.recordAssistPort = recordAssistPort;
    }
    @Override
    public boolean isDocker() {
        return docker;
    }
    @Override
    public void setDocker(boolean docker) {
        this.docker = docker;
    }
    public String getCreateTime() {
        return createTime;
    }
    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
    public String getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java
@@ -7,6 +7,7 @@
    private String type;
    private String app;
    private String stream;
    private String mediaServerId;
    private String url;
    private String src_url;
    private String dst_url;
@@ -17,6 +18,7 @@
    private boolean enable_hls;
    private boolean enable_mp4;
    private String platformGbId;
    private String createTime;
    public String getType() {
        return type;
@@ -40,6 +42,16 @@
    public void setStream(String stream) {
        this.stream = stream;
    }
    @Override
    public String getMediaServerId() {
        return mediaServerId;
    }
    @Override
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
    public String getUrl() {
@@ -122,4 +134,12 @@
    public void setPlatformGbId(String platformGbId) {
        this.platformGbId = platformGbId;
    }
    public String getCreateTime() {
        return createTime;
    }
    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java
@@ -76,6 +76,11 @@
     */
    private String vhost;
    /**
     * 使用的流媒体ID
     */
    private String mediaServerId;
    public String getVhost() {
        return vhost;
    }
@@ -202,5 +207,14 @@
    }
    @Override
    public String getMediaServerId() {
        return mediaServerId;
    }
    @Override
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java
New file
@@ -0,0 +1,33 @@
package com.genersoft.iot.vmp.media.zlm.dto;
/**
 * 记录zlm运行中一些参数
 */
public class ZLMRunInfo {
    /**
     * zlm当前流数量
     */
    private int mediaCount;
    /**
     * 在线状态
     */
    private boolean online;
    public int getMediaCount() {
        return mediaCount;
    }
    public void setMediaCount(int mediaCount) {
        this.mediaCount = mediaCount;
    }
    public boolean isOnline() {
        return online;
    }
    public void setOnline(boolean online) {
        this.online = online;
    }
}
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
New file
@@ -0,0 +1,44 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import java.util.List;
/**
 * 媒体服务节点
 */
public interface IMediaServerService {
    List<IMediaServerItem> getAll();
    IMediaServerItem getOne(String generalMediaServerId);
    IMediaServerItem getOneByHostAndPort(String host, int port);
    /**
     * 新的节点加入
     * @param zlmServerConfig
     * @return
     */
    void handLeZLMServerConfig(ZLMServerConfig zlmServerConfig);
    void updateServerCatch(IMediaServerItem mediaServerItem, Integer count, Boolean b);
    IMediaServerItem getMediaServerForMinimumLoad();
    void setZLMConfig(IMediaServerItem mediaServerItem);
    void init();
    void closeRTPServer(Device device, String channelId);
    void update(MediaConfig mediaConfig);
    void addCount(String mediaServerId);
    void removeCount(String mediaServerId);
}
src/main/java/com/genersoft/iot/vmp/service/IMediaService.java
@@ -2,6 +2,8 @@
import com.alibaba.fastjson.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
/**
 * 媒体信息业务
@@ -14,23 +16,8 @@
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream);
    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId,String addr);
    /**
     * 根据应用名和流ID获取播放地址, 只是地址拼接
     * @param app
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks);
    /**
     * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
     * @param app
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks, String addr);
    /**
     * 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在, 返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
@@ -38,5 +25,21 @@
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String addr);
    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId);
    /**
     * 根据应用名和流ID获取播放地址, 只是地址拼接
     * @param app
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaServerItem, String app, String stream, JSONArray tracks);
    /**
     * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
     * @param app
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr);
}
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -1,8 +1,11 @@
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
/**
@@ -10,8 +13,10 @@
 */
public interface IPlayService {
    void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid);
    void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid);
    void onPublishHandlerForPlayBack(IMediaServerItem mediaServerItem,JSONObject resonse, String deviceId, String channelId, String uuid);
    void onPublishHandlerForPlay(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid);
    PlayResult play(String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    PlayResult play(IMediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    IMediaServerItem getNewMediaServerItem(Device device);
}
src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java
@@ -1,6 +1,8 @@
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.github.pagehelper.PageInfo;
@@ -61,5 +63,5 @@
     * 获取ffmpeg.cmd模板
     * @return
     */
    JSONObject getFFmpegCMDs();
    JSONObject getFFmpegCMDs(IMediaServerItem mediaServerItem);
}
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java
@@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.github.pagehelper.PageInfo;
@@ -8,7 +9,7 @@
public interface IStreamPushService {
    List<StreamPushItem> handleJSON(String json);
    List<StreamPushItem> handleJSON(String json, IMediaServerItem mediaServerItem);
    /**
     * 将应用名和流ID加入国标关联
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
New file
@@ -0,0 +1,340 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.ProxyServletConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.ZLMRunInfo;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import org.mitre.dsmiley.httpproxy.ProxyServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;
/**
 * 媒体服务器节点管理
 */
@Service
public class MediaServerServiceImpl implements IMediaServerService {
    private final static Logger logger = LoggerFactory.getLogger(MediaServerServiceImpl.class);
    private Map<String, IMediaServerItem> zlmServers = new HashMap<>(); // 所有数据库的zlm的缓存
    private Map<String, Integer> zlmServerStatus = new LinkedHashMap<>(); // 所有上线的zlm的缓存以及负载
    @Value("${sip.ip}")
    private String sipIp;
    @Value("${server.ssl.enabled:false}")
    private boolean sslEnabled;
    @Value("${server.port}")
    private String serverPort;
    @Autowired
    private MediaConfig mediaConfig;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private MediaServerMapper mediaServerMapper;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private VideoStreamSessionManager streamSession;
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 初始化
     */
    @Override
    public void init() {
        zlmServers.clear();
        zlmServerStatus.clear();
        List<MediaServerItem> mediaServerItemList = mediaServerMapper.queryAll();
        for (IMediaServerItem mediaServerItem : mediaServerItemList) {
            zlmServers.put(mediaServerItem.getId(), mediaServerItem);
        }
    }
    @Override
    public void closeRTPServer(Device device, String channelId) {
        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId);
        IMediaServerItem mediaServerItem = null;
        if (streamInfo != null) {
            mediaServerItem = this.getOne (streamInfo.getMediaServerId());
        }
        String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
        zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId);
        streamSession.remove(device.getDeviceId(), channelId);
    }
    @Override
    public void update(MediaConfig mediaConfig) {
    }
    @Override
    public List<IMediaServerItem> getAll() {
        if (zlmServers.size() == 0) {
            init();
        }
        List<IMediaServerItem> result = new ArrayList<>();
        for (String id : zlmServers.keySet()) {
            IMediaServerItem mediaServerItem = zlmServers.get(id);
            mediaServerItem.setCount(zlmServerStatus.get(id) == null ? 0 : zlmServerStatus.get(id));
            result.add(mediaServerItem);
        }
        return result;
//        return mediaServerMapper.queryAll();
    }
    /**
     * 获取单个zlm服务器
     * @param mediaServerId 服务id
     * @return MediaServerItem
     */
    @Override
    public IMediaServerItem getOne(String mediaServerId) {
        if (mediaServerId ==null) return null;
        IMediaServerItem mediaServerItem = zlmServers.get(mediaServerId);
        if (mediaServerItem != null) {
            mediaServerItem.setCount(zlmServerStatus.get(mediaServerId) == null ? 0 : zlmServerStatus.get(mediaServerId));
            return mediaServerItem;
        }else {
            IMediaServerItem item = mediaServerMapper.queryOne(mediaServerId);
            if (item != null) {
                zlmServers.put(item.getId(), item);
            }
            return item;
        }
    }
    @Override
    public IMediaServerItem getOneByHostAndPort(String host, int port) {
        return mediaServerMapper.queryOneByHostAndPort(host, port);
    }
    /**
     * 处理zlm上线
     * @param zlmServerConfig zlm上线携带的参数
     */
    @Override
    public void handLeZLMServerConfig(ZLMServerConfig zlmServerConfig) {
        logger.info("[ {} ]-[ {}:{} ]已连接",
                zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
        IMediaServerItem serverItem = getOne(zlmServerConfig.getGeneralMediaServerId());
        String now = this.format.format(new Date(System.currentTimeMillis()));
        if (serverItem != null) {
            serverItem.setSecret(zlmServerConfig.getApiSecret());
            serverItem.setIp(zlmServerConfig.getIp());
            // 如果是配置文件中的zlm。 也就是默认zlm。 一切以配置文件内容为准
            // docker部署不会使用zlm配置的端口号;
            // 直接编译部署的使用配置文件的端口号,如果zlm修改配改了配置,wvp自动修改
            if (serverItem.getId().equals(mediaConfig.getId())
                    || (serverItem.getIp().equals(mediaConfig.getIp()) && serverItem.getHttpPort() == mediaConfig.getHttpPort())) {
                // 配置文件的zlm
                mediaConfig.setId(zlmServerConfig.getGeneralMediaServerId());
                mediaConfig.setUpdateTime(now);
                if (mediaConfig.getHttpPort() == 0) mediaConfig.setHttpPort(zlmServerConfig.getHttpPort());
                if (mediaConfig.getHttpSSlPort() == 0) mediaConfig.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
                if (mediaConfig.getRtmpPort() == 0) mediaConfig.setRtmpPort(zlmServerConfig.getRtmpPort());
                if (mediaConfig.getRtmpSSlPort() == 0) mediaConfig.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
                if (mediaConfig.getRtspPort() == 0) mediaConfig.setRtspPort(zlmServerConfig.getRtspPort());
                if (mediaConfig.getRtspSSLPort() == 0) mediaConfig.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
                if (mediaConfig.getRtpProxyPort() == 0) mediaConfig.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
                mediaServerMapper.update(mediaConfig);
                serverItem = mediaConfig.getMediaSerItem();
                setZLMConfig(mediaConfig);
            }else {
                if (!serverItem.isDocker()) {
                    serverItem.setHttpPort(zlmServerConfig.getHttpPort());
                    serverItem.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
                    serverItem.setRtmpPort(zlmServerConfig.getRtmpPort());
                    serverItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
                    serverItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
                    serverItem.setRtspPort(zlmServerConfig.getRtspPort());
                }
                serverItem.setUpdateTime(now);
                mediaServerMapper.update(serverItem);
                setZLMConfig(serverItem);
            }
        }else {
            if (zlmServerConfig.getGeneralMediaServerId().equals(mediaConfig.getId())
                    || (zlmServerConfig.getIp().equals(mediaConfig.getIp()) && zlmServerConfig.getHttpPort() == mediaConfig.getHttpPort())) {
                mediaConfig.setId(zlmServerConfig.getGeneralMediaServerId());
                mediaConfig.setCreateTime(now);
                mediaConfig.setUpdateTime(now);
                serverItem = mediaConfig;
                mediaServerMapper.add(mediaConfig);
            }else {
                // 一个新的zlm接入wvp
                serverItem = new MediaServerItem(zlmServerConfig, sipIp);
                serverItem.setCreateTime(now);
                serverItem.setUpdateTime(now);
                mediaServerMapper.add(serverItem);
            }
        }
        // 更新缓存
        if (zlmServerStatus.get(serverItem.getId()) == null) {
            zlmServers.put(serverItem.getId(), serverItem);
            zlmServerStatus.put(serverItem.getId(),0);
        }
        // 查询服务流数量
        IMediaServerItem finalServerItem = serverItem;
        zlmresTfulUtils.getMediaList(serverItem, null, null, "rtmp",(mediaList ->{
            Integer code = mediaList.getInteger("code");
            if (code == 0) {
                JSONArray data = mediaList.getJSONArray("data");
                if (data != null) {
                    zlmServerStatus.put(finalServerItem.getId(),data.size());
                }else {
                    zlmServerStatus.put(finalServerItem.getId(),0);
                }
            }
        }));
    }
    /**
     * 更新缓存
     * @param mediaServerItem zlm服务
     * @param count 在线数
     * @param online 在线状态
     */
    @Override
    public void updateServerCatch(IMediaServerItem mediaServerItem, Integer count, Boolean online) {
        if (mediaServerItem != null) {
            zlmServers.put(mediaServerItem.getId(), mediaServerItem);
            Collection<Integer> values = zlmServerStatus.values();
            if (online != null && count != null) {
                zlmServerStatus.put(mediaServerItem.getId(), count);
            }
        }
    }
    @Override
    public void addCount(String mediaServerId) {
        if (zlmServerStatus.get(mediaServerId) != null) {
            zlmServerStatus.put(mediaServerId, zlmServerStatus.get(mediaServerId) + 1);
        }
    }
    @Override
    public void removeCount(String mediaServerId) {
        if (zlmServerStatus.get(mediaServerId) != null) {
            zlmServerStatus.put(mediaServerId, zlmServerStatus.get(mediaServerId) - 1);
        }
    }
    /**
     * 获取负载最低的节点
     * @return MediaServerItem
     */
    @Override
    public IMediaServerItem getMediaServerForMinimumLoad() {
        int mediaCount = -1;
        String key = null;
        System.out.println(JSON.toJSONString(zlmServerStatus));
        if (zlmServerStatus.size() == 1) {
            Map.Entry entry = zlmServerStatus.entrySet().iterator().next();
            key= (String) entry.getKey();
        }else {
            for (String id : zlmServerStatus.keySet()) {
                if (key == null) {
                    key = id;
                    mediaCount = zlmServerStatus.get(id);
                }
                if (zlmServerStatus.get(id) == 0) {
                    key = id;
                    break;
                }else if (mediaCount >= zlmServerStatus.get(id)){
                    mediaCount = zlmServerStatus.get(id);
                    key = id;
                }
            }
        }
        if (key == null) {
            logger.info("获取负载最低的节点时无在线节点");
            return null;
        }else{
            return  zlmServers.get(key);
        }
    }
    /**
     * 对zlm服务器进行基础配置
     * @param mediaServerItem 服务ID
     */
    @Override
    public void setZLMConfig(IMediaServerItem mediaServerItem) {
        logger.info("[ {} ]-[ {}:{} ]设置zlm",
                mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        String protocol = sslEnabled ? "https" : "http";
        String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
        String recordHookPrex = null;
        if (mediaServerItem.getRecordAssistPort() != 0) {
            recordHookPrex = String.format("http://127.0.0.1:%s/api/record", mediaServerItem.getRecordAssistPort());
        }
        Map<String, Object> param = new HashMap<>();
        param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
        param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264  -f flv %s");
        param.put("hook.enable","1");
        param.put("hook.on_flow_report","");
        param.put("hook.on_play",String.format("%s/on_play", hookPrex));
        param.put("hook.on_http_access","");
        param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
        param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
        param.put("hook.on_record_ts","");
        param.put("hook.on_rtsp_auth","");
        param.put("hook.on_rtsp_realm","");
        param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
        param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
        param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));
        param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
        param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
        param.put("hook.timeoutSec","20");
        param.put("general.streamNoneReaderDelayMS",mediaServerItem.getStreamNoneReaderDelayMS());
        JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaServerItem, param);
        if (responseJSON != null && responseJSON.getInteger("code") == 0) {
            logger.info("[ {} ]-[ {}:{} ]设置zlm成功",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        }else {
            logger.info("[ {} ]-[ {}:{} ]设置zlm失败" + responseJSON.getString("msg"),
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        }
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
@@ -6,6 +6,9 @@
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.service.IMediaService;
@@ -22,29 +25,52 @@
    private IVideoManagerStorager storager;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Override
    public StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks) {
        return getStreamInfoByAppAndStream(app, stream, tracks, null);
    public StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks) {
        return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null);
    }
    @Override
    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream) {
        return getStreamInfoByAppAndStreamWithCheck(app, stream, null);
    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) {
        StreamInfo streamInfo = null;
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (mediaInfo == null) {
            return streamInfo;
        }
        JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaInfo, app, stream);
        if (mediaList != null) {
            if (mediaList.getInteger("code") == 0) {
                JSONArray data = mediaList.getJSONArray("data");
                if (data == null) return null;
                JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class);
                JSONArray tracks = mediaJSON.getJSONArray("tracks");
                streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks);
            }
        }
        return streamInfo;
    }
    @Override
    public StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks, String addr) {
        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId) {
        return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null);
    }
    @Override
    public StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr) {
        StreamInfo streamInfoResult = new StreamInfo();
        streamInfoResult.setStreamId(stream);
        streamInfoResult.setApp(app);
        if (addr == null) {
            addr = mediaInfo.getStreamIp();
        }
        streamInfoResult.setMediaServerId(mediaInfo.getId());
        streamInfoResult.setRtmp(String.format("rtmp://%s:%s/%s/%s", addr, mediaInfo.getRtmpPort(), app,  stream));
        streamInfoResult.setRtsp(String.format("rtsp://%s:%s/%s/%s", addr, mediaInfo.getRtspPort(), app,  stream));
        streamInfoResult.setFlv(String.format("http://%s:%s/%s/%s.flv", addr, mediaInfo.getHttpPort(), app,  stream));
@@ -60,19 +86,4 @@
        return streamInfoResult;
    }
    @Override
    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String addr) {
        StreamInfo streamInfo = null;
        JSONObject mediaList = zlmresTfulUtils.getMediaList(app, stream);
        if (mediaList != null) {
            if (mediaList.getInteger("code") == 0) {
                JSONArray data = mediaList.getJSONArray("data");
                if (data == null) return null;
                JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class);
                JSONArray tracks = mediaJSON.getJSONArray("tracks");
                streamInfo = getStreamInfoByAppAndStream(app, stream, tracks, addr);
            }
        }
        return streamInfo;
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -14,6 +14,9 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -60,6 +63,9 @@
    private IMediaService mediaService;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private VideoStreamSessionManager streamSession;
    @Autowired
@@ -67,8 +73,18 @@
    @Override
    public PlayResult play(String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
    public PlayResult play(IMediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
        PlayResult playResult = new PlayResult();
        if (mediaServerItem == null) {
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
            WVPResult wvpResult = new WVPResult();
            wvpResult.setCode(-1);
            wvpResult.setMsg("未找到可用的zlm");
            msg.setData(wvpResult);
            resultHolder.invokeResult(msg);
            return playResult;
        }
        Device device = storager.queryVideoDevice(deviceId);
        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
        playResult.setDevice(device);
@@ -82,7 +98,7 @@
        result.onTimeout(()->{
            logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            // 释放rtpserver
            cmder.closeRTPServer(playResult.getDevice(), channelId);
            mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
            WVPResult wvpResult = new WVPResult();
@@ -115,9 +131,10 @@
                    WVPResult wvpResult = (WVPResult)responseEntity.getBody();
                    if (wvpResult.getCode() == 0) {
                        StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
                        IMediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
                        String streamUrl = streamInfoForSuccess.getFmp4();
                        // 请求截图
                        zlmresTfulUtils.getSnap(streamUrl, 15, 1, path, fileName);
                        zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
                    }
                }
            } catch (FileNotFoundException e) {
@@ -126,17 +143,17 @@
        });
        if (streamInfo == null) {
            // 发送点播消息
            cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
            cmder.playStreamCmd(mediaServerItem, device, channelId, (IMediaServerItem mediaServerItemInUse, JSONObject response) -> {
                logger.info("收到订阅消息: " + response.toJSONString());
                onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
                onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid.toString());
                if (hookEvent != null) {
                    hookEvent.response(response);
                    hookEvent.response(mediaServerItem, response);
                }
            }, event -> {
            }, (event) -> {
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                Response response = event.getResponse();
                cmder.closeRTPServer(playResult.getDevice(), channelId);
                mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
                WVPResult wvpResult = new WVPResult();
                wvpResult.setCode(-1);
                wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
@@ -158,7 +175,10 @@
                resultHolder.invokeResult(msg);
                return playResult;
            }
            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
            String mediaServerId = streamInfo.getMediaServerId();
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
            if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
@@ -171,16 +191,16 @@
                resultHolder.invokeResult(msg);
                if (hookEvent != null) {
                    hookEvent.response(JSONObject.parseObject(JSON.toJSONString(streamInfo)));
                    hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
                }
            } else {
                redisCatchStorage.stopPlay(streamInfo);
                storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
                cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
                cmder.playStreamCmd(mediaServerItem, device, channelId, (IMediaServerItem mediaServerItemInuse, JSONObject response) -> {
                    logger.info("收到订阅消息: " + response.toJSONString());
                    onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
                }, event -> {
                    cmder.closeRTPServer(playResult.getDevice(), channelId);
                    onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString());
                }, (event) -> {
                    mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
                    RequestMessage msg = new RequestMessage();
                    msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                    Response response = event.getResponse();
@@ -198,10 +218,10 @@
    }
    @Override
    public void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid) {
    public void onPublishHandlerForPlay(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
        RequestMessage msg = new RequestMessage();
        msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
        StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
        if (streamInfo != null) {
            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
            if (deviceChannel != null) {
@@ -234,10 +254,26 @@
    }
    @Override
    public void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid) {
    public IMediaServerItem getNewMediaServerItem(Device device) {
        if (device == null) return null;
        String mediaServerId = device.getMediaServerId();
        IMediaServerItem mediaServerItem = null;
        if (mediaServerId == null) {
            mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
        }else {
            mediaServerItem = mediaServerService.getOne(mediaServerId);
        }
        if (mediaServerItem == null) {
            logger.warn("点播时未找到可使用的ZLM...");
        }
        return mediaServerItem;
    }
    @Override
    public void onPublishHandlerForPlayBack(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
        RequestMessage msg = new RequestMessage();
        msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
        StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
        if (streamInfo != null) {
            redisCatchStorage.startPlayback(streamInfo);
            msg.setData(JSON.toJSONString(streamInfo));
@@ -249,10 +285,10 @@
        }
    }
    public StreamInfo onPublishHandler(JSONObject resonse, String deviceId, String channelId, String uuid) {
    public StreamInfo onPublishHandler(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
        String streamId = resonse.getString("stream");
        JSONArray tracks = resonse.getJSONArray("tracks");
        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream("rtp", streamId, tracks);
        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem,"rtp", streamId, tracks);
        streamInfo.setDeviceID(deviceId);
        streamInfo.setChannelId(channelId);
        return streamInfo;
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
@@ -2,10 +2,12 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
@@ -13,6 +15,8 @@
import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -25,6 +29,8 @@
@Service
public class StreamProxyServiceImpl implements IStreamProxyService {
    private final static Logger logger = LoggerFactory.getLogger(StreamProxyServiceImpl.class);
    @Autowired
    private IVideoManagerStorager videoManagerStorager;
@@ -32,7 +38,7 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    private ZLMRESTfulUtils zlmresTfulUtils;;
    @Autowired
    private StreamProxyMapper streamProxyMapper;
@@ -46,15 +52,28 @@
    @Autowired
    private IGbStreamService gbStreamService;
    @Autowired
    private IMediaServerService mediaServerService;
    @Override
    public String save(StreamProxyItem param) {
        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
        IMediaServerItem mediaInfo;
        if ("auto".equals(param.getMediaServerId())){
            mediaInfo = mediaServerService.getMediaServerForMinimumLoad();
        }else {
            mediaInfo = mediaServerService.getOne(param.getMediaServerId());
        }
        if (mediaInfo == null) {
            logger.warn("保存代理未找到在线的ZLM...");
            return "保存失败";
        }
        String dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(),
                param.getStream() );
        param.setDst_url(dstUrl);
        StringBuffer result = new StringBuffer();
        boolean streamLive = false;
        param.setMediaServerId(mediaInfo.getId());
        // 更新
        if (videoManagerStorager.queryStreamProxy(param.getApp(), param.getStream()) != null) {
            if (videoManagerStorager.updateStreamProxy(param)) {
@@ -81,6 +100,8 @@
                        videoManagerStorager.updateStreamProxy(param);
                    }
                }
            }else {
                result.append("保存失败");
            }
        }
@@ -99,11 +120,18 @@
    @Override
    public JSONObject addStreamProxyToZlm(StreamProxyItem param) {
        JSONObject result = null;
        IMediaServerItem mediaServerItem = null;
        if (param.getMediaServerId() == null) {
            logger.warn("添加代理时MediaServerId 为null");
            return null;
        }else {
            mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
        }
        if ("default".equals(param.getType())){
            result = zlmresTfulUtils.addStreamProxy(param.getApp(), param.getStream(), param.getUrl(),
            result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl(),
                    param.isEnable_hls(), param.isEnable_mp4(), param.getRtp_type());
        }else if ("ffmpeg".equals(param.getType())) {
            result = zlmresTfulUtils.addFFmpegSource(param.getSrc_url(), param.getDst_url(),
            result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrc_url(), param.getDst_url(),
                    param.getTimeout_ms() + "", param.isEnable_hls(), param.isEnable_mp4(),
                    param.getFfmpeg_cmd_key());
        }
@@ -112,8 +140,9 @@
    @Override
    public JSONObject removeStreamProxyFromZlm(StreamProxyItem param) {
        JSONObject result = zlmresTfulUtils.closeStreams(param.getApp(), param.getStream());
        if (param ==null) return null;
        IMediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
        JSONObject result = zlmresTfulUtils.closeStreams(mediaServerItem, param.getApp(), param.getStream());
        return result;
    }
@@ -124,17 +153,18 @@
    @Override
    public void del(String app, String stream) {
        StreamProxyItem streamProxyItem = new StreamProxyItem();
        streamProxyItem.setApp(app);
        streamProxyItem.setStream(stream);
        JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyItem);
        if (jsonObject.getInteger("code") == 0) {
        StreamProxyItem streamProxyItem = videoManagerStorager.queryStreamProxy(app, stream);
        if (streamProxyItem != null) {
            videoManagerStorager.deleteStreamProxy(app, stream);
            // 如果关联了国标那么移除关联
            gbStreamMapper.del(app, stream);
            platformGbStreamMapper.delByAppAndStream(app, stream);
            // TODO 如果关联的推流, 那么状态设置为离线
            JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyItem);
            if (jsonObject != null && jsonObject.getInteger("code") == 0) {
                // 如果关联了国标那么移除关联
                gbStreamMapper.del(app, stream);
                platformGbStreamMapper.delByAppAndStream(app, stream);
                // TODO 如果关联的推流, 那么状态设置为离线
            }
        }
    }
    @Override
@@ -168,9 +198,9 @@
    }
    @Override
    public JSONObject getFFmpegCMDs() {
    public JSONObject getFFmpegCMDs(IMediaServerItem mediaServerItem) {
        JSONObject result = new JSONObject();
        JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig();
        JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
        if (mediaServerConfigResuly != null && mediaServerConfigResuly.getInteger("code") == 0
                && mediaServerConfigResuly.getJSONArray("data").size() > 0){
            JSONObject mediaServerConfig = mediaServerConfigResuly.getJSONArray("data").getJSONObject(0);
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
@@ -5,9 +5,13 @@
import com.alibaba.fastjson.TypeReference;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.StreamPushMapper;
import com.github.pagehelper.PageHelper;
@@ -32,8 +36,14 @@
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IMediaServerService mediaServerService;
    @Override
    public List<StreamPushItem> handleJSON(String jsonData) {
    public List<StreamPushItem> handleJSON(String jsonData, IMediaServerItem mediaServerItem) {
        if (jsonData == null) return null;
        Map<String, StreamPushItem> result = new HashMap<>();
@@ -50,6 +60,7 @@
            if (streamPushItem == null) {
                streamPushItem = new StreamPushItem();
                streamPushItem.setApp(item.getApp());
                streamPushItem.setMediaServerId(mediaServerItem.getId());
                streamPushItem.setStream(item.getStream());
                streamPushItem.setAliveSecond(item.getAliveSecond());
                streamPushItem.setCreateStamp(item.getCreateStamp());
@@ -87,7 +98,8 @@
    @Override
    public boolean removeFromGB(GbStream stream) {
        int del = gbStreamMapper.del(stream.getApp(), stream.getStream());
        JSONObject mediaList = zlmresTfulUtils.getMediaList(stream.getApp(), stream.getStream());
        IMediaServerItem mediaInfo = mediaServerService.getOne(stream.getMediaServerId());
        JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaInfo, stream.getApp(), stream.getStream());
        if (mediaList == null) {
            streamPushMapper.del(stream.getApp(), stream.getStream());
        }
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -2,10 +2,11 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import java.util.List;
import java.util.Map;
@@ -39,19 +40,6 @@
    StreamInfo queryPlaybackByStreamId(String steamId);
    StreamInfo queryPlayByDevice(String deviceId, String channelId);
    /**
     * 更新流媒体信息
     * @param ZLMServerConfig
     * @return
     */
    boolean updateMediaInfo(ZLMServerConfig ZLMServerConfig);
    /**
     * 获取流媒体信息
     * @return
     */
    ZLMServerConfig getMediaInfo();
    Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
@@ -115,6 +103,13 @@
    void clearCatchByDeviceId(String deviceId);
    /**
     * 获取mediaServer节点
     * @param mediaServerId
     * @return
     */
//    MediaServerItem getMediaInfo(String mediaServerId);
    /**
     * 设置所有设备离线
     */
    void outlineForAll();
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -3,6 +3,8 @@
import java.util.List;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
@@ -365,4 +367,12 @@
     * @param online
     */
    void updateParentPlatformStatus(String platformGbID, boolean online);
    /**
     * 更新媒体节点
     * @param mediaServerItem
     */
    void updateMediaServer(MediaServerItem mediaServerItem);
    List<StreamProxyItem> getStreamProxyListForEnableInMediaServer(String id, boolean b);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java
@@ -12,9 +12,10 @@
public interface GbStreamMapper {
    @Insert("INSERT INTO gb_stream (app, stream, gbId, name, " +
            "longitude, latitude, streamType, status) VALUES" +
            "longitude, latitude, streamType, mediaServerId, status) VALUES" +
            "('${app}', '${stream}', '${gbId}', '${name}', " +
            "'${longitude}', '${latitude}', '${streamType}', ${status})")
            "'${longitude}', '${latitude}', '${streamType}', " +
            "'${mediaServerId}', ${status})")
    int add(GbStream gbStream);
    @Update("UPDATE gb_stream " +
@@ -25,6 +26,7 @@
            "streamType=#{streamType}," +
            "longitude=#{longitude}, " +
            "latitude=#{latitude}," +
            "mediaServerId=#{mediaServerId}," +
            "status=${status} " +
            "WHERE app=#{app} AND stream=#{stream} AND gbId=#{gbId}")
    int update(GbStream gbStream);
@@ -52,4 +54,7 @@
            "SET status=${status} " +
            "WHERE app=#{app} AND stream=#{stream}")
    void setStatus(String app, String stream, boolean status);
    @Select("SELECT gs.*, pgs.platformId FROM gb_stream gs LEFT JOIN  platform_gb_stream pgs ON gs.app = pgs.app AND gs.stream = pgs.stream WHERE mediaServerId=#{mediaServerId} ")
    List<GbStream> selectAllByMediaServerId(String mediaServerId);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java
New file
@@ -0,0 +1,99 @@
package com.genersoft.iot.vmp.storager.dao;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface MediaServerMapper {
    @Insert("INSERT INTO media_server (" +
            "id, " +
            "ip, " +
            "hookIp, " +
            "sdpIp, " +
            "streamIp, " +
            "httpPort, " +
            "httpSSlPort, " +
            "rtmpPort, " +
            "rtmpSSlPort, " +
            "rtpProxyPort, " +
            "rtspPort, " +
            "rtspSSLPort, " +
            "autoConfig, " +
            "secret, " +
            "streamNoneReaderDelayMS, " +
            "rtpEnable, " +
            "rtpPortRange, " +
            "recordAssistPort, " +
            "createTime, " +
            "updateTime" +
            ") VALUES " +
            "(" +
            "'${id}', " +
            "'${ip}', " +
            "'${hookIp}', " +
            "'${sdpIp}', " +
            "'${streamIp}', " +
            "${httpPort}, " +
            "${httpSSlPort}, " +
            "${rtmpPort}, " +
            "${rtmpSSlPort}, " +
            "${rtpProxyPort}, " +
            "${rtspPort}, " +
            "${rtspSSLPort}, " +
            "${autoConfig}, " +
            "'${secret}', " +
            "${streamNoneReaderDelayMS}, " +
            "${rtpEnable}, " +
            "'${rtpPortRange}', " +
            "${recordAssistPort}, " +
            "'${createTime}', " +
            "'${updateTime}')")
    int add(IMediaServerItem mediaServerItem);
    @Update(value = {" <script>" +
            "UPDATE media_server " +
            "SET updateTime='${updateTime}'" +
            "<if test=\"ip != null\">, ip='${ip}'</if>" +
            "<if test=\"hookIp != null\">, hookIp='${hookIp}'</if>" +
            "<if test=\"sdpIp != null\">, sdpIp='${sdpIp}'</if>" +
            "<if test=\"streamIp != null\">, streamIp='${streamIp}'</if>" +
            "<if test=\"httpPort != null\">, httpPort=${httpPort}</if>" +
            "<if test=\"httpSSlPort != null\">, httpSSlPort=${httpSSlPort}</if>" +
            "<if test=\"rtmpPort != null\">, rtmpPort=${rtmpPort}</if>" +
            "<if test=\"rtmpSSlPort != null\">, rtmpSSlPort=${rtmpSSlPort}</if>" +
            "<if test=\"rtpProxyPort != null\">, rtpProxyPort=${rtpProxyPort}</if>" +
            "<if test=\"rtspPort != null\">, rtspPort=${rtspPort}</if>" +
            "<if test=\"rtspSSLPort != null\">, rtspSSLPort=${rtspSSLPort}</if>" +
            "<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" +
            "<if test=\"streamNoneReaderDelayMS != null\">, streamNoneReaderDelayMS=${streamNoneReaderDelayMS}</if>" +
            "<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" +
            "<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" +
            "<if test=\"secret != null\">, secret='${secret}'</if>" +
            "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
            "WHERE id='${id}'"+
            " </script>"})
    int update(IMediaServerItem mediaServerItem);
    @Select("SELECT * FROM media_server WHERE id='${id}'")
    MediaServerItem queryOne(String id);
    @Select("SELECT * FROM media_server")
    List<MediaServerItem> queryAll();
    @Select("DELETE FROM media_server WHERE id='${id}'")
    int delOne(String secret);
    @Select("SELECT * FROM media_server WHERE ip='${host}' and httpPort=${port}")
    MediaServerItem queryOneByHostAndPort(String host, int port);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java
@@ -10,10 +10,10 @@
@Repository
public interface StreamProxyMapper {
    @Insert("INSERT INTO stream_proxy (type, app, stream, url, src_url, dst_url, " +
            "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable) VALUES" +
            "('${type}','${app}', '${stream}', '${url}', '${src_url}', '${dst_url}', " +
            "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable} )")
    @Insert("INSERT INTO stream_proxy (type, app, stream,mediaServerId, url, src_url, dst_url, " +
            "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable, createTime) VALUES" +
            "('${type}','${app}', '${stream}', '${mediaServerId}','${url}', '${src_url}', '${dst_url}', " +
            "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable}, '${createTime}' )")
    int add(StreamProxyItem streamProxyDto);
    @Update("UPDATE stream_proxy " +
@@ -21,6 +21,7 @@
            "app=#{app}," +
            "stream=#{stream}," +
            "url=#{url}, " +
            "mediaServerId=#{mediaServerId}, " +
            "src_url=#{src_url}," +
            "dst_url=#{dst_url}, " +
            "timeout_ms=#{timeout_ms}, " +
@@ -35,12 +36,17 @@
    @Delete("DELETE FROM stream_proxy WHERE app=#{app} AND stream=#{stream}")
    int del(String app, String stream);
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream")
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream order by st.createTime desc")
    List<StreamProxyItem> selectAll();
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.enable=${enable}")
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.enable=${enable} order by st.createTime desc")
    List<StreamProxyItem> selectForEnable(boolean enable);
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.app=#{app} AND st.stream=#{stream}")
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.app=#{app} AND st.stream=#{stream} order by st.createTime desc")
    StreamProxyItem selectOne(String app, String stream);
    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st " +
            "LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " +
            "WHERE st.enable=${enable} and st.mediaServerId = '${id}' order by st.createTime desc")
    List<StreamProxyItem> selectForEnableInMediaServer(String id, boolean enable);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
@@ -11,14 +11,15 @@
public interface StreamPushMapper {
    @Insert("INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " +
            "createStamp, aliveSecond) VALUES" +
            "createStamp, aliveSecond, mediaServerId) VALUES" +
            "('${app}', '${stream}', '${totalReaderCount}', '${originType}', '${originTypeStr}', " +
            "'${createStamp}', '${aliveSecond}' )")
            "'${createStamp}', '${aliveSecond}', '${mediaServerId}' )")
    int add(StreamPushItem streamPushItem);
    @Update("UPDATE stream_push " +
            "SET app=#{app}," +
            "stream=#{stream}," +
            "mediaServerId=#{mediaServerId}," +
            "totalReaderCount=#{totalReaderCount}, " +
            "originType=#{originType}," +
            "originTypeStr=#{originTypeStr}, " +
@@ -41,10 +42,10 @@
    @Insert("<script>"  +
            "INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " +
            "createStamp, aliveSecond) " +
            "createStamp, aliveSecond, mediaServerId) " +
            "VALUES <foreach collection='streamPushItems' item='item' index='index' >" +
            "( '${item.app}', '${item.stream}', '${item.totalReaderCount}', '${item.originType}', " +
            "'${item.originTypeStr}','${item.createStamp}', '${item.aliveSecond}' )" +
            "'${item.originTypeStr}','${item.createStamp}', '${item.aliveSecond}', '${item.mediaServerId}' )" +
            " </foreach>" +
            "</script>")
    void addAll(List<StreamPushItem> streamPushItems);
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -5,6 +5,8 @@
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
@@ -85,26 +87,6 @@
                channelId));
        if (playLeys == null || playLeys.size() == 0) return null;
        return (StreamInfo)redis.get(playLeys.get(0).toString());
    }
    /**
     * 更新流媒体信息
     * @param ZLMServerConfig
     * @return
     */
    @Override
    public boolean updateMediaInfo(ZLMServerConfig ZLMServerConfig) {
        ZLMServerConfig.setUpdateTime(format.format(new Date(System.currentTimeMillis())));
        return redis.set(VideoManagerConstants.MEDIA_SERVER_PREFIX, ZLMServerConfig);
    }
    /**
     * 获取流媒体信息
     * @return
     */
    @Override
    public ZLMServerConfig getMediaInfo() {
        return (ZLMServerConfig)redis.get(VideoManagerConstants.MEDIA_SERVER_PREFIX);
    }
    @Override
@@ -297,7 +279,7 @@
    @Override
    public void outlineForAll() {
        List<Object> onlineDevices = redis.scan(String.format("%S*", VideoManagerConstants.KEEPLIVEKEY_PREFIX));
        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + "*" );
        for (int i = 0; i < onlineDevices.size(); i++) {
            String key = (String) onlineDevices.get(i);
            redis.del(key);
@@ -308,4 +290,5 @@
    public void updateWVPInfo(JSONObject jsonObject) {
    }
}
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
@@ -5,6 +5,7 @@
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -69,6 +70,9 @@
    @Autowired
    private VideoStreamSessionManager streamSession;
    @Autowired
    private MediaServerMapper mediaServerMapper;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -459,6 +463,8 @@
        boolean result = false;
        streamProxyItem.setStreamType("proxy");
        streamProxyItem.setStatus(true);
        String now = this.format.format(new Date(System.currentTimeMillis()));
        streamProxyItem.setCreateTime(now);
        try {
            if (gbStreamMapper.add(streamProxyItem)<0 || streamProxyMapper.add(streamProxyItem) < 0) {
                //事务回滚
@@ -467,6 +473,7 @@
            result = true;
            dataSourceTransactionManager.commit(transactionStatus);     //手动提交
        }catch (Exception e) {
            logger.error("向数据库添加流代理失败:", e);
            dataSourceTransactionManager.rollback(transactionStatus);
        }
        return result;
@@ -599,4 +606,21 @@
    public void updateParentPlatformStatus(String platformGbID, boolean online) {
        platformMapper.updateParentPlatformStatus(platformGbID, online);
    }
    @Override
    public void updateMediaServer(MediaServerItem mediaServerItem) {
        String now = this.format.format(new Date(System.currentTimeMillis()));
        mediaServerItem.setUpdateTime(now);
        if (mediaServerMapper.queryOne(mediaServerItem.getId()) != null) {
            mediaServerMapper.update(mediaServerItem);
        }else {
            mediaServerItem.setCreateTime(now);
            mediaServerMapper.add(mediaServerItem);
        }
    }
    @Override
    public List<StreamProxyItem> getStreamProxyListForEnableInMediaServer(String id, boolean enable) {
        return streamProxyMapper.selectForEnableInMediaServer(id, enable);
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java
@@ -25,6 +25,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@@ -78,7 +79,7 @@
        cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("设备配置操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
@@ -87,7 +88,7 @@
            logger.warn(String.format("设备配置操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("Status", "Timeout");
@@ -95,7 +96,7 @@
            msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
@@ -123,7 +124,7 @@
        cmder.deviceConfigQuery(device, channelId, configType, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("获取设备配置失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
@@ -132,11 +133,11 @@
            logger.warn(String.format("获取设备配置超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
@@ -26,6 +26,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@@ -97,7 +98,7 @@
        cmder.recordCmd(device, channelId, recordCmdStr, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
@@ -106,11 +107,11 @@
            logger.warn(String.format("开始/停止录像操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
@@ -254,7 +255,7 @@
        cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
@@ -263,7 +264,7 @@
            logger.warn(String.format("看守位控制操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("Status", "Timeout");
@@ -271,7 +272,7 @@
            msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java
@@ -43,11 +43,13 @@
    @ApiImplicitParams({
            @ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class),
            @ApiImplicitParam(name = "stream", value = "流id", dataTypeClass = String.class),
            @ApiImplicitParam(name = "mediaServerId", value = "媒体服务器id", dataTypeClass = String.class),
    })
    @GetMapping(value = "/stream_info_by_app_and_stream")
    @ResponseBody
    public StreamInfo getStreamInfoByAppAndStream(String app, String stream){
        return mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream);
    public StreamInfo getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam String mediaServerId){
        return mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream,mediaServerId);
    }
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
@@ -8,6 +8,9 @@
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
@@ -72,6 +75,9 @@
    @Autowired
    private IMediaService mediaService;
    @Autowired
    private IMediaServerService mediaServerService;
    @ApiOperation("开始点播")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@@ -81,8 +87,10 @@
    public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId,
                                                       @PathVariable String channelId) {
        PlayResult playResult = playService.play(deviceId, channelId, null, null);
        // 获取可用的zlm
        Device device = storager.queryVideoDevice(deviceId);
        IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        PlayResult playResult = playService.play(newMediaServerItem, deviceId, channelId, null, null);
        return playResult.getResult();
    }
@@ -102,8 +110,8 @@
        // 录像查询以channelId作为deviceId查询
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result);
        cmder.streamByeCmd(deviceId, channelId, event -> {
        Device device = storager.queryVideoDevice(deviceId);
        cmder.streamByeCmd(deviceId, channelId, (event) -> {
            StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
            if (streamInfo == null) {
                RequestMessage msg = new RequestMessage();
@@ -120,6 +128,7 @@
                msg.setData(String.format("success"));
                resultHolder.invokeResult(msg);
            }
            mediaServerService.closeRTPServer(device, channelId);
        });
        if (deviceId != null || channelId != null) {
@@ -165,16 +174,16 @@
            logger.warn("视频转码API调用失败!, 视频流已经停止!");
            return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
        }
        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
        IMediaServerItem mediaInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
        if (!rtpInfo.getBoolean("exist")) {
            logger.warn("视频转码API调用失败!, 视频流已停止推流!");
            return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK);
        } else {
            ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
            String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(),
                    streamId );
            String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId);
            JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(srcUrl, dstUrl, "1000000", true, false, null);
            JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(mediaInfo, srcUrl, dstUrl, "1000000", true, false, null);
            logger.info(jsonObject.toJSONString());
            JSONObject result = new JSONObject();
            if (jsonObject != null && jsonObject.getInteger("code") == 0) {
@@ -182,7 +191,7 @@
                JSONObject data = jsonObject.getJSONObject("data");
                if (data != null) {
                       result.put("key", data.getString("key"));
                    StreamInfo streamInfoResult = mediaService.getStreamInfoByAppAndStreamWithCheck("convert", streamId);
                    StreamInfo streamInfoResult = mediaService.getStreamInfoByAppAndStreamWithCheck("convert", streamId, mediaInfo.getId());
                    result.put("data", streamInfoResult);
                }
            }else {
@@ -203,25 +212,38 @@
            @ApiImplicitParam(name = "key", value = "视频流key", dataTypeClass = String.class),
    })
    @PostMapping("/convertStop/{key}")
    public ResponseEntity<String> playConvertStop(@PathVariable String key) {
        JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(key);
        logger.info(jsonObject.toJSONString());
    public ResponseEntity<String> playConvertStop(@PathVariable String key, String mediaServerId) {
        JSONObject result = new JSONObject();
        if (jsonObject != null && jsonObject.getInteger("code") == 0) {
            result.put("code", 0);
            JSONObject data = jsonObject.getJSONObject("data");
            if (data != null && data.getBoolean("flag")) {
                result.put("code", "0");
                result.put("msg", "success");
            }else {
            }
        }else {
            result.put("code", 1);
            result.put("msg", "delFFmpegSource fail");
        if (mediaServerId == null) {
            result.put("code", 400);
            result.put("msg", "mediaServerId is null");
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (mediaInfo == null) {
            result.put("code", 0);
            result.put("msg", "使用的流媒体已经停止运行");
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
        }else {
            JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(mediaInfo, key);
            logger.info(jsonObject.toJSONString());
            if (jsonObject != null && jsonObject.getInteger("code") == 0) {
                result.put("code", 0);
                JSONObject data = jsonObject.getJSONObject("data");
                if (data != null && data.getBoolean("flag")) {
                    result.put("code", "0");
                    result.put("msg", "success");
                }else {
                }
            }else {
                result.put("code", 1);
                result.put("msg", "delFFmpegSource fail");
            }
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
        }
    }
    @ApiOperation("语音广播命令")
@@ -249,7 +271,7 @@
            resultHolder.invokeResult(msg);
            return result;
        }
        cmder.audioBroadcastCmd(device, event -> {
        cmder.audioBroadcastCmd(device, (event) -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId);
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
@@ -4,6 +4,8 @@
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
//import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IPlayService;
import io.swagger.annotations.Api;
@@ -87,9 +89,18 @@
            cmder.streamByeCmd(deviceId, channelId);
        }
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
        cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
        IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        if (newMediaServerItem == null) {
            logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
            return result;
        }
        cmder.playbackStreamCmd(newMediaServerItem, device, channelId, startTime, endTime, (IMediaServerItem mediaServerItem, JSONObject response) -> {
            logger.info("收到订阅消息: " + response.toJSONString());
            playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString());
            playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString());
        }, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
@@ -9,6 +9,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@@ -104,7 +105,7 @@
        cmder.presetQuery(device, channelId, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("获取设备预置位失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
@@ -113,11 +114,11 @@
            logger.warn(String.format("获取设备预置位超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
            msg.setData("获取设备预置位超时");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecoderProxyController.java
@@ -2,6 +2,9 @@
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
@@ -27,6 +30,8 @@
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private MediaConfig mediaConfig;
@@ -48,7 +53,11 @@
            return null;
        }
        // 后续改为根据Id获取对应的ZLM
        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaId);
        if (mediaInfo == null) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return null;
        }
        String requestURI = String.format("http://%s:%s%s?%s",
                mediaInfo.getSdpIp(),
                mediaConfig.getRecordAssistPort(),
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
@@ -2,8 +2,11 @@
import com.genersoft.iot.vmp.VManageBootstrap;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import gov.nist.javax.sip.SipStackImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -16,6 +19,7 @@
import javax.sip.SipProvider;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@SuppressWarnings("rawtypes")
@Api(tags = "服务控制")
@@ -28,17 +32,28 @@
    private ConfigurableApplicationContext context;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    private IMediaServerService mediaServerService;
    @ApiOperation("流媒体服务列表")
    @GetMapping(value = "/media_server/list")
    @ResponseBody
    public Object getMediaServerList(){
        // TODO 为后续多个zlm支持准备
        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
        ArrayList<ZLMServerConfig> result = new ArrayList<>();
        result.add(mediaInfo);
    public WVPResult<List<IMediaServerItem>> getMediaServerList(){
        WVPResult<List<IMediaServerItem>> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
        result.setData(mediaServerService.getAll());
        return result;
    }
    @ApiOperation("获取流媒体服务")
    @GetMapping(value = "/media_server/one/{id}")
    @ResponseBody
    public WVPResult<IMediaServerItem> getMediaServer(@PathVariable String id){
        WVPResult<IMediaServerItem> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
        result.setData(mediaServerService.getOne(id));
        return result;
    }
src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java
@@ -1,11 +1,15 @@
package com.genersoft.iot.vmp.vmanager.streamProxy;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import io.netty.util.internal.StringUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -14,6 +18,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@SuppressWarnings("rawtypes")
@@ -30,6 +35,10 @@
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private IMediaServerService mediaServerService;
    @Autowired
    private IStreamProxyService streamProxyService;
@@ -60,6 +69,7 @@
    @ResponseBody
    public WVPResult save(@RequestBody StreamProxyItem param){
        logger.info("添加代理: " + JSONObject.toJSONString(param));
        if (StringUtils.isEmpty(param.getMediaServerId())) param.setMediaServerId("auto");
        String msg = streamProxyService.save(param);
        WVPResult<Object> result = new WVPResult<>();
        result.setCode(0);
@@ -69,10 +79,15 @@
    @ApiOperation("获取ffmpeg.cmd模板")
    @GetMapping(value = "/ffmpeg_cmd/list")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "mediaServerId", value = "流媒体ID", dataTypeClass = String.class),
    })
    @ResponseBody
    public WVPResult getFFmpegCMDs(){
        logger.debug("获取ffmpeg.cmd模板:" );
        JSONObject data = streamProxyService.getFFmpegCMDs();
    public WVPResult getFFmpegCMDs(@RequestParam String mediaServerId){
        logger.debug("获取节点[ {} ]ffmpeg.cmd模板", mediaServerId );
        IMediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
        JSONObject data = streamProxyService.getFFmpegCMDs(mediaServerItem);
        WVPResult<JSONObject> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
@@ -82,12 +97,12 @@
    @ApiOperation("移除代理")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class),
            @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class),
            @ApiImplicitParam(name = "app", value = "应用名", required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "stream", value = "流ID", required = true, dataTypeClass = String.class),
    })
    @DeleteMapping(value = "/del")
    @ResponseBody
    public WVPResult del(String app, String stream){
    public WVPResult del(@RequestParam String app, @RequestParam String stream){
        logger.info("移除代理: " + app + "/" + stream);
        WVPResult<Object> result = new WVPResult<>();
        if (app == null || stream == null) {
src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java
@@ -10,7 +10,7 @@
import org.springframework.web.bind.annotation.*;
/**
 * 兼容LiveGBS的API:设备控制
 * API兼容:设备控制
 */
@CrossOrigin
@RestController
src/main/java/com/genersoft/iot/vmp/web/ApiController.java
@@ -11,7 +11,7 @@
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * 兼容LiveGBS的API:系统接口
 * API兼容:系统接口
 */
@Controller
@CrossOrigin
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java
@@ -14,7 +14,7 @@
import java.util.List;
/**
 * 兼容LiveGBS的API:设备信息
 * API兼容:设备信息
 */
@SuppressWarnings("unchecked")
@CrossOrigin
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
@@ -17,7 +17,7 @@
import org.springframework.web.context.request.async.DeferredResult;
/**
 * 兼容LiveGBS的API:实时直播
 * API兼容:实时直播
 */
@SuppressWarnings(value = {"rawtypes", "unchecked"})
@CrossOrigin
src/main/resources/all-application.yml
@@ -70,9 +70,13 @@
    keepalive-timeout: 180
    # [可选] 国标级联注册失败,再次发起注册的时间间隔。 默认60秒
    register-time-interval: 60
    # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。
    # keepalliveToOnline: false
#zlm 默认服务器配置
media:
    # [可选] zlm服务器唯一id,用于触发hook时区别是哪台服务器,general.mediaServerId
    id:
    # [必须修改] zlm服务器的内网IP
    ip: 192.168.0.100
    # [可选] 返回流地址时的ip,置空使用 media.ip
src/main/resources/wvp.sqlite
Binary files differ
web_src/src/components/CloudRecord.vue
@@ -7,22 +7,23 @@
            <el-main>
        <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
          <span style="font-size: 1rem; font-weight: bold;">云端录像</span>
          <div style="position: absolute; right: 5rem; top: 0.3rem;">
            节点选择: <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServer" placeholder="请选择" default-first-option>
            <el-option
              v-for="item in mediaServerList"
              :key="item.id"
              :label="item.id + '( ' + item.streamIp + ' )'"
              :value="item">
            </el-option>
          </el-select>
          </div>
          <div style="position: absolute; right: 1rem; top: 0.3rem;">
            <el-button v-if="!recordDetail" icon="el-icon-refresh-right" circle size="mini" :loading="loading" @click="getRecordList()"></el-button>
            <el-button v-if="recordDetail" icon="el-icon-arrow-left" circle size="mini" @click="backToList()"></el-button>
          </div>
        </div>
        <div v-if="!recordDetail">
          <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;font-size: 14px;">
            节点选择: <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServer" placeholder="请选择" default-first-option>
            <el-option
              v-for="item in mediaServerList"
              :key="item.generalMediaServerId"
              :label="item.generalMediaServerId + '( ' + item.wanIp + ' )'"
              :value="item">
            </el-option>
          </el-select>
          </div>
          <!--设备列表-->
          <el-table :data="recordList" border style="width: 100%" :height="winHeight">
            <el-table-column prop="app" label="应用名" align="center">
@@ -60,6 +61,7 @@
<script>
    import uiHeader from './UiHeader.vue'
    import cloudRecordDetail from './CloudRecordDetail.vue'
  import MediaServer from './service/MediaServer'
    export default {
        name: 'app',
        components: {
@@ -78,6 +80,7 @@
        count:15,
        total:0,
        loading: false,
        mediaServerObj : new MediaServer(),
        recordDetail: false
            };
@@ -107,20 +110,13 @@
      },
      getMediaServerList: function (){
        let that = this;
        this.$axios({
          method: 'get',
          url:`/api/server/media_server/list`,
        }).then(function (res) {
          console.log(res)
          that.mediaServerList = res.data;
        that.mediaServerObj.getMediaServerList((data)=>{
          that.mediaServerList = data;
          if (that.mediaServerList.length > 0) {
            that.mediaServer = that.mediaServerList[0]
            that.getRecordList();
          }
        }).catch(function (error) {
          console.log(error);
        });
        })
      },
      getRecordList: function (){
        let that = this;
web_src/src/components/PushVideoList.vue
@@ -17,6 +17,8 @@
                    </el-table-column>
                    <el-table-column prop="gbId" label="国标编码" width="150" align="center">
                    </el-table-column>
                    <el-table-column prop="mediaServerId" label="流媒体" width="150" align="center">
                    </el-table-column>
                    <el-table-column label="开始时间" align="center" >
                        <template slot-scope="scope">
                            <el-button-group>
@@ -29,7 +31,7 @@
                            {{(scope.row.status == false && scope.row.gbId == null) || scope.row.status ?'是':'否'}}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" width="360" align="center" fixed="right">
                        <template slot-scope="scope">
                            <el-button-group>
@@ -125,7 +127,7 @@
                    that.getDeviceListLoading = false;
                });
            },
            playPuhsh: function(row){
                let that = this;
                this.getListLoading = true;
@@ -134,7 +136,8 @@
                    url:`/api/media/stream_info_by_app_and_stream`,
                    params: {
                        app: row.app,
                        stream: row.stream
                        stream: row.stream,
            mediaServerId: row.mediaServerId
                    }
                }).then(function (res) {
                    that.getListLoading = false;
web_src/src/components/StreamProxyList.vue
@@ -32,6 +32,15 @@
                        </div>
                        </template>
                    </el-table-column>
          <el-table-column prop="mediaServerId" label="流媒体" width="150" align="center"></el-table-column>
          <el-table-column label="类型" width="100" align="center">
            <template slot-scope="scope">
              <div slot="reference" class="name-wrapper">
                <el-tag size="medium">{{scope.row.type}}</el-tag>
              </div>
            </template>
          </el-table-column>
                    <el-table-column prop="gbId" label="国标编码" width="180" align="center" show-overflow-tooltip/>
          <el-table-column label="启用" width="120" align="center">
            <template slot-scope="scope">
@@ -147,8 +156,6 @@
                        count: that.count
                    }
                }).then(function (res) {
                    console.log(res);
                    console.log(res.data.list);
                    that.total = res.data.total;
                    that.streamProxyList = res.data.list;
                    that.getListLoading = false;
@@ -170,7 +177,6 @@
          this.getListLoading = false;
          if (res.data.code == 0 ){
            if (res.data.data.length > 0) {
              console.log(res.data.data)
              this.$refs.onvifEdit.openDialog(res.data.data, (url)=>{
                  if (url != null) {
                    this.$refs.onvifEdit.close();
@@ -200,7 +206,8 @@
                    url:`/api/media/stream_info_by_app_and_stream`,
                    params: {
                        app: row.app,
                        stream: row.stream
                        stream: row.stream,
            mediaServerId: row.mediaServerId
                    }
                }).then(function (res) {
                    that.getListLoading = false;
web_src/src/components/control.vue
@@ -7,6 +7,17 @@
        <el-main>
            <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
                <span style="font-size: 1rem; font-weight: bold;">控制台</span>
                <div style="position: absolute; right: 17rem; top: 0.3rem;">
                  节点选择: <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerChoose" placeholder="请选择" default-first-option>
                  <el-option
                    v-for="item in mediaServerList"
                    :key="item.id"
                    :label="item.id + '( ' + item.streamIp + ' )'"
                    :value="item.id">
                  </el-option>
                  </el-select>
                  <span >{{loadCount}}</span>
                </div>
                <div style="position: absolute; right: 1rem; top: 0.3rem;">
                    <el-popover placement="bottom" width="750" height="300" trigger="click">
                        <div style="height: 600px;overflow:auto;">
@@ -53,6 +64,7 @@
<script>
import uiHeader from './UiHeader.vue'
import MediaServer from './service/MediaServer'
import echarts from 'echarts';
export default {
@@ -87,68 +99,101 @@
            chartInterval: 0, //更新图表统计图定时任务标识
            allSessionData: [],
            visible: false,
            serverConfig: {}
            serverConfig: {},
            mediaServer : new MediaServer(),
            mediaServerChoose : null,
            loadCount : 0,
            mediaServerList : []
        };
    },
    mounted() {
        this.getAllSession();
        this.initTable();
        this.updateData();
        this.chartInterval = setInterval(this.updateData, 3000);
        this.mediaServer.getMediaServerList((data)=>{
          this.mediaServerList = data.data;
          if (this.mediaServerList && this.mediaServerList.length > 0) {
            this.mediaServerChoose = this.mediaServerList[0].id
            this.loadCount = this.mediaServerList[0].count;
            this.getThreadsLoad();
            this.getAllSession();
          }
        })
    },
    destroyed() {
        clearInterval(this.chartInterval); //释放定时任务
    },
    methods: {
        chooseMediaChange: function (val) {
            this.loadCount = 0
            this.initTable()
            this.updateData();
        },
        updateData: function () {
            this.getThreadsLoad();
            this.getLoadCount();
            this.getAllSession();
        },
        /**
         * 获取线程状态
         */
        getThreadsLoad: function () {
            let that = this;
            this.$axios({
            if (that.mediaServerChoose != null) {
              this.$axios({
                method: 'get',
                url: '/zlm/index/api/getThreadsLoad'
            }).then(function (res) {
                url: '/zlm/' + that.mediaServerChoose +'/index/api/getThreadsLoad'
              }).then(function (res) {
                if (res.data.code == 0) {
                    that.tableOption.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
                        hour12: false
                    }));
                    that.table1Option.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
                        hour12: false
                    }));
                  that.tableOption.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
                    hour12: false
                  }));
                  that.table1Option.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
                    hour12: false
                  }));
                    for (var i = 0; i < res.data.data.length; i++) {
                        if (that.tableOption.series[i] === undefined) {
                            let data = {
                                data: [],
                                type: 'line'
                            };
                            let data1 = {
                                data: [],
                                type: 'line'
                            };
                            data.data.push(res.data.data[i].delay);
                            data1.data.push(res.data.data[i].load);
                            that.tableOption.series.push(data);
                            that.table1Option.series.push(data1);
                        } else {
                            that.tableOption.series[i].data.push(res.data.data[i].delay);
                            that.table1Option.series[i].data.push(res.data.data[i].load);
                        }
                  for (var i = 0; i < res.data.data.length; i++) {
                    if (that.tableOption.series[i] === undefined) {
                      let data = {
                        data: [],
                        type: 'line'
                      };
                      let data1 = {
                        data: [],
                        type: 'line'
                      };
                      data.data.push(res.data.data[i].delay);
                      data1.data.push(res.data.data[i].load);
                      that.tableOption.series.push(data);
                      that.table1Option.series.push(data1);
                    } else {
                      that.tableOption.series[i].data.push(res.data.data[i].delay);
                      that.table1Option.series[i].data.push(res.data.data[i].load);
                    }
                    that.tableOption.dataZoom[0].start = that.charZoomStart;
                    that.tableOption.dataZoom[0].end = that.charZoomEnd;
                    that.table1Option.dataZoom[0].start = that.charZoomStart;
                    that.table1Option.dataZoom[0].end = that.charZoomEnd;
                    //that.myChart = echarts.init(document.getElementById('ThreadsLoad'));
                    that.myChart.setOption(that.tableOption, true);
                    // that.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
                    that.myChart1.setOption(that.table1Option, true);
                  }
                  that.tableOption.dataZoom[0].start = that.charZoomStart;
                  that.tableOption.dataZoom[0].end = that.charZoomEnd;
                  that.table1Option.dataZoom[0].start = that.charZoomStart;
                  that.table1Option.dataZoom[0].end = that.charZoomEnd;
                  //that.myChart = echarts.init(document.getElementById('ThreadsLoad'));
                  that.myChart.setOption(that.tableOption, true);
                  // that.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
                  that.myChart1.setOption(that.table1Option, true);
                }
            });
              });
            }
        },
        getLoadCount: function (){
          let that = this;
          if (that.mediaServerChoose != null) {
            that.mediaServer.getMediaServer(that.mediaServerChoose, (data)=>{
              if (data.code == 0) {
                that.loadCount = data.data.count
              }
            })
          }
        },
        initTable: function () {
            let that = this;
@@ -242,10 +287,9 @@
        getAllSession: function () {
            let that = this;
            that.allSessionData = [];
            console.log("地址:" + '/zlm/index/api/getAllSession');
            this.$axios({
                method: 'get',
                url: '/zlm/index/api/getAllSession'
                url: '/zlm/' + that.mediaServerChoose +'/index/api/getAllSession'
            }).then(function (res) {
                res.data.data.forEach(item => {
                    let data = {
web_src/src/components/dialog/StreamProxyEdit.vue
@@ -39,6 +39,22 @@
              <el-form-item label="超时时间:毫秒" prop="timeout_ms" v-if="proxyParam.type=='ffmpeg'">
                <el-input v-model="proxyParam.timeout_ms" clearable></el-input>
              </el-form-item>
              <el-form-item label="节点选择" prop="rtp_type">
                <el-select
                  v-model="proxyParam.mediaServerId"
                  @change="mediaServerIdChange"
                  style="width: 100%"
                  placeholder="请选择拉流节点"
                >
                  <el-option label="自动选择" value="auto"></el-option>
                  <el-option
                    v-for="item in mediaServerList"
                    :key="item.id"
                    :label="item.id"
                    :value="item.id">
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="FFmpeg命令模板" prop="ffmpeg_cmd_key" v-if="proxyParam.type=='ffmpeg'">
<!--                <el-input v-model="proxyParam.ffmpeg_cmd_key" clearable></el-input>-->
                <el-select
@@ -68,6 +84,7 @@
                  <el-option label="组播" value="2"></el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="国标平台">
                <el-select
                  v-model="proxyParam.platformGbId"
@@ -106,6 +123,8 @@
</template>
<script>
import MediaServer from './../service/MediaServer'
export default {
  name: "streamProxyEdit",
  props: {},
@@ -134,27 +153,8 @@
      isLoging: false,
      dialogLoading: false,
      onSubmit_text: "立即创建",
      platformList: [{
          id: 1,
          enable: true,
          name: "141",
          serverGBId: "34020000002000000001",
          serverGBDomain: "3402000000",
          serverIP: "192.168.1.141",
          serverPort: 15060,
          deviceGBId: "34020000002000000001",
          deviceIp: "192.168.1.20",
          devicePort: "5060",
          username: "34020000002000000001",
          password: "12345678",
          expires: "300",
          keepTimeout: "60",
          transport: "UDP",
          characterSet: "GB2312",
          ptz: false,
          rtcp: false,
          status: true,
      }],
      platformList: [],
      mediaServer: new MediaServer(),
      proxyParam: {
          name: null,
          type: "default",
@@ -170,7 +170,9 @@
          enable_hls: true,
          enable_mp4: false,
          platformGbId: null,
          mediaServerId: "auto",
      },
      mediaServerList:{},
      ffmpegCmdList:{},
      rules: {
@@ -193,7 +195,6 @@
      }
      let that = this;
      this.$axios({
        method: 'get',
        url:`/api/platform/query/10000/0`
@@ -202,17 +203,28 @@
      }).catch(function (error) {
        console.log(error);
      });
      this.$axios({
        method: 'get',
        url:`/api/proxy/ffmpeg_cmd/list`
      }).then(function (res) {
        that.ffmpegCmdList = res.data.data;
      }).catch(function (error) {
        console.log(error);
      });
      this.mediaServer.getMediaServerList((data)=>{
        this.mediaServerList = data;
      })
    },
    mediaServerIdChange:function (){
      let that = this;
      if (that.proxyParam.mediaServerId !== "auto"){
        that.$axios({
          method: 'get',
          url:`/api/proxy/ffmpeg_cmd/list`,
          params: {
            mediaServerId: that.proxyParam.mediaServerId
          }
        }).then(function (res) {
          that.ffmpegCmdList = res.data.data;
        }).catch(function (error) {
          console.log(error);
        });
      }
    },
    onSubmit: function () {
      console.log("onSubmit");
      this.dialogLoading = true;
      var that = this;
      that.$axios({
@@ -239,7 +251,6 @@
      });
    },
    close: function () {
      console.log("关闭添加视频平台");
      this.showDialog = false;
      this.dialogLoading = false;
      this.$refs.streamProxy.resetFields();
web_src/src/components/dialog/devicePlayer.vue
@@ -181,6 +181,7 @@
            showVideoDialog: false,
            streamId: '',
            app : '',
            mediaServerId : '',
            convertKey: '',
            deviceId: '',
            channelId: '',
@@ -218,7 +219,7 @@
            if (tab.name == "codec") {
                this.$axios({
                    method: 'get',
                    url: '/zlm/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId
                    url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId
                }).then(function (res) {
                    that.tracksLoading = false;
                    if (res.data.code == 0 && res.data.online) {
@@ -235,12 +236,11 @@
            }
        },
        openDialog: function (tab, deviceId, channelId, param) {
          console.log("openDialog")
          console.log(param)
            this.tabActiveName = tab;
            this.channelId = channelId;
            this.deviceId = deviceId;
            this.streamId = "";
            this.mediaServerId = "";
            this.app = "";
            this.videoUrl = ""
            if (!!this.$refs.videoPlayer) {
@@ -257,8 +257,8 @@
                    break;
                case "streamPlay":
                    this.tabActiveName = "media";
                    this.showRrecord = false,
                    this.showPtz = false,
                    this.showRrecord = false;
                    this.showPtz = false;
                    this.play(param.streamInfo, param.hasAudio)
                    break;
                case "control":
@@ -269,19 +269,17 @@
            console.log(val)
        },
        play: function (streamInfo, hasAudio) {
            this.hasAudio = hasAudio;
            this.isLoging = false;
            // this.videoUrl = streamInfo.rtc;
            this.videoUrl = this.getUrlByStreamInfo(streamInfo);
            this.streamId = streamInfo.streamId;
            this.app = streamInfo.app;
            this.mediaServerId = streamInfo.mediaServerId;
            this.playFromStreamInfo(false, streamInfo)
        },
        getUrlByStreamInfo(streamInfo){
            let baseZlmApi = process.env.NODE_ENV === 'development'?`${location.host}/debug/zlm`:`${location.host}/zlm`
            console.log(12121212)
            console.log(baseZlmApi)
            // return `${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
            // return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
            return streamInfo.ws_flv;
@@ -430,6 +428,7 @@
                    var streamInfo = res.data;
                    that.app = streamInfo.app;
                    that.streamId = streamInfo.streamId;
                    that.mediaServerId = streamInfo.mediaServerId;
                    that.videoUrl = that.getUrlByStreamInfo(streamInfo);
                    that.recordPlay = true;
                });
web_src/src/components/service/MediaServer.js
New file
@@ -0,0 +1,32 @@
import axios from 'axios';
class MediaServer{
  constructor() {
    this.$axios = axios;
  }
  getMediaServerList(callback){
    this.$axios({
      method: 'get',
      url:`/api/server/media_server/list`,
    }).then(function (res) {
      if (typeof (callback) == "function") callback(res.data)
    }).catch(function (error) {
      console.log(error);
    });
  }
  getMediaServer(id, callback){
    this.$axios({
      method: 'get',
      url:`/api/server/media_server/one/` + id,
    }).then(function (res) {
      if (typeof (callback) == "function") callback(res.data)
    }).catch(function (error) {
      console.log(error);
    });
  }
}
export default MediaServer;