优化集群方案, 每个zlm一套ssrc;
优化集群下的docker接入逻辑;
更正sql脚本;
支持重启不设置设备离线。重启SIP事务不丢失
47个文件已修改
7个文件已添加
3个文件已删除
2394 ■■■■■ 已修改文件
pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql.sql 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/SipDeviceRunner.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcUtil.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java 34 ●●●●● 补丁 | 查看 | 原始文档 | 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/MediaServerItem.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IMediaService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/bean/SSRCInfo.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java 391 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/SerializeUtils.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/redis/JedisUtil.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/session/PlayTypeEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/all-application.yml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/wvp.sqlite 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/service/MediaServer.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -46,6 +46,7 @@
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.build.timestamp.format>MMddHHmm</maven.build.timestamp.format>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
        <jedis-version>3.1.0</jedis-version>
        <!-- 依赖版本 -->
        <pagehelper.version>5.2.0</pagehelper.version>
@@ -80,6 +81,12 @@
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis-version}</version>
        </dependency>
        <!-- druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
sql/mysql.sql
@@ -1,112 +1,145 @@
-- auto-generated definition
create schema wvp collate utf8_bin;
CREATE DATABASE `wvp` /*!40100 DEFAULT CHARACTER SET utf8mb3 COLLATE utf8mb3_bin */;
use wvp;
create table device
(
    deviceId           varchar(50) not null
    deviceId      varchar(50)  not null
        primary key,
    name               varchar(255) null,
    manufacturer       varchar(255) null,
    model              varchar(255) null,
    firmware           varchar(255) null,
    transport          varchar(50) null,
    streamMode         varchar(50) null,
    online             varchar(50) null,
    registerTime       varchar(50) null,
    keepaliveTime      varchar(50) null,
    ip                 varchar(50) not null,
    createTime         varchar(50) not null,
    updateTime         varchar(50) not null,
    port               int          not null,
    expires            int          not null,
    hostAddress        varchar(50) not null
    name          varchar(255) null,
    manufacturer  varchar(255) null,
    model         varchar(255) null,
    firmware      varchar(255) null,
    transport     varchar(50)  null,
    streamMode    varchar(50)  null,
    online        varchar(50)  null,
    registerTime  varchar(50)  null,
    keepaliveTime varchar(50)  null,
    ip            varchar(50)  not null,
    createTime    varchar(50)  not null,
    updateTime    varchar(50)  not null,
    port          int          not null,
    expires       int          not null,
    hostAddress   varchar(50)  not null
);
create table device_channel
(
    channelId   varchar(50) not null,
    channelId   varchar(50)  not null,
    name        varchar(255) null,
    manufacture varchar(50) null,
    model       varchar(50) null,
    owner       varchar(50) null,
    civilCode   varchar(50) null,
    block       varchar(50) null,
    address     varchar(50) null,
    parentId    varchar(50) null,
    manufacture varchar(50)  null,
    model       varchar(50)  null,
    owner       varchar(50)  null,
    civilCode   varchar(50)  null,
    block       varchar(50)  null,
    address     varchar(50)  null,
    parentId    varchar(50)  null,
    safetyWay   int          null,
    registerWay int          null,
    certNum     varchar(50) null,
    certNum     varchar(50)  null,
    certifiable int          null,
    errCode     int          null,
    endTime     varchar(50) null,
    secrecy     varchar(50) null,
    ipAddress   varchar(50) null,
    endTime     varchar(50)  null,
    secrecy     varchar(50)  null,
    ipAddress   varchar(50)  null,
    port        int          null,
    password    varchar(255) null,
    PTZType     int          null,
    status      int          null,
    longitude   double       null,
    latitude    double       null,
    streamId    varchar(50) null,
    deviceId    varchar(50) not null,
    parental    varchar(50) null,
    hasAudio    bit(1)   null,
    createTime  varchar(50) not null,
    updateTime  varchar(50) not null,
    streamId    varchar(50)  null,
    deviceId    varchar(50)  not null,
    parental    varchar(50)  null,
    hasAudio    bit          null,
    createTime  varchar(50)  not null,
    updateTime  varchar(50)  not null,
    primary key (channelId, deviceId)
);
create table device_mobile_position
(
    deviceId       varchar(50) not null,
    deviceId       varchar(50)  not null,
    deviceName     varchar(255) null,
    time           varchar(50) not null,
    time           varchar(50)  not null,
    longitude      double       not null,
    latitude       double       not null,
    altitude       double       null,
    speed          double       null,
    direction      double       null,
    reportSource   varchar(50) null,
    geodeticSystem varchar(50) null,
    cnLng          varchar(50) null,
    cnLat          varchar(50) null,
    reportSource   varchar(50)  null,
    geodeticSystem varchar(50)  null,
    cnLng          varchar(50)  null,
    cnLat          varchar(50)  null,
    primary key (deviceId, time)
);
create table gb_stream
(
    app        varchar(255) not null,
    stream     varchar(255) not null,
    gbId       varchar(50) not null,
    name       varchar(255) null,
    longitude  double       null,
    latitude   double       null,
    streamType varchar(50) null,
    status     int          null,
    app           varchar(255) not null,
    stream        varchar(255) not null,
    gbId          varchar(50)  not null,
    name          varchar(255) null,
    longitude     double       null,
    latitude      double       null,
    streamType    varchar(50)  null,
    mediaServerId varchar(50)  null,
    status        int          null,
    primary key (app, stream, gbId)
);
create table media_server
(
    id                      varchar(255) not null
        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,
    defaultServer           int          not null,
    createTime              varchar(50)  not null,
    updateTime              varchar(50)  not null,
    constraint media_server_i
        unique (ip, httpPort)
);
create table parent_platform
(
    id          int auto_increment,
    id             int auto_increment,
    enable         int          null,
    name           varchar(255) null,
    serverGBId     varchar(50) not null,
    serverGBDomain varchar(50) null,
    serverIP       varchar(50) null,
    serverGBId     varchar(50)  not null,
    serverGBDomain varchar(50)  null,
    serverIP       varchar(50)  null,
    serverPort     int          null,
    deviceGBId     varchar(50) not null,
    deviceIp       varchar(50) null,
    devicePort     varchar(50) null,
    deviceGBId     varchar(50)  not null,
    deviceIp       varchar(50)  null,
    devicePort     varchar(50)  null,
    username       varchar(255) null,
    password       varchar(50) null,
    expires        varchar(50) null,
    keepTimeout    varchar(50) null,
    transport      varchar(50) null,
    characterSet   varchar(50) null,
    password       varchar(50)  null,
    expires        varchar(50)  null,
    keepTimeout    varchar(50)  null,
    transport      varchar(50)  null,
    characterSet   varchar(50)  null,
    ptz            int          null,
    rtcp           int          null,
    status         bit(1)   null,
    status         bit          null,
    primary key (id, serverGBId)
);
@@ -121,7 +154,7 @@
create table platform_gb_stream
(
    platformId varchar(50) not null,
    platformId varchar(50)  not null,
    app        varchar(255) not null,
    stream     varchar(255) not null,
    primary key (platformId, app, stream)
@@ -129,7 +162,7 @@
create table stream_proxy
(
    type           varchar(50) not null,
    type           varchar(50)  not null,
    app            varchar(255) not null,
    stream         varchar(255) not null,
    url            varchar(255) null,
@@ -137,11 +170,12 @@
    dst_url        varchar(255) null,
    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,
    rtp_type       varchar(50)  null,
    mediaServerId  varchar(50)  null,
    enable_hls     bit          null,
    enable_mp4     bit          null,
    enable         bit          not null,
    createTime     varchar(50)  not null,
    primary key (app, stream)
);
@@ -149,11 +183,12 @@
(
    app              varchar(255) not null,
    stream           varchar(255) not null,
    totalReaderCount varchar(50) null,
    totalReaderCount varchar(50)  null,
    originType       int          null,
    originTypeStr    varchar(50) null,
    originTypeStr    varchar(50)  null,
    createStamp      int          null,
    aliveSecond      int          null,
    mediaServerId    varchar(50)  null,
    primary key (app, stream)
);
@@ -164,31 +199,6 @@
    username    varchar(255) not null,
    password    varchar(255) not null,
    roleId      int          not null,
    create_time varchar(50) not null
    create_time varchar(50)  not null
);
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/VideoManagerConstants.java
@@ -12,6 +12,8 @@
    public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
    public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS";
    public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
    public static final String DEVICE_PREFIX = "VMP_DEVICE_";
@@ -45,4 +47,8 @@
    public static final String EVENT_OUTLINE_UNREGISTER = "1";
    
    public static final String EVENT_OUTLINE_TIMEOUT = "2";
    public static final String MEDIA_SSRC_USED_PREFIX = "VMP_media_used_ssrc_";
    public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_media_transaction_";
}
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
@@ -1,13 +1,16 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
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;
import java.text.SimpleDateFormat;
import java.util.Date;
@Configuration("mediaConfig")
public class MediaConfig implements IMediaServerItem {
public class MediaConfig{
    @Value("${media.id:}")
    private String id;
@@ -20,6 +23,9 @@
    @Value("${sip.ip}")
    private String sipIp;
    @Value("${sip.domain}")
    private String sipDomain;
    @Value("${media.sdp-ip:${media.ip}}")
    private String sdpIp;
@@ -66,29 +72,12 @@
    @Value("${media.record-assist-port:0}")
    private Integer recordAssistPort = 0;
    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;
    }
    public void setIp(String ip) {
        this.ip = ip;
    }
    public String getHookIp() {
@@ -100,76 +89,24 @@
    }
    public void setHookIp(String hookIp) {
        this.hookIp = hookIp;
    }
    public String getSipIp() {
        return sipIp;
    }
    public void setSipIp(String sipIp) {
        this.sipIp = sipIp;
    }
    public void setSdpIp(String sdpIp) {
        this.sdpIp = sdpIp;
    }
    public void setStreamIp(String streamIp) {
        this.streamIp = streamIp;
    }
    public int getHttpPort() {
        return httpPort;
    }
    @Override
    public void setHttpPort(int httpPort) {
    }
    public void setHttpPort(Integer httpPort) {
        this.httpPort = httpPort;
    }
    public int getHttpSSlPort() {
        return httpSSlPort;
    }
    @Override
    public void setHttpSSlPort(int httpSSlPort) {
    }
    public void setHttpSSlPort(Integer httpSSlPort) {
        this.httpSSlPort = httpSSlPort;
    }
    public int getRtmpPort() {
        return rtmpPort;
    }
    @Override
    public void setRtmpPort(int rtmpPort) {
    }
    public void setRtmpPort(Integer rtmpPort) {
        this.rtmpPort = rtmpPort;
    }
    public int getRtmpSSlPort() {
        return rtmpSSlPort;
    }
    @Override
    public void setRtmpSSlPort(int rtmpSSlPort) {
    }
    public void setRtmpSSlPort(Integer rtmpSSlPort) {
        this.rtmpSSlPort = rtmpSSlPort;
    }
    public int getRtpProxyPort() {
@@ -181,102 +118,36 @@
    }
    @Override
    public void setRtpProxyPort(int rtpProxyPort) {
    }
    public void setRtpProxyPort(Integer rtpProxyPort) {
        this.rtpProxyPort = rtpProxyPort;
    }
    public int getRtspPort() {
        return rtspPort;
    }
    @Override
    public void setRtspPort(int rtspPort) {
    }
    public void setRtspPort(Integer rtspPort) {
        this.rtspPort = rtspPort;
    }
    public int getRtspSSLPort() {
        return rtspSSLPort;
    }
    @Override
    public void setRtspSSLPort(int rtspSSLPort) {
    }
    public void setRtspSSLPort(Integer 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;
    }
    @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() {
@@ -295,13 +166,11 @@
        }
    }
    public MediaServerItem getMediaSerItem(){
        MediaServerItem mediaServerItem = new MediaServerItem();
        mediaServerItem.setId(id);
        mediaServerItem.setIp(ip);
        mediaServerItem.setDocker(true);
        mediaServerItem.setDefaultServer(true);
        mediaServerItem.setHookIp(hookIp);
        mediaServerItem.setSdpIp(sdpIp);
        mediaServerItem.setStreamIp(streamIp);
@@ -318,39 +187,12 @@
        mediaServerItem.setRtpEnable(rtpEnable);
        mediaServerItem.setRtpPortRange(rtpPortRange);
        mediaServerItem.setRecordAssistPort(recordAssistPort);
        mediaServerItem.setCreateTime(createTime);
        mediaServerItem.setUpdateTime(updateTime);
        mediaServerItem.setCount(count);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        mediaServerItem.setCreateTime(format.format(new Date(System.currentTimeMillis())));
        mediaServerItem.setUpdateTime(format.format(new Date(System.currentTimeMillis())));
        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,6 +1,6 @@
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 com.genersoft.iot.vmp.service.IMediaServerService;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.http.HttpHost;
@@ -49,7 +49,7 @@
        @Override
        protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) {
            String queryStr = super.rewriteQueryStringFromRequest(servletRequest, queryString);
            IMediaServerItem mediaInfo = getMediaInfoByUri(servletRequest.getRequestURI());
            MediaServerItem mediaInfo = getMediaInfoByUri(servletRequest.getRequestURI());
            if (mediaInfo != null) {
                if (!StringUtils.isEmpty(queryStr)) {
                    queryStr += "&secret=" + mediaInfo.getSecret();
@@ -88,7 +88,7 @@
        @Override
        protected String getTargetUri(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            MediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            String uri = null;
            if (mediaInfo != null) {
@@ -106,7 +106,7 @@
        @Override
        protected HttpHost getTargetHost(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            MediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            HttpHost host;
            if (mediaInfo != null) {
                host = new HttpHost(mediaInfo.getIp(), mediaInfo.getHttpPort());
@@ -120,7 +120,7 @@
        /**
         * 根据uri获取流媒体信息
         */
        IMediaServerItem getMediaInfoByUri(String uri){
        MediaServerItem getMediaInfoByUri(String uri){
            String[] split = uri.split("/");
            String mediaServerId = split[2];
            return mediaServerService.getOne(mediaServerId);
@@ -132,7 +132,7 @@
        @Override
        protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            MediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            String url = super.rewriteUrlFromRequest(servletRequest);
            if (mediaInfo == null) {
                return  url;
@@ -186,7 +186,7 @@
        @Override
        protected String getTargetUri(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            MediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            String uri = null;
            if (mediaInfo != null) {
@@ -204,7 +204,7 @@
        @Override
        protected HttpHost getTargetHost(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            MediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            HttpHost host;
            if (mediaInfo != null) {
                host = new HttpHost(mediaInfo.getIp(), mediaInfo.getRecordAssistPort());
@@ -218,7 +218,7 @@
        /**
         * 根据uri获取流媒体信息
         */
        IMediaServerItem getMediaInfoByUri(String uri){
        MediaServerItem getMediaInfoByUri(String uri){
            String[] split = uri.split("/");
            String mediaServerId = split[2];
            return mediaServerService.getOne(mediaServerId);
@@ -230,7 +230,7 @@
        @Override
        protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) {
            String requestURI = servletRequest.getRequestURI();
            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            MediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
            String url = super.rewriteUrlFromRequest(servletRequest);
            if (mediaInfo == null) {
                return  url;
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
@@ -1,5 +1,7 @@
package com.genersoft.iot.vmp.conf;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -10,6 +12,8 @@
import com.alibaba.fastjson.parser.ParserConfig;
import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
 * @Description:Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置
@@ -19,6 +23,37 @@
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.poolMaxTotal:1000}")
    private int poolMaxTotal;
    @Value("${spring.redis.poolMaxIdle:500}")
    private int poolMaxIdle;
    @Value("${spring.redis.poolMaxWait:5}")
    private int poolMaxWait;
    @Bean
    public JedisPool jedisPool() {
        if (StringUtils.isBlank(password)) {
            password = null;
        }
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(poolMaxIdle);
        poolConfig.setMaxTotal(poolMaxTotal);
        // 秒转毫秒
        poolConfig.setMaxWaitMillis(poolMaxWait * 1000L);
        JedisPool jp = new JedisPool(poolConfig, host, port, timeout * 1000, password, database);
        return jp;
    }
    @Bean("redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
@@ -34,7 +69,7 @@
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        // 使用fastjson时需设置此项,否则会报异常not support type
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        return template;
    }
src/main/java/com/genersoft/iot/vmp/conf/SipDeviceRunner.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -12,6 +12,7 @@
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import gov.nist.javax.sip.SipProviderImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,7 +40,7 @@
    @Autowired
    private SipSubscribe sipSubscribe;
    private SipStack sipStack;
    private SipStackImpl sipStack;
    private SipFactory sipFactory;
@@ -52,7 +53,7 @@
    private ThreadPoolExecutor initSipServer() {
        
        int processThreadNum = Runtime.getRuntime().availableProcessors() * 10;
        LinkedBlockingQueue<Runnable> processQueue = new LinkedBlockingQueue<Runnable>(10000);
        LinkedBlockingQueue<Runnable> processQueue = new LinkedBlockingQueue<>(10000);
        processThreadPool = new ThreadPoolExecutor(processThreadNum,processThreadNum,
                0L,TimeUnit.MILLISECONDS,processQueue,
                new ThreadPoolExecutor.CallerRunsPolicy());
@@ -88,17 +89,14 @@
    @Bean("tcpSipProvider")
    @DependsOn("sipStack")
    private SipProvider startTcpListener() {
    private SipProviderImpl startTcpListener() {
        ListeningPoint tcpListeningPoint = null;
        SipProvider tcpSipProvider  = null;
        SipProviderImpl tcpSipProvider  = null;
        try {
            tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "TCP");
            tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
            tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
            tcpSipProvider.addSipListener(this);
            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) {
@@ -114,13 +112,14 @@
    
    @Bean("udpSipProvider")
    @DependsOn("sipStack")
    private SipProvider startUdpListener() {
    private SipProviderImpl startUdpListener() {
        ListeningPoint udpListeningPoint = null;
        SipProvider udpSipProvider = null;
        SipProviderImpl udpSipProvider = null;
        try {
            udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "UDP");
            udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
            udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
            udpSipProvider.addSipListener(this);
//            udpSipProvider.setAutomaticDialogSupportEnabled(false);
        } catch (TransportNotSupportedException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
@@ -141,7 +140,7 @@
     */
    @Override
    public void processRequest(RequestEvent evt) {
//        logger.debug(evt.getRequest().toString());
        logger.debug(evt.getRequest().toString());
        // 由于jainsip是单线程程序,为提高性能并发处理
        processThreadPool.execute(() -> {
            if (processorFactory != null) {
@@ -153,7 +152,7 @@
    @Override
    public void processResponse(ResponseEvent evt) {
        Response response = evt.getResponse();
//        logger.debug(evt.getResponse().toString());
        logger.debug(evt.getResponse().toString());
        int status = response.getStatusCode();
        if (((status >= 200) && (status < 300)) || status == 401) { // Success!
            ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
@@ -163,6 +162,7 @@
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (evt.getResponse() != null && sipSubscribe.getOkSubscribesSize() > 0 ) {
                CallIdHeader callIdHeader = (CallIdHeader)evt.getResponse().getHeader(CallIdHeader.NAME);
                if (callIdHeader != null) {
@@ -220,7 +220,6 @@
    @Override
    public void processIOException(IOExceptionEvent exceptionEvent) {
        // TODO Auto-generated method stub
    }
    /**
@@ -236,7 +235,6 @@
    @Override
    public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
        // TODO Auto-generated method stub
    }
    /**
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java
New file
@@ -0,0 +1,70 @@
package com.genersoft.iot.vmp.gb28181.bean;
import javax.sip.message.Request;
public class SsrcTransaction {
    private String deviceId;
    private String channelId;
    private String ssrc;
    private String streamId;
    private byte[] transaction;
    private byte[] dialog;
    private String mediaServerId;
    public String getDeviceId() {
        return deviceId;
    }
    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }
    public String getChannelId() {
        return channelId;
    }
    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }
    public String getSsrc() {
        return ssrc;
    }
    public void setSsrc(String ssrc) {
        this.ssrc = ssrc;
    }
    public String getStreamId() {
        return streamId;
    }
    public void setStreamId(String streamId) {
        this.streamId = streamId;
    }
    public byte[] getTransaction() {
        return transaction;
    }
    public void setTransaction(byte[] transaction) {
        this.transaction = transaction;
    }
    public byte[] getDialog() {
        return dialog;
    }
    public void setDialog(byte[] dialog) {
        this.dialog = dialog;
    }
    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,7 +6,6 @@
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;
@@ -77,7 +76,7 @@
                }
                stream.append(sendRtpItem.getStreamId());
                redisCatchStorage.deleteSendRTPServer(event.getPlatformGbID(), sendRtpItem.getChannelId());
                IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                Map<String, Object> param = new HashMap<>();
                param.put("vhost", "__defaultVhost__");
                param.put("app", app.toString());
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java
New file
@@ -0,0 +1,140 @@
package com.genersoft.iot.vmp.gb28181.session;
import com.genersoft.iot.vmp.utils.ConfigConst;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
public class SsrcConfig {
    /**
     * zlm流媒体服务器Id
     */
    private String mediaServerId;
    private String ssrcPrefix;
    /**
     * zlm流媒体服务器已用会话句柄
     */
    private List<String> isUsed;
    /**
     * zlm流媒体服务器可用会话句柄
     */
    private List<String> notUsed;
    public SsrcConfig() {
    }
    public SsrcConfig(String mediaServerId, Set<String> usedSet, String sipDomain) {
        this.mediaServerId = mediaServerId;
        this.isUsed = new ArrayList<>();
        this.ssrcPrefix = sipDomain.substring(3, 8);
        this.notUsed = new ArrayList<>();
        for (int i = 1; i < ConfigConst.MAX_STRTEAM_COUNT; i++) {
            String ssrc;
            if (i < 10) {
                ssrc = "000" + i;
            } else if (i < 100) {
                ssrc = "00" + i;
            } else if (i < 1000) {
                ssrc = "0" + i;
            } else {
                ssrc = String.valueOf(i);
            }
            if (null == usedSet || !usedSet.contains(ssrc)) {
                this.notUsed.add(ssrc);
            } else {
                this.isUsed.add(ssrc);
            }
        }
    }
    /**
     * 获取视频预览的SSRC值,第一位固定为0
     * @return ssrc
     */
    public String getPlaySsrc() {
        return "0" + getSsrcPrefix() + getSN();
    }
    /**
     * 获取录像回放的SSRC值,第一位固定为1
     *
     */
    public String getPlayBackSsrc() {
        return "1" + getSsrcPrefix() + getSN();
    }
    /**
     * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
     * @param ssrc 需要重置的ssrc
     */
    public void releaseSsrc(String ssrc) {
        if (ssrc == null) {
            return;
        }
        String sn = ssrc.substring(6);
        try {
            isUsed.remove(sn);
            notUsed.add(sn);
        }catch (NullPointerException e){
            System.out.printf("11111");
        }
    }
    /**
     * 获取后四位数SN,随机数
     *
     */
    private String getSN() {
        String sn = null;
        int index = 0;
        if (notUsed.size() == 0) {
            throw new RuntimeException("ssrc已经用完");
        } else if (notUsed.size() == 1) {
            sn = notUsed.get(0);
        } else {
            index = new Random().nextInt(notUsed.size() - 1);
            sn = notUsed.get(index);
        }
        notUsed.remove(index);
        isUsed.add(sn);
        return sn;
    }
    public String getSsrcPrefix() {
        return ssrcPrefix;
    }
    public String getMediaServerId() {
        return mediaServerId;
    }
    public void setMediaServerId(String mediaServerId) {
        this.mediaServerId = mediaServerId;
    }
    public void setSsrcPrefix(String ssrcPrefix) {
        this.ssrcPrefix = ssrcPrefix;
    }
    public List<String> getIsUsed() {
        return isUsed;
    }
    public void setIsUsed(List<String> isUsed) {
        this.isUsed = isUsed;
    }
    public List<String> getNotUsed() {
        return notUsed;
    }
    public void setNotUsed(List<String> notUsed) {
        this.notUsed = notUsed;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcUtil.java
File was deleted
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
@@ -1,9 +1,23 @@
package com.genersoft.iot.vmp.gb28181.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.message.Request;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.utils.SerializeUtils;
import com.genersoft.iot.vmp.utils.redis.JedisUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import gov.nist.javax.sip.stack.SIPDialog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**    
@@ -14,50 +28,85 @@
@Component
public class VideoStreamSessionManager {
    private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> ssrcMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> streamIdMap = new ConcurrentHashMap<>();
    @Autowired
    private RedisUtil redisUtil;
    public String createPlaySsrc(){
        return SsrcUtil.getPlaySsrc();
    public void put(String deviceId, String channelId ,String ssrc, String streamId, String mediaServerId, ClientTransaction transaction){
        SsrcTransaction ssrcTransaction = new SsrcTransaction();
        ssrcTransaction.setDeviceId(deviceId);
        ssrcTransaction.setChannelId(channelId);
        ssrcTransaction.setStreamId(streamId);
        byte[] transactionByteArray = SerializeUtils.serialize(transaction);
        ssrcTransaction.setTransaction(transactionByteArray);
        ssrcTransaction.setSsrc(ssrc);
        ssrcTransaction.setMediaServerId(mediaServerId);
        redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX +  deviceId + "_" + channelId, ssrcTransaction);
    }
    public String createPlayBackSsrc(){
        return SsrcUtil.getPlayBackSsrc();
    public void put(String deviceId, String channelId , Dialog dialog){
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction != null) {
            byte[] dialogByteArray = SerializeUtils.serialize(dialog);
            ssrcTransaction.setDialog(dialogByteArray);
        }
        redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX +  deviceId + "_" + channelId, ssrcTransaction);
    }
    public void put(String deviceId, String channelId ,String ssrc, String streamId, ClientTransaction transaction){
        sessionMap.put(deviceId + "_" + channelId, transaction);
        ssrcMap.put(deviceId + "_" + channelId, ssrc);
        streamIdMap.put(deviceId + "_" + channelId, streamId);
    }
    
    public ClientTransaction getTransaction(String deviceId, String channelId){
        return sessionMap.get(deviceId + "_" + channelId);
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction == null) return null;
        byte[] transactionByteArray = ssrcTransaction.getTransaction();
        ClientTransaction clientTransaction = (ClientTransaction)SerializeUtils.deSerialize(transactionByteArray);
        return clientTransaction;
    }
    public SIPDialog getDialog(String deviceId, String channelId){
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction == null) return null;
        byte[] dialogByteArray = ssrcTransaction.getDialog();
        if (dialogByteArray == null) return null;
        SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
        return dialog;
    }
    public SsrcTransaction getSsrcTransaction(String deviceId, String channelId){
        SsrcTransaction ssrcTransaction = (SsrcTransaction)redisUtil.get(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + deviceId + "_" + channelId);
        return ssrcTransaction;
    }
    public String getStreamId(String deviceId, String channelId){
        return streamIdMap.get(deviceId + "_" + channelId);
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction == null) return null;
        return ssrcTransaction.getStreamId();
    }
    public String getMediaServerId(String deviceId, String channelId){
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction == null) return null;
        return ssrcTransaction.getMediaServerId();
    }
    public String getSSRC(String deviceId, String channelId){
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction == null) return null;
        return ssrcTransaction.getSsrc();
    }
    
    public void remove(String deviceId, String channelId) {
        sessionMap.remove(deviceId + "_" + channelId);
        if (ssrcMap.get(deviceId + "_" + channelId) != null) {
            SsrcUtil.releaseSsrc(ssrcMap.get(deviceId + "_" + channelId));
        SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
        if (ssrcTransaction == null) return;
        redisUtil.del(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX +  deviceId + "_" + channelId);
    }
    public List<SsrcTransaction> getAllSsrc() {
        List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX));
        List<SsrcTransaction> result= new ArrayList<>();
        for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
            String key = (String)ssrcTransactionKeys.get(i);
            SsrcTransaction ssrcTransaction = (SsrcTransaction)redisUtil.get(key);
            result.add(ssrcTransaction);
        }
        ssrcMap.remove(deviceId + "_" + channelId);
        streamIdMap.remove(deviceId + "_" + channelId);
    }
    public ConcurrentHashMap<String, ClientTransaction> getSessionMap() {
        return sessionMap;
    }
    public ConcurrentHashMap<String, String> getSsrcMap() {
        return ssrcMap;
    }
    public ConcurrentHashMap<String, String> getStreamIdMap() {
        return streamIdMap;
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
@@ -7,6 +7,7 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
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;
@@ -107,7 +108,6 @@
    @Autowired
    private IMediaServerService mediaServerService;
    // 注:这里使用注解会导致循环依赖注入,暂用springBean
    private SipProvider tcpSipProvider;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -3,8 +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;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
/**    
 * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -92,7 +92,7 @@
     * @param device  视频设备
     * @param channelId  预览通道
     */
    void playStreamCmd(IMediaServerItem mediaServerItem, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    
    /**
     * 请求回放视频流
@@ -102,7 +102,7 @@
     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */
    void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    /**
     * 请求历史媒体下载
@@ -113,12 +113,10 @@
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     * @param downloadSpeed 下载倍速参数
     */ 
    void downloadStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, String downloadSpeed, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    /**
     * 视频流停止
     *
     * @param ssrc  ssrc
     */
    void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent);
    void streamByeCmd(String deviceId, String channelId);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -1,6 +1,8 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.HashSet;
import javax.sip.*;
import javax.sip.address.SipURI;
@@ -8,18 +10,21 @@
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
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.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.media.zlm.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.stack.SIPDialog;
import gov.nist.javax.sip.stack.SIPTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,7 +40,6 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
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;
/**    
@@ -55,12 +59,12 @@
    @Lazy
    @Autowired
    @Qualifier(value="tcpSipProvider")
    private SipProvider tcpSipProvider;
    private SipProviderImpl tcpSipProvider;
    @Lazy
    @Autowired
    @Qualifier(value="udpSipProvider")
    private SipProvider udpSipProvider;
    private SipProviderImpl udpSipProvider;
    @Autowired
    private SIPRequestHeaderProvider headerProvider;
@@ -75,9 +79,6 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Autowired
    private UserSetup userSetup;
    @Autowired
@@ -85,6 +86,11 @@
    @Autowired
    private SipSubscribe sipSubscribe;
    @Autowired
    private IMediaServerService mediaServerService;
    private SIPDialog dialog;
    public SipConfig getSipConfig() {
        return sipConfig;
@@ -334,26 +340,13 @@
      * @param errorEvent sip错误订阅
      */
    @Override
    public void playStreamCmd(IMediaServerItem mediaServerItem, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
        String streamId = null;
    public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
        String streamId = ssrcInfo.getStreamId();
        try {
            if (device == null) return;
            String streamMode = device.getStreamMode().toUpperCase();
            String ssrc = streamSession.createPlaySsrc();
            if (mediaServerItem.isRtpEnable()) {
                streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            }else {
                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);
            logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
            // 添加订阅
            JSONObject subscribeKey = new JSONObject();
            subscribeKey.put("app", "rtp");
@@ -361,7 +354,7 @@
            subscribeKey.put("regist", true);
            subscribeKey.put("mediaServerId", mediaServerItem.getId());
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
                    (IMediaServerItem mediaServerItemInUse, JSONObject json)->{
                    (MediaServerItem mediaServerItemInUse, JSONObject json)->{
                if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
                event.response(mediaServerItemInUse, json);
                subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
@@ -369,7 +362,6 @@
            //
            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 "+ mediaServerItem.getSdpIp() +"\r\n");
            content.append("s=Play\r\n");
            content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
@@ -377,11 +369,11 @@
            if (userSetup.isSeniorSdp()) {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 126 125 99 34 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
@@ -402,11 +394,11 @@
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
@@ -421,20 +413,25 @@
                }
            }
            content.append("y="+ssrc+"\r\n");//ssrc
            content.append("y="+ssrcInfo.getSsrc()+"\r\n");//ssrc
            String tm = Long.toString(System.currentTimeMillis());
            CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
                    : udpSipProvider.getNewCallId();
            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc, callIdHeader);
            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrcInfo.getSsrc(), callIdHeader);
            ClientTransaction transaction = transmitRequest(device, request, (e -> {
            String finalStreamId = streamId;
            transmitRequest(device, request, (e -> {
                streamSession.remove(device.getDeviceId(), channelId);
                mediaServerService.releaseSsrc(mediaServerItem, ssrcInfo.getSsrc());
                errorEvent.response(e);
            }));
            streamSession.put(device.getDeviceId(), channelId ,ssrc,streamId, transaction);
            }), e ->{
                streamSession.put(device.getDeviceId(), channelId ,ssrcInfo.getSsrc(), finalStreamId, mediaServerItem.getId(),e.getClientTransaction());
                streamSession.put(device.getDeviceId(), channelId , e.getDialog());
            });
            
        } catch ( SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
@@ -450,30 +447,21 @@
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */ 
    @Override
    public void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
    public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
            , SipSubscribe.Event errorEvent) {
        try {
            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);
            logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStreamId(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
            // 添加订阅
            JSONObject subscribeKey = new JSONObject();
            subscribeKey.put("app", "rtp");
            subscribeKey.put("stream", streamId);
            subscribeKey.put("stream", ssrcInfo.getStreamId());
            subscribeKey.put("regist", true);
            subscribeKey.put("mediaServerId", mediaServerItem.getId());
            logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString());
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
                    (IMediaServerItem mediaServerItemInUse, JSONObject json)->{
                    (MediaServerItem mediaServerItemInUse, JSONObject json)->{
                if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
                event.response(mediaServerItemInUse, json);
                subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
@@ -494,11 +482,11 @@
            if (userSetup.isSeniorSdp()) {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 126 125 99 34 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
@@ -519,11 +507,11 @@
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
@@ -538,7 +526,7 @@
                }
            }
            content.append("y="+ssrc+"\r\n");//ssrc
            content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
            
            String tm = Long.toString(System.currentTimeMillis());
@@ -547,9 +535,11 @@
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader);
            ClientTransaction transaction = transmitRequest(device, request, errorEvent);
            streamSession.put(device.getDeviceId(), channelId, ssrc, streamId, transaction);
            transmitRequest(device, request, errorEvent, okEvent -> {
                Dialog dialog = okEvent.getClientTransaction().getDialog();
                streamSession.put(device.getDeviceId(), channelId, ssrcInfo.getSsrc(), ssrcInfo.getStreamId(), mediaServerItem.getId(), okEvent.getClientTransaction());
                streamSession.put(device.getDeviceId(), channelId, dialog);
            });
        } catch ( SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
        }
@@ -565,30 +555,20 @@
     * @param downloadSpeed 下载倍速参数
     */ 
    @Override
    public void downloadStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, String downloadSpeed, ZLMHttpHookSubscribe.Event event
    public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, ZLMHttpHookSubscribe.Event event
            , SipSubscribe.Event errorEvent) {
        try {
            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);
            logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStreamId(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
            // 添加订阅
            JSONObject subscribeKey = new JSONObject();
            subscribeKey.put("app", "rtp");
            subscribeKey.put("stream", streamId);
            subscribeKey.put("stream", ssrcInfo.getStreamId());
            subscribeKey.put("regist", true);
            subscribeKey.put("mediaServerId", mediaServerItem.getId());
            logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString());
            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
                    (IMediaServerItem mediaServerItemInUse, JSONObject json)->{
                    (MediaServerItem mediaServerItemInUse, JSONObject json)->{
                if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
                event.response(mediaServerItemInUse, json);
                subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
@@ -609,11 +589,11 @@
            if (userSetup.isSeniorSdp()) {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 126 125 99 34 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 126 125 99 34 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
@@ -634,11 +614,11 @@
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
                    content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
@@ -654,7 +634,7 @@
            }
            content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
            content.append("y="+ssrc+"\r\n");//ssrc
            content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
            
            String tm = Long.toString(System.currentTimeMillis());
@@ -664,7 +644,7 @@
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader);
            ClientTransaction transaction = transmitRequest(device, request, errorEvent);
            streamSession.put(device.getDeviceId(), channelId, ssrc, streamId, transaction);
            streamSession.put(device.getDeviceId(), channelId, ssrcInfo.getSsrc(), ssrcInfo.getStreamId(), mediaServerItem.getId(), transaction);
        } catch ( SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
@@ -684,53 +664,35 @@
     */
    @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) {
                logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
                return;
            }
            Dialog dialog = transaction.getDialog();
            SIPDialog dialog = streamSession.getDialog(deviceId, channelId);
            if (dialog == null) {
                logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
                return;
            }
            SipStack sipStack = udpSipProvider.getSipStack();
            SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
            if (dialog != sipDialog) {
                dialog = sipDialog;
            }else {
                dialog.setSipProvider(udpSipProvider);
                try {
                    Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
                    sipStackField.setAccessible(true);
                    sipStackField.set(dialog, sipStack);
                    Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
                    eventListenersField.setAccessible(true);
                    eventListenersField.set(dialog, new HashSet<>());
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            Request byeRequest = dialog.createRequest(Request.BYE);
            SipURI byeURI = (SipURI) byeRequest.getRequestURI();
            SIPRequest request = (SIPRequest)transaction.getRequest();
@@ -752,7 +714,12 @@
            dialog.sendRequest(clientTransaction);
            streamSession.remove(deviceId, channelId);
            SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId);
            if (ssrcTransaction != null) {
                MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
                mediaServerService.releaseSsrc(mediaServerItem, ssrcTransaction.getSsrc());
                streamSession.remove(deviceId, channelId);
            }
        } catch (SipException | ParseException e) {
            e.printStackTrace();
        }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java
@@ -13,7 +13,6 @@
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;
@@ -81,7 +80,7 @@
            while (!rtpPushed) {
                try {
                    if (System.currentTimeMillis() - startTime < 30 * 1000) {
                        IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                        MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                        if (zlmrtpServerFactory.isStreamReady(mediaInfo, streamInfo.getApp(), streamInfo.getStreamId())) {
                            rtpPushed = true;
                            logger.info("已获取设备推流[{}/{}],开始向上级推流[{}:{}]",
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
@@ -15,7 +15,6 @@
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;
@@ -65,7 +64,7 @@
                param.put("stream",streamId);
                param.put("ssrc",sendRtpItem.getSsrc());
                logger.info("停止向上级推流:" + streamId);
                IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
                redisCatchStorage.deleteSendRTPServer(platformGbId, channelId);
                if (zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId) == 0) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
@@ -11,14 +11,11 @@
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;
@@ -97,7 +94,7 @@
                // 查询平台下是否有该通道
                DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
                GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId);
                IMediaServerItem mediaServerItem = null;
                MediaServerItem mediaServerItem = null;
                // 不是通道可能是直播流
                if (channel != null && gbStream == null ) {
                    if (channel.getStatus() == 0) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java
@@ -3,14 +3,18 @@
import java.text.ParseException;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import gov.nist.javax.sip.ResponseEventExt;
import gov.nist.javax.sip.stack.SIPDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
@@ -27,6 +31,9 @@
public class InviteResponseProcessor implements ISIPResponseProcessor {
     private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
    @Autowired
    private VideoStreamSessionManager streamSession;
    /**
     * 处理invite响应
@@ -46,7 +53,7 @@
            // 下发ack
            if (statusCode == Response.OK) {
                ResponseEventExt event = (ResponseEventExt)evt;
                Dialog dialog = evt.getDialog();
                SIPDialog dialog = (SIPDialog)evt.getDialog();
                CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
                Request reqAck = dialog.createAck(cseq.getSeqNumber());
                SipURI requestURI = (SipURI) reqAck.getRequestURI();
@@ -54,7 +61,12 @@
                requestURI.setPort(event.getRemotePort());
                reqAck.setRequestURI(requestURI);
                logger.info("向 " + event.getRemoteIpAddress() + ":" + event.getRemotePort() + "回复ack");
                SipURI sipURI = (SipURI)dialog.getRemoteParty().getURI();
                String deviceId = requestURI.getUser();
                String channelId = sipURI.getUser();
                dialog.sendAck(reqAck);
            }
        } catch (InvalidArgumentException | SipException e) {
            e.printStackTrace();
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -3,15 +3,14 @@
import java.util.List;
import java.util.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
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.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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.service.IPlayService;
@@ -41,7 +40,6 @@
public class ZLMHttpHookListener {
    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
    @Autowired
    private SIPCommander cmder;
@@ -125,7 +123,7 @@
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_play, json);
        if (subscribe != null ) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
@@ -150,7 +148,7 @@
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
        if (subscribe != null) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
@@ -237,7 +235,7 @@
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_shell_login, json);
        if (subscribe != null ) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
@@ -264,7 +262,7 @@
        String mediaServerId = json.getString("mediaServerId");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
        if (subscribe != null ) {
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            if (mediaInfo != null) {
                subscribe.response(mediaInfo, json);
            }
@@ -297,7 +295,7 @@
                }
            }else {
                if (!"rtp".equals(app) ){
                    IMediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
                    MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
                    if (regist) {
                        zlmMediaListManager.addMedia(mediaServerItem, app, streamId);
                    }else {
@@ -369,7 +367,7 @@
            logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
        }
        String mediaServerId = json.getString("mediaServerId");
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (userSetup.isAutoApplyPlay() && mediaInfo != null) {
            String app = json.getString("app");
            String streamId = json.getString("stream");
@@ -381,7 +379,13 @@
                    Device device = storager.queryVideoDevice(deviceId);
                    if (device != null) {
                        UUID uuid = UUID.randomUUID();
                        cmder.playStreamCmd(mediaInfo, device, channelId, (IMediaServerItem mediaServerItemInuse, JSONObject response) -> {
                        SSRCInfo ssrcInfo;
                        String streamId2 = null;
                        if (mediaInfo.isRtpEnable()) {
                            streamId2 = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
                        }
                        ssrcInfo = mediaServerService.openRTPServer(mediaInfo, streamId2);
                        cmder.playStreamCmd(mediaInfo, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
                            logger.info("收到订阅消息: " + response.toJSONString());
                            playService.onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString());
                        }, null);
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
@@ -1,7 +1,6 @@
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;
@@ -32,7 +31,7 @@
    }
    public interface Event{
        void response(IMediaServerItem mediaServerItem, JSONObject response);
        void response(MediaServerItem mediaServerItem, JSONObject response);
    }
    private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
@@ -58,6 +57,9 @@
                if (result == null) {
                    result = key.getString(s).equals(hookResponse.getString(s));
                }else {
                    if (key.getString(s) == null) {
                        continue;
                    }
                    result = result && key.getString(s).equals(hookResponse.getString(s));
                }
@@ -83,9 +85,9 @@
                if (result == null) {
                    result = key.getString(s).equals(hookResponse.getString(s));
                }else {
                    if (key.getString(s) == null) continue;
                    result = result && key.getString(s).equals(hookResponse.getString(s));
                }
            }
            if (null != result && result){
                iterator.remove();
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
@@ -1,12 +1,9 @@
package com.genersoft.iot.vmp.media.zlm;
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;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@@ -76,7 +73,7 @@
                    jsonObject.put("stream", streamPushItem.getStream());
                    jsonObject.put("mediaServerId", mediaServerItem.getId());
                    subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_play,jsonObject,
                            (IMediaServerItem mediaServerItemInuse, JSONObject response)->{
                            (MediaServerItem mediaServerItemInuse, JSONObject response)->{
                                updateMedia(mediaServerItem, response.getString("app"), response.getString("stream"));
                            }
                    );
@@ -86,13 +83,13 @@
    }
    public void addMedia(IMediaServerItem mediaServerItem, String app, String streamId) {
    public void addMedia(MediaServerItem mediaServerItem, String app, String streamId) {
        //使用异步更新推流
        updateMedia(mediaServerItem, app, streamId);
    }
    public void updateMedia(IMediaServerItem mediaServerItem, String app, String streamId) {
    public void updateMedia(MediaServerItem mediaServerItem, String app, String streamId) {
        //使用异步更新推流
        zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId, "rtmp", json->{
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
@@ -2,14 +2,11 @@
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;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.*;
@@ -27,7 +24,7 @@
        void run(JSONObject response);
    }
    public JSONObject sendPost(IMediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
    public JSONObject sendPost(MediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
        OkHttpClient client = new OkHttpClient();
        String url = String.format("http://%s:%s/index/api/%s",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
        JSONObject responseJSON = null;
@@ -93,7 +90,7 @@
    }
    public void sendPostForImg(IMediaServerItem mediaServerItem, String api, Map<String, Object> param, String targetPath, String fileName) {
    public void sendPostForImg(MediaServerItem 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",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
        JSONObject responseJSON = null;
@@ -139,7 +136,7 @@
    }
    public JSONObject getMediaList(IMediaServerItem mediaServerItem,String app, String stream, String schema, RequestCallback callback){
    public JSONObject getMediaList(MediaServerItem 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);
@@ -148,15 +145,15 @@
        return sendPost(mediaServerItem, "getMediaList",param, callback);
    }
    public JSONObject getMediaList(IMediaServerItem mediaServerItem,String app, String stream){
    public JSONObject getMediaList(MediaServerItem mediaServerItem, String app, String stream){
        return getMediaList(mediaServerItem, app, stream,null,  null);
    }
    public JSONObject getMediaList(IMediaServerItem mediaServerItem,RequestCallback callback){
    public JSONObject getMediaList(MediaServerItem mediaServerItem, RequestCallback callback){
        return sendPost(mediaServerItem, "getMediaList",null, callback);
    }
    public JSONObject getMediaInfo(IMediaServerItem mediaServerItem,String app, String schema, String stream){
    public JSONObject getMediaInfo(MediaServerItem mediaServerItem, String app, String schema, String stream){
        Map<String, Object> param = new HashMap<>();
        param.put("app",app);
        param.put("schema",schema);
@@ -165,13 +162,13 @@
        return sendPost(mediaServerItem, "getMediaInfo",param, null);
    }
    public JSONObject getRtpInfo(IMediaServerItem mediaServerItem,String stream_id){
    public JSONObject getRtpInfo(MediaServerItem mediaServerItem, String stream_id){
        Map<String, Object> param = new HashMap<>();
        param.put("stream_id",stream_id);
        return sendPost(mediaServerItem, "getRtpInfo",param, null);
    }
    public JSONObject addFFmpegSource(IMediaServerItem mediaServerItem,String src_url, String dst_url, String timeout_ms,
    public JSONObject addFFmpegSource(MediaServerItem 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,41 +182,41 @@
        return sendPost(mediaServerItem, "addFFmpegSource",param, null);
    }
    public JSONObject delFFmpegSource(IMediaServerItem mediaServerItem,String key){
    public JSONObject delFFmpegSource(MediaServerItem mediaServerItem, String key){
        Map<String, Object> param = new HashMap<>();
        param.put("key", key);
        return sendPost(mediaServerItem, "delFFmpegSource",param, null);
    }
    public JSONObject getMediaServerConfig(IMediaServerItem mediaServerItem){
    public JSONObject getMediaServerConfig(MediaServerItem mediaServerItem){
        return sendPost(mediaServerItem, "getServerConfig",null, null);
    }
    public JSONObject setServerConfig(IMediaServerItem mediaServerItem, Map<String, Object> param){
    public JSONObject setServerConfig(MediaServerItem mediaServerItem, Map<String, Object> param){
        return sendPost(mediaServerItem,"setServerConfig",param, null);
    }
    public JSONObject openRtpServer(IMediaServerItem mediaServerItem,Map<String, Object> param){
    public JSONObject openRtpServer(MediaServerItem mediaServerItem, Map<String, Object> param){
        return sendPost(mediaServerItem, "openRtpServer",param, null);
    }
    public JSONObject closeRtpServer(IMediaServerItem mediaServerItem,Map<String, Object> param) {
    public JSONObject closeRtpServer(MediaServerItem mediaServerItem, Map<String, Object> param) {
        return sendPost(mediaServerItem, "closeRtpServer",param, null);
    }
    public JSONObject listRtpServer(IMediaServerItem mediaServerItem) {
    public JSONObject listRtpServer(MediaServerItem mediaServerItem) {
        return sendPost(mediaServerItem, "listRtpServer",null, null);
    }
    public JSONObject startSendRtp(IMediaServerItem mediaServerItem,Map<String, Object> param) {
    public JSONObject startSendRtp(MediaServerItem mediaServerItem, Map<String, Object> param) {
        return sendPost(mediaServerItem, "startSendRtp",param, null);
    }
    public JSONObject stopSendRtp(IMediaServerItem mediaServerItem,Map<String, Object> param) {
    public JSONObject stopSendRtp(MediaServerItem mediaServerItem, Map<String, Object> param) {
        return sendPost(mediaServerItem, "stopSendRtp",param, null);
    }
    public JSONObject addStreamProxy(IMediaServerItem mediaServerItem,String app, String stream, String url, boolean enable_hls, boolean enable_mp4, String rtp_type) {
    public JSONObject addStreamProxy(MediaServerItem 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,7 +228,7 @@
        return sendPost(mediaServerItem, "addStreamProxy",param, null);
    }
    public JSONObject closeStreams(IMediaServerItem mediaServerItem,String app, String stream) {
    public JSONObject closeStreams(MediaServerItem mediaServerItem, String app, String stream) {
        Map<String, Object> param = new HashMap<>();
        param.put("vhost", "__defaultVhost__");
        param.put("app", app);
@@ -240,17 +237,17 @@
        return sendPost(mediaServerItem, "close_streams",param, null);
    }
    public JSONObject getAllSession(IMediaServerItem mediaServerItem) {
    public JSONObject getAllSession(MediaServerItem mediaServerItem) {
        return sendPost(mediaServerItem, "getAllSession",null, null);
    }
    public void kickSessions(IMediaServerItem mediaServerItem, String localPortSStr) {
    public void kickSessions(MediaServerItem mediaServerItem, String localPortSStr) {
        Map<String, Object> param = new HashMap<>();
        param.put("local_port", localPortSStr);
        sendPost(mediaServerItem, "kick_sessions",param, null);
    }
    public void getSnap(IMediaServerItem mediaServerItem, String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
    public void getSnap(MediaServerItem 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);
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
@@ -2,10 +2,7 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
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;
@@ -21,28 +18,19 @@
    private Logger logger = LoggerFactory.getLogger("ZLMRTPServerFactory");
    @Autowired
    private MediaConfig mediaConfig;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    private int[] portRangeArray = new int[2];
    private int currentPort = 0;
    private Map<String, Integer> currentStreams = null;
    public int createRTPServer(IMediaServerItem mediaServerItem, String streamId) {
        if (currentStreams == null) {
            currentStreams = new HashMap<>();
            JSONObject jsonObject = zlmresTfulUtils.listRtpServer(mediaServerItem);
            if (jsonObject != null) {
                JSONArray data = jsonObject.getJSONArray("data");
                if (data != null) {
                    for (int i = 0; i < data.size(); i++) {
                        JSONObject dataItem = data.getJSONObject(i);
                        currentStreams.put(dataItem.getString("stream_id"), dataItem.getInteger("port"));
                    }
    public int createRTPServer(MediaServerItem mediaServerItem, String streamId) {
        Map<String, Integer> currentStreams = new HashMap<>();
        JSONObject listRtpServerJsonResult = zlmresTfulUtils.listRtpServer(mediaServerItem);
        if (listRtpServerJsonResult != null) {
            JSONArray data = listRtpServerJsonResult.getJSONArray("data");
            if (data != null) {
                for (int i = 0; i < data.size(); i++) {
                    JSONObject dataItem = data.getJSONObject(i);
                    currentStreams.put(dataItem.getString("stream_id"), dataItem.getInteger("port"));
                }
            }
        }
@@ -56,18 +44,18 @@
        Map<String, Object> param = new HashMap<>();
        int result = -1;
        int newPort = getPortFromportRange();
        int newPort = getPortFromportRange(mediaServerItem);
        param.put("port", newPort);
        param.put("enable_tcp", 1);
        param.put("stream_id", streamId);
        JSONObject jsonObject = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
        JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
        if (jsonObject != null) {
            switch (jsonObject.getInteger("code")){
        if (openRtpServerResultJson != null) {
            switch (openRtpServerResultJson.getInteger("code")){
                case 0:
                    result= newPort;
                    break;
                case -300: // id已经存在, 可能已经在其他端口推流
                case -300: // id已经存在, 可能已经在其他端口推流, TODO 也可能是设备不等ack就直接推流了, 需要查询与设置的推流ip端口是否一致
                    Map<String, Object> closeRtpServerParam = new HashMap<>();
                    closeRtpServerParam.put("stream_id", streamId);
                    zlmresTfulUtils.closeRtpServer(mediaServerItem, closeRtpServerParam);
@@ -77,7 +65,7 @@
                    result= createRTPServer(mediaServerItem, streamId);
                    break;
                default:
                    logger.error("创建RTP Server 失败 {}: " + jsonObject.getString("msg"), newPort);
                    logger.error("创建RTP Server 失败 {}: " + openRtpServerResultJson.getString("msg"), newPort);
                    break;
            }
        }else {
@@ -87,7 +75,7 @@
        return result;
    }
    public boolean closeRTPServer(IMediaServerItem serverItem, String streamId) {
    public boolean closeRTPServer(MediaServerItem serverItem, String streamId) {
        boolean result = false;
        if (serverItem !=null){
            Map<String, Object> param = new HashMap<>();
@@ -107,21 +95,25 @@
        return result;
    }
    private int getPortFromportRange() {
    private int getPortFromportRange(MediaServerItem mediaServerItem) {
        int currentPort = mediaServerItem.getCurrentPort();
        if (currentPort == 0) {
            String[] portRangeStrArray = mediaConfig.getRtpPortRange().split(",");
            String[] portRangeStrArray = mediaServerItem.getRtpPortRange().split(",");
            portRangeArray[0] = Integer.parseInt(portRangeStrArray[0]);
            portRangeArray[1] = Integer.parseInt(portRangeStrArray[1]);
        }
        if (currentPort == 0 || currentPort++ > portRangeArray[1]) {
            currentPort = portRangeArray[0];
            mediaServerItem.setCurrentPort(currentPort);
            return portRangeArray[0];
        } else {
            if (currentPort % 2 == 1) {
                currentPort++;
            }
            return currentPort++;
            currentPort++;
            mediaServerItem.setCurrentPort(currentPort);
            return currentPort;
        }
    }
@@ -135,10 +127,14 @@
     * @param tcp 是否为tcp
     * @return SendRtpItem
     */
    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(serverItem, SsrcUtil.getPlaySsrc());
    public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){
        // 使用RTPServer 功能找一个可用的端口
        String playSsrc = serverItem.getSsrcConfig().getPlaySsrc();
        int localPort = createRTPServer(serverItem, playSsrc);
        if (localPort != -1) {
            // TODO 高并发时可能因为未放入缓存而ssrc冲突
            serverItem.getSsrcConfig().releaseSsrc(playSsrc);
            closeRTPServer(serverItem, playSsrc);
        }else {
            logger.error("没有可用的端口");
@@ -168,10 +164,12 @@
     * @param tcp 是否为tcp
     * @return SendRtpItem
     */
    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(serverItem, SsrcUtil.getPlaySsrc());
    public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){
        String playSsrc = serverItem.getSsrcConfig().getPlaySsrc();
        int localPort = createRTPServer(serverItem, playSsrc);
        if (localPort != -1) {
            // TODO 高并发时可能因为未放入缓存而ssrc冲突
            serverItem.getSsrcConfig().releaseSsrc(ssrc);
            closeRTPServer(serverItem, playSsrc);
        }else {
            logger.error("没有可用的端口");
@@ -194,7 +192,7 @@
    /**
     * 调用zlm RESTful API —— startSendRtp
     */
    public Boolean startSendRtpStream(IMediaServerItem mediaServerItem, Map<String, Object>param) {
    public Boolean startSendRtpStream(MediaServerItem mediaServerItem, Map<String, Object>param) {
        Boolean result = false;
        JSONObject jsonObject = zlmresTfulUtils.startSendRtp(mediaServerItem, param);
        if (jsonObject == null) {
@@ -219,7 +217,7 @@
    /**
     * 查询待转推的流是否就绪
     */
    public Boolean isStreamReady(IMediaServerItem mediaServerItem, String app, String streamId) {
    public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
        return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
    }
@@ -229,7 +227,7 @@
     * @param streamId
     * @return
     */
    public int totalReaderCount(IMediaServerItem mediaServerItem, String app, String streamId) {
    public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) {
        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
        return mediaInfo.getInteger("totalReaderCount");
    }
@@ -237,7 +235,7 @@
    /**
     * 调用zlm RESTful API —— stopSendRtp
     */
    public Boolean stopSendRtpStream(IMediaServerItem mediaServerItem,Map<String, Object>param) {
    public Boolean stopSendRtpStream(MediaServerItem mediaServerItem, Map<String, Object>param) {
        Boolean result = false;
        JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
        if (jsonObject == null) {
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -4,22 +4,16 @@
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;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.print.attribute.standard.Media;
import java.util.*;
@Component
@@ -47,16 +41,26 @@
    @Override
    public void run(String... strings) throws Exception {
        IMediaServerItem presetMediaServer = mediaServerService.getOneByHostAndPort(
        // 清楚redis缓存的在线zlm信息
        mediaServerService.clearMediaServerForOnline();
        // 将配置文件的meida配置写入数据库
        MediaServerItem presetMediaServer = mediaServerService.getOneByHostAndPort(
                mediaConfig.getIp(), mediaConfig.getHttpPort());
        if (presetMediaServer  != null) {
            mediaConfig.setId(presetMediaServer.getId());
            mediaServerService.update(mediaConfig);
            MediaServerItem mediaSerItem = mediaConfig.getMediaSerItem();
            mediaSerItem.setId(presetMediaServer.getId());
            mediaServerService.update(mediaSerItem);
        }else {
            if (mediaConfig.getId() != null) {
                MediaServerItem mediaSerItem = mediaConfig.getMediaSerItem();
                mediaServerService.add(mediaSerItem);
            }
        }
        // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,
                (IMediaServerItem mediaServerItem, JSONObject response)->{
                (MediaServerItem mediaServerItem, JSONObject response)->{
            ZLMServerConfig zlmServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
            if (zlmServerConfig !=null ) {
                startGetMedia.remove(zlmServerConfig.getGeneralMediaServerId());
@@ -69,23 +73,25 @@
        logger.info("等待默认zlm接入...");
        // 获取所有的zlm, 并开启主动连接
        List<IMediaServerItem> all = mediaServerService.getAll();
        List<MediaServerItem> all = mediaServerService.getAll();
        if (presetMediaServer == null) {
            all.add(mediaConfig.getMediaSerItem());
        }
        for (IMediaServerItem mediaServerItem : all) {
        for (MediaServerItem mediaServerItem : all) {
            if (startGetMedia == null) startGetMedia = new HashMap<>();
            startGetMedia.put(mediaServerItem.getId(), true);
            new Thread(() -> {
                ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem);
                if (zlmServerConfig != null) {
                    zlmServerConfig.setIp(mediaServerItem.getIp());
                    zlmServerConfig.setHttpPort(mediaServerItem.getHttpPort());
                    startGetMedia.remove(mediaServerItem.getId());
                    mediaServerService.handLeZLMServerConfig(zlmServerConfig);
                }
            }).start();
        }
        Timer timer = new Timer();
        // 1分钟后未连接到则不再去主动连接
        // 2分钟后未连接到则不再去主动连接, TODO 并对重启前使用此在zlm的通道发送bye
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
@@ -100,7 +106,7 @@
        }, 60 * 1000 * 2);
    }
    public ZLMServerConfig getMediaServerConfig(IMediaServerItem mediaServerItem) {
    public ZLMServerConfig getMediaServerConfig(MediaServerItem mediaServerItem) {
        if ( startGetMedia.get(mediaServerItem.getId()) == null || !startGetMedia.get(mediaServerItem.getId())) return null;
        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
        ZLMServerConfig ZLMServerConfig = null;
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IMediaServerItem.java
File was deleted
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java
@@ -1,10 +1,13 @@
package com.genersoft.iot.vmp.media.zlm.dto;
import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import org.springframework.util.StringUtils;
public class MediaServerItem implements IMediaServerItem{
import java.util.HashMap;
public class MediaServerItem{
    private String id;
@@ -46,9 +49,18 @@
    private String updateTime;
    private boolean docker;
    private boolean defaultServer;
    private int count;
    private SsrcConfig ssrcConfig;
    private int currentPort;
    /**
     * 每一台ZLM都有一套独立的SSRC列表
     * 在ApplicationCheckRunner里对mediaServerSsrcMap进行初始化
     */
    private HashMap<String, SsrcConfig> mediaServerSsrcMap;
    public MediaServerItem() {
    }
@@ -218,14 +230,12 @@
        this.recordAssistPort = recordAssistPort;
    }
    @Override
    public boolean isDocker() {
        return docker;
    public boolean isDefaultServer() {
        return defaultServer;
    }
    @Override
    public void setDocker(boolean docker) {
        this.docker = docker;
    public void setDefaultServer(boolean defaultServer) {
        this.defaultServer = defaultServer;
    }
    public String getCreateTime() {
@@ -244,11 +254,29 @@
        this.updateTime = updateTime;
    }
    public int getCount() {
        return count;
    public HashMap<String, SsrcConfig> getMediaServerSsrcMap() {
        return mediaServerSsrcMap;
    }
    public void setCount(int count) {
        this.count = count;
    public void setMediaServerSsrcMap(HashMap<String, SsrcConfig> mediaServerSsrcMap) {
        this.mediaServerSsrcMap = mediaServerSsrcMap;
    }
    public SsrcConfig getSsrcConfig() {
        return ssrcConfig;
    }
    public void setSsrcConfig(SsrcConfig ssrcConfig) {
        this.ssrcConfig = ssrcConfig;
    }
    public int getCurrentPort() {
        return currentPort;
    }
    public void setCurrentPort(int currentPort) {
        this.currentPort = currentPort;
    }
}
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
@@ -3,8 +3,8 @@
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 com.genersoft.iot.vmp.service.bean.SSRCInfo;
import java.util.List;
@@ -13,11 +13,13 @@
 */
public interface IMediaServerService {
    List<IMediaServerItem> getAll();
    List<MediaServerItem> getAll();
    IMediaServerItem getOne(String generalMediaServerId);
    List<MediaServerItem> getAllOnline();
    IMediaServerItem getOneByHostAndPort(String host, int port);
    MediaServerItem getOne(String generalMediaServerId);
    MediaServerItem getOneByHostAndPort(String host, int port);
    /**
     * 新的节点加入
@@ -26,19 +28,27 @@
     */
    void handLeZLMServerConfig(ZLMServerConfig zlmServerConfig);
    void updateServerCatch(IMediaServerItem mediaServerItem, Integer count, Boolean b);
    MediaServerItem getMediaServerForMinimumLoad();
    IMediaServerItem getMediaServerForMinimumLoad();
    void setZLMConfig(MediaServerItem mediaServerItem);
    void setZLMConfig(IMediaServerItem mediaServerItem);
    void init();
    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId);
    void closeRTPServer(Device device, String channelId);
    void update(MediaConfig mediaConfig);
    void clearRTPServer(MediaServerItem mediaServerItem);
    void update(MediaServerItem mediaSerItem);
    void addCount(String mediaServerId);
    void removeCount(String mediaServerId);
    void releaseSsrc(MediaServerItem mediaServerItem, String ssrc);
    void clearMediaServerForOnline();
    void add(MediaServerItem mediaSerItem);
    void resetOnlineServerItem(MediaServerItem serverItem);
}
src/main/java/com/genersoft/iot/vmp/service/IMediaService.java
@@ -2,7 +2,6 @@
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;
/**
@@ -33,7 +32,7 @@
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaServerItem, String app, String stream, JSONArray tracks);
    StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaServerItem, String app, String stream, JSONArray tracks);
    /**
     * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
@@ -41,5 +40,5 @@
     * @param stream
     * @return
     */
    StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr);
    StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr);
}
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -4,7 +4,6 @@
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;
@@ -13,10 +12,10 @@
 */
public interface IPlayService {
    void onPublishHandlerForPlayBack(IMediaServerItem mediaServerItem,JSONObject resonse, String deviceId, String channelId, String uuid);
    void onPublishHandlerForPlay(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid);
    void onPublishHandlerForPlayBack(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid);
    void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid);
    PlayResult play(IMediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    IMediaServerItem getNewMediaServerItem(Device device);
    MediaServerItem getNewMediaServerItem(Device device);
}
src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java
@@ -1,7 +1,6 @@
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;
@@ -63,5 +62,5 @@
     * 获取ffmpeg.cmd模板
     * @return
     */
    JSONObject getFFmpegCMDs(IMediaServerItem mediaServerItem);
    JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem);
}
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java
@@ -1,7 +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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.github.pagehelper.PageInfo;
@@ -9,7 +9,7 @@
public interface IStreamPushService {
    List<StreamPushItem> handleJSON(String json, IMediaServerItem mediaServerItem);
    List<StreamPushItem> handleJSON(String json, MediaServerItem mediaServerItem);
    /**
     * 将应用名和流ID加入国标关联
src/main/java/com/genersoft/iot/vmp/service/bean/SSRCInfo.java
New file
@@ -0,0 +1,38 @@
package com.genersoft.iot.vmp.service.bean;
public class SSRCInfo {
    private int port;
    private String ssrc;
    private String StreamId;
    public SSRCInfo(int port, String ssrc, String streamId) {
        this.port = port;
        this.ssrc = ssrc;
        StreamId = streamId;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    public String getSsrc() {
        return ssrc;
    }
    public void setSsrc(String ssrc) {
        this.ssrc = ssrc;
    }
    public String getStreamId() {
        return StreamId;
    }
    public void setStreamId(String streamId) {
        StreamId = streamId;
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -1,29 +1,32 @@
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.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.ProxyServletConfig;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import org.mitre.dsmiley.httpproxy.ProxyServlet;
import com.genersoft.iot.vmp.utils.redis.JedisUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
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.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import java.sql.Array;
import java.text.SimpleDateFormat;
import java.util.*;
@@ -31,15 +34,13 @@
 * 媒体服务器节点管理
 */
@Service
public class MediaServerServiceImpl implements IMediaServerService {
@Order(value=2)
public class MediaServerServiceImpl implements IMediaServerService, CommandLineRunner {
    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;
    @Autowired
    private SipConfig sipConfig;
    @Value("${server.ssl.enabled:false}")
    private boolean sslEnabled;
@@ -56,7 +57,6 @@
    @Autowired
    private MediaServerMapper mediaServerMapper;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
@@ -66,53 +66,134 @@
    @Autowired
    private ZLMRTPServerFactory zlmrtpServerFactory;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    JedisUtil jedisUtil;
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 初始化
     */
    @Override
    public void init() {
        zlmServers.clear();
        zlmServerStatus.clear();
    public void run(String... args) throws Exception {
        logger.info("Media Server 缓存初始化");
        List<MediaServerItem> mediaServerItemList = mediaServerMapper.queryAll();
        for (IMediaServerItem mediaServerItem : mediaServerItemList) {
            zlmServers.put(mediaServerItem.getId(), mediaServerItem);
        for (MediaServerItem mediaServerItem : mediaServerItemList) {
            // 更新
            if (mediaServerItem.getSsrcConfig() == null) {
                SsrcConfig ssrcConfig = new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getSipDomain());
                mediaServerItem.setSsrcConfig(ssrcConfig);
                redisUtil.set(VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerItem.getId(), mediaServerItem);
            }
            // 查询redis是否存在此mediaServer
            String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerItem.getId();
            if (!redisUtil.hasKey(key)) {
                redisUtil.set(key, mediaServerItem);
            }
        }
    }
    @Override
    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId) {
        if (mediaServerItem == null || mediaServerItem.getId() == null) return null;
        // 获取mediaServer可用的ssrc
        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerItem.getId();
        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
        if (ssrcConfig == null) {
            logger.info("media server [ {} ] ssrcConfig is null", mediaServerItem.getId());
            return null;
        }else {
            String ssrc = ssrcConfig.getPlaySsrc();
            if (streamId == null) streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
            int rtpServerPort = mediaServerItem.getRtpProxyPort();
            if (mediaServerItem.isRtpEnable()) {
                rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId);
            }
            redisUtil.set(key, mediaServerItem);
            return new SSRCInfo(rtpServerPort, ssrc, streamId);
        }
    }
    @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 mediaServerId = streamSession.getMediaServerId(device.getDeviceId(), channelId);
        MediaServerItem mediaServerItem = this.getOne(mediaServerId);
        if (mediaServerItem != null) {
            String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId);
            releaseSsrc(mediaServerItem, streamSession.getSSRC(device.getDeviceId(), channelId));
        }
        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) {
    public void releaseSsrc(MediaServerItem mediaServerItem, String ssrc) {
        if (mediaServerItem == null || ssrc == null) return;
        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
        ssrcConfig.releaseSsrc(ssrc);
        mediaServerItem.setSsrcConfig(ssrcConfig);
        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerItem.getId();
        redisUtil.set(key, mediaServerItem);
    }
    /**
     * zlm 重启后重置他的推流信息, TODO 给正在使用的设备发送停止命令
     * @param mediaServerItem
     */
    @Override
    public void clearRTPServer(MediaServerItem mediaServerItem) {
        mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getSipDomain()));
        redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX, mediaServerItem.getId(), 0);
    }
    @Override
    public void update(MediaServerItem mediaSerItem) {
        mediaServerMapper.update(mediaSerItem);
        MediaServerItem mediaServerItemInRedis = getOne(mediaSerItem.getId());
        MediaServerItem mediaServerItemInDataBase = mediaServerMapper.queryOne(mediaSerItem.getId());
        if (mediaServerItemInRedis != null && mediaServerItemInRedis.getSsrcConfig() != null) {
            mediaServerItemInDataBase.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig());
        }else {
            mediaServerItemInDataBase.setSsrcConfig(
                    new SsrcConfig(
                            mediaServerItemInDataBase.getId(),
                            null,
                            sipConfig.getSipDomain()
                    )
            );
        }
        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerItemInDataBase.getId();
        redisUtil.set(key, mediaServerItemInDataBase);
    }
    @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);
    public List<MediaServerItem> getAll() {
        List<MediaServerItem> result = new ArrayList<>();
        List<Object> mediaServerKeys = redisUtil.scan(String.format("%S*", VideoManagerConstants.MEDIA_SERVER_PREFIX));
        for (int i = 0; i < mediaServerKeys.size(); i++) {
            String key = (String) mediaServerKeys.get(i);
            result.add((MediaServerItem)redisUtil.get(key));
        }
        return result;
    }
//        return mediaServerMapper.queryAll();
    @Override
    public List<MediaServerItem> getAllOnline() {
        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;
        Set<String> mediaServerIdSet = redisUtil.zRevRange(key, 0, -1);
        List<MediaServerItem> result = new ArrayList<>();
        if (mediaServerIdSet != null && mediaServerIdSet.size() > 0) {
            for (String mediaServerId : mediaServerIdSet) {
                String serverKey = VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerId;
                result.add((MediaServerItem) redisUtil.get(serverKey));
            }
        }
        return result;
    }
    /**
@@ -121,24 +202,26 @@
     * @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;
        }
    public MediaServerItem getOne(String mediaServerId) {
        if (mediaServerId == null) return null;
        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + mediaServerId;
        return (MediaServerItem)redisUtil.get(key);
    }
    @Override
    public IMediaServerItem getOneByHostAndPort(String host, int port) {
    public MediaServerItem getOneByHostAndPort(String host, int port) {
        return mediaServerMapper.queryOneByHostAndPort(host, port);
    }
    @Override
    public void clearMediaServerForOnline() {
        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;
        redisUtil.del(key);
    }
    @Override
    public void add(MediaServerItem mediaSerItem) {
        mediaServerMapper.add(mediaSerItem);
    }
    /**
@@ -150,111 +233,100 @@
        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());
        MediaServerItem serverItem = mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId());
        if (serverItem == null) {
            serverItem = mediaServerMapper.queryOneByHostAndPort(zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
        }
        if (zlmServerConfig.getGeneralMediaServerId().equals(mediaConfig.getId())
                || (zlmServerConfig.getIp().equals(mediaConfig.getIp()) && zlmServerConfig.getHttpPort() == mediaConfig.getHttpPort())) {
            // 配置文件的zlm
            // 如果是配置文件中的zlm。 也就是默认zlm。 一切以配置文件内容为准
            // docker部署不会使用zlm配置的端口号;
            // 直接编译部署的使用配置文件的端口号,如果zlm修改配改了配置,wvp自动修改
            // docker部署不会使用zlm配置的端口号不是默认的则不做更新, 配置修改需要自行修改server配置;
            MediaServerItem serverItemFromConfig = mediaConfig.getMediaSerItem();
            serverItemFromConfig.setId(zlmServerConfig.getGeneralMediaServerId());
            if (mediaConfig.getHttpPort() == 0) serverItemFromConfig.setHttpPort(zlmServerConfig.getHttpPort());
            if (mediaConfig.getHttpSSlPort() == 0) serverItemFromConfig.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
            if (mediaConfig.getRtmpPort() == 0) serverItemFromConfig.setRtmpPort(zlmServerConfig.getRtmpPort());
            if (mediaConfig.getRtmpSSlPort() == 0) serverItemFromConfig.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
            if (mediaConfig.getRtspPort() == 0) serverItemFromConfig.setRtspPort(zlmServerConfig.getRtspPort());
            if (mediaConfig.getRtspSSLPort() == 0) serverItemFromConfig.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
            if (mediaConfig.getRtpProxyPort() == 0) serverItemFromConfig.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
            if (serverItem != null){
                // 可能是同一个zlm但id发生了变化
                if (!serverItem.getId().equals(zlmServerConfig.getGeneralMediaServerId())) {
                    mediaServerMapper.delOne(serverItem.getId());
                    redisUtil.del(VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItem.getId());
            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());
                    String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItemFromConfig.getId();
                    serverItemFromConfig.setSsrcConfig(new SsrcConfig(serverItemFromConfig.getId(), null, sipConfig.getSipDomain()));
                    redisUtil.set(key, serverItemFromConfig);
                    mediaServerMapper.add(serverItemFromConfig);
                }else {
                    mediaServerMapper.update(serverItemFromConfig);
                }
                serverItem.setUpdateTime(now);
                mediaServerMapper.update(serverItem);
            }else {
                String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItemFromConfig.getId();
                serverItemFromConfig.setSsrcConfig(new SsrcConfig(serverItemFromConfig.getId(), null, sipConfig.getSipDomain()));
                redisUtil.set(key, serverItemFromConfig);
                mediaServerMapper.add(serverItemFromConfig);
            }
            resetOnlineServerItem(serverItemFromConfig);
            setZLMConfig(serverItemFromConfig);
        }else {
            String now = this.format.format(new Date(System.currentTimeMillis()));
            if (serverItem == null){
                    // 一个新的zlm接入wvp
                    serverItem = new MediaServerItem(zlmServerConfig, sipConfig.getSipIp());
                    serverItem.setCreateTime(now);
                    serverItem.setUpdateTime(now);
                String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItem.getId();
                serverItem.setSsrcConfig(new SsrcConfig(serverItem.getId(), null, sipConfig.getSipDomain()));
                redisUtil.set(key, serverItem);
                // 存入数据库
                mediaServerMapper.add(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.getMediaSerItem();
                mediaServerMapper.add(mediaConfig);
            }else {
                // 一个新的zlm接入wvp
                serverItem = new MediaServerItem(zlmServerConfig, sipIp);
                serverItem.setCreateTime(now);
                serverItem.setUpdateTime(now);
                mediaServerMapper.add(serverItem);
            }
            resetOnlineServerItem(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);
            }
    public void resetOnlineServerItem(MediaServerItem serverItem) {
        // 更新缓存
        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;
        // 使用zset的分数作为当前并发量, 默认值设置为0
        if (redisUtil.zScore(key, serverItem.getId()) == null) {  // 不存在则设置默认值 已存在则重置
            redisUtil.zAdd(key, serverItem.getId(), 0L);
            // 查询服务流数量
            zlmresTfulUtils.getMediaList(serverItem, null, null, "rtmp",(mediaList ->{
                Integer code = mediaList.getInteger("code");
                if (code == 0) {
                    JSONArray data = mediaList.getJSONArray("data");
                    if (data != null) {
                        redisUtil.zAdd(key, serverItem.getId(), data.size());
                    }
                }
            }));
        }else {
            clearRTPServer(serverItem);
        }
    }
    @Override
    public void addCount(String mediaServerId) {
        if (zlmServerStatus.get(mediaServerId) != null) {
            zlmServerStatus.put(mediaServerId, zlmServerStatus.get(mediaServerId) + 1);
        }
        if (mediaServerId == null) return;
        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;
        Double aDouble = redisUtil.zScore(key, mediaServerId);
        redisUtil.zIncrScore(key, mediaServerId, 1);
    }
    @Override
    public void removeCount(String mediaServerId) {
        if (zlmServerStatus.get(mediaServerId) != null) {
            zlmServerStatus.put(mediaServerId, zlmServerStatus.get(mediaServerId) - 1);
        }
        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;
        redisUtil.zIncrScore(key, mediaServerId, - 1);
    }
    /**
@@ -262,35 +334,18 @@
     * @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;
                }
            }
    public MediaServerItem getMediaServerForMinimumLoad() {
        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;
        if (redisUtil.zSize(key)  == null || redisUtil.zSize(key) == 0) {
            logger.info("获取负载最低的节点时无在线节点");
        }
        if (key == null) {
            logger.info("获取负载最低的节点时无在线节点");
            return null;
        }else{
            return  zlmServers.get(key);
        }
        // 获取分数最低的,及并发最低的
        Set<Object> objects = redisUtil.ZRange(key, 0, -1);
        ArrayList<Object> MediaServerObjectS = new ArrayList<>(objects);
        String mediaServerId = (String)MediaServerObjectS.get(0);
        return getOne(mediaServerId);
    }
    /**
@@ -298,7 +353,7 @@
     * @param mediaServerItem 服务ID
     */
    @Override
    public void setZLMConfig(IMediaServerItem mediaServerItem) {
    public void setZLMConfig(MediaServerItem mediaServerItem) {
        logger.info("[ {} ]-[ {}:{} ]设置zlm",
                mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        String protocol = sslEnabled ? "https" : "http";
@@ -333,8 +388,10 @@
            logger.info("[ {} ]-[ {}:{} ]设置zlm成功",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        }else {
            logger.info("[ {} ]-[ {}:{} ]设置zlm失败" + responseJSON.getString("msg"),
            logger.info("[ {} ]-[ {}:{} ]设置zlm失败",
                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
        }
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
@@ -4,9 +4,7 @@
import com.alibaba.fastjson.JSONArray;
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.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;
@@ -33,14 +31,14 @@
    @Override
    public StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks) {
    public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks) {
        return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null);
    }
    @Override
    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) {
        StreamInfo streamInfo = null;
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (mediaInfo == null) {
            return streamInfo;
        }
@@ -63,7 +61,7 @@
    }
    @Override
    public StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr) {
    public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr) {
        StreamInfo streamInfoResult = new StreamInfo();
        streamInfoResult.setStreamId(stream);
        streamInfoResult.setApp(app);
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -14,11 +14,12 @@
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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
import com.genersoft.iot.vmp.service.IMediaService;
@@ -54,6 +55,9 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private RedisUtil redis;
    @Autowired
    private DeferredResultHolder resultHolder;
    @Autowired
@@ -73,7 +77,7 @@
    @Override
    public PlayResult play(IMediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
    public PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
        PlayResult playResult = new PlayResult();
        if (mediaServerItem == null) {
            RequestMessage msg = new RequestMessage();
@@ -97,14 +101,21 @@
        // 超时处理
        result.onTimeout(()->{
            logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            // 释放rtpserver
            mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
            WVPResult wvpResult = new WVPResult();
            wvpResult.setCode(-1);
            wvpResult.setMsg("Timeout");
            SIPDialog dialog = streamSession.getDialog(deviceId, channelId);
            if (dialog != null) {
                wvpResult.setMsg("收流超时,请稍候重试");
            }else {
                wvpResult.setMsg("点播超时,请稍候重试");
            }
            msg.setData(wvpResult);
            // 点播超时回复BYE
            cmder.streamByeCmd(device.getDeviceId(), channelId);
            // 释放rtpserver
            mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
            resultHolder.invokeResult(msg);
        });
        result.onCompletion(()->{
@@ -131,7 +142,7 @@
                    WVPResult wvpResult = (WVPResult)responseEntity.getBody();
                    if (wvpResult.getCode() == 0) {
                        StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
                        IMediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
                        MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
                        String streamUrl = streamInfoForSuccess.getFmp4();
                        // 请求截图
                        zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
@@ -142,14 +153,23 @@
            }
        });
        if (streamInfo == null) {
            SSRCInfo ssrcInfo;
            String streamId = null;
            if (mediaServerItem.isRtpEnable()) {
                streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            }
            ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId);
            // 发送点播消息
            cmder.playStreamCmd(mediaServerItem, device, channelId, (IMediaServerItem mediaServerItemInUse, JSONObject response) -> {
            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
                logger.info("收到订阅消息: " + response.toJSONString());
                onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid.toString());
                if (hookEvent != null) {
                    hookEvent.response(mediaServerItem, response);
                }
            }, (event) -> {
                // 点播返回sip错误
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                Response response = event.getResponse();
@@ -162,6 +182,7 @@
                if (errorEvent != null) {
                    errorEvent.response(event);
                }
            });
        } else {
            String streamId = streamInfo.getStreamId();
@@ -176,7 +197,7 @@
                return playResult;
            }
            String mediaServerId = streamInfo.getMediaServerId();
            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
            if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
@@ -194,9 +215,17 @@
                    hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
                }
            } else {
                // TODO 点播前是否重置状态
                redisCatchStorage.stopPlay(streamInfo);
                storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
                cmder.playStreamCmd(mediaServerItem, device, channelId, (IMediaServerItem mediaServerItemInuse, JSONObject response) -> {
                SSRCInfo ssrcInfo;
                String streamId2 = null;
                if (mediaServerItem.isRtpEnable()) {
                    streamId2 = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
                }
                ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId2);
                cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
                    logger.info("收到订阅消息: " + response.toJSONString());
                    onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString());
                }, (event) -> {
@@ -218,7 +247,7 @@
    }
    @Override
    public void onPublishHandlerForPlay(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
    public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
        RequestMessage msg = new RequestMessage();
        msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
@@ -228,14 +257,6 @@
                deviceChannel.setStreamId(streamInfo.getStreamId());
                storager.startPlay(deviceId, channelId, streamInfo.getStreamId());
            }
            ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId);
            SIPDialog dialog = (SIPDialog)transaction.getDialog();
            StreamInfo.TransactionInfo transactionInfo = new StreamInfo.TransactionInfo();
            transactionInfo.callId = dialog.getCallId().getCallId();
            transactionInfo.localTag = dialog.getLocalTag();
            transactionInfo.remoteTag = dialog.getRemoteTag();
            transactionInfo.branch = dialog.getFirstTransactionInt().getBranchId();
            streamInfo.setTransactionInfo(transactionInfo);
            redisCatchStorage.startPlay(streamInfo);
            msg.setData(JSON.toJSONString(streamInfo));
@@ -254,10 +275,10 @@
    }
    @Override
    public IMediaServerItem getNewMediaServerItem(Device device) {
    public MediaServerItem getNewMediaServerItem(Device device) {
        if (device == null) return null;
        String mediaServerId = device.getMediaServerId();
        IMediaServerItem mediaServerItem = null;
        MediaServerItem mediaServerItem = null;
        if (mediaServerId == null) {
            mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
        }else {
@@ -270,7 +291,7 @@
    }
    @Override
    public void onPublishHandlerForPlayBack(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
    public void onPublishHandlerForPlayBack(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
        RequestMessage msg = new RequestMessage();
        msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
@@ -285,7 +306,7 @@
        }
    }
    public StreamInfo onPublishHandler(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
    public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
        String streamId = resonse.getString("stream");
        JSONArray tracks = resonse.getJSONArray("tracks");
        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem,"rtp", streamId, tracks);
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
@@ -3,7 +3,6 @@
import com.alibaba.fastjson.JSONObject;
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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IGbStreamService;
@@ -58,7 +57,7 @@
    @Override
    public String save(StreamProxyItem param) {
        IMediaServerItem mediaInfo;
        MediaServerItem mediaInfo;
        if ("auto".equals(param.getMediaServerId())){
            mediaInfo = mediaServerService.getMediaServerForMinimumLoad();
        }else {
@@ -120,7 +119,7 @@
    @Override
    public JSONObject addStreamProxyToZlm(StreamProxyItem param) {
        JSONObject result = null;
        IMediaServerItem mediaServerItem = null;
        MediaServerItem mediaServerItem = null;
        if (param.getMediaServerId() == null) {
            logger.warn("添加代理时MediaServerId 为null");
            return null;
@@ -141,7 +140,7 @@
    @Override
    public JSONObject removeStreamProxyFromZlm(StreamProxyItem param) {
        if (param ==null) return null;
        IMediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
        MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
        JSONObject result = zlmresTfulUtils.closeStreams(mediaServerItem, param.getApp(), param.getStream());
        return result;
    }
@@ -198,7 +197,7 @@
    }
    @Override
    public JSONObject getFFmpegCMDs(IMediaServerItem mediaServerItem) {
    public JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem) {
        JSONObject result = new JSONObject();
        JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
        if (mediaServerConfigResuly != null && mediaServerConfigResuly.getInteger("code") == 0
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
@@ -5,7 +5,6 @@
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;
@@ -43,7 +42,7 @@
    private IMediaServerService mediaServerService;
    @Override
    public List<StreamPushItem> handleJSON(String jsonData, IMediaServerItem mediaServerItem) {
    public List<StreamPushItem> handleJSON(String jsonData, MediaServerItem mediaServerItem) {
        if (jsonData == null) return null;
        Map<String, StreamPushItem> result = new HashMap<>();
@@ -98,7 +97,7 @@
    @Override
    public boolean removeFromGB(GbStream stream) {
        int del = gbStreamMapper.del(stream.getApp(), stream.getStream());
        IMediaServerItem mediaInfo = mediaServerService.getOne(stream.getMediaServerId());
        MediaServerItem 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
@@ -5,8 +5,6 @@
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;
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -3,7 +3,6 @@
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;
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java
@@ -1,7 +1,5 @@
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;
@@ -35,6 +33,7 @@
            "rtpEnable, " +
            "rtpPortRange, " +
            "recordAssistPort, " +
            "defaultServer, " +
            "createTime, " +
            "updateTime" +
            ") VALUES " +
@@ -57,9 +56,10 @@
            "${rtpEnable}, " +
            "'${rtpPortRange}', " +
            "${recordAssistPort}, " +
            "${defaultServer}, " +
            "'${createTime}', " +
            "'${updateTime}')")
    int add(IMediaServerItem mediaServerItem);
    int add(MediaServerItem mediaServerItem);
    @Update(value = {" <script>" +
            "UPDATE media_server " +
@@ -83,7 +83,7 @@
            "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
            "WHERE id='${id}'"+
            " </script>"})
    int update(IMediaServerItem mediaServerItem);
    int update(MediaServerItem mediaServerItem);
    @Select("SELECT * FROM media_server WHERE id='${id}'")
    MediaServerItem queryOne(String id);
@@ -92,7 +92,7 @@
    List<MediaServerItem> queryAll();
    @Select("DELETE FROM media_server WHERE id='${id}'")
    int delOne(String secret);
    void delOne(String id);
    @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/impl/RedisCatchStorageImpl.java
@@ -3,10 +3,7 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
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;
src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java
New file
@@ -0,0 +1,8 @@
package com.genersoft.iot.vmp.utils;
public class ConfigConst {
    /**
     * 播流最大并发个数
     */
    public static final Integer MAX_STRTEAM_COUNT = 10000;
}
src/main/java/com/genersoft/iot/vmp/utils/SerializeUtils.java
New file
@@ -0,0 +1,31 @@
package com.genersoft.iot.vmp.utils;
import java.io.*;
public class SerializeUtils {
    public static byte[] serialize(Object obj){
        byte[] bytes = null;
        try {
            ByteArrayOutputStream baos=new ByteArrayOutputStream();;
            ObjectOutputStream oos=new ObjectOutputStream(baos);
            oos.writeObject(obj);
            bytes=baos.toByteArray();
            baos.close();
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
    public static Object deSerialize(byte[] bytes){
        Object obj=null;
        try {
            ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
            ObjectInputStream ois=new ObjectInputStream(bais);
            obj=ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}
src/main/java/com/genersoft/iot/vmp/utils/redis/JedisUtil.java
New file
@@ -0,0 +1,97 @@
package com.genersoft.iot.vmp.utils.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Set;
/**
 * @Description:Jedis工具类
 * @author: wangshaopeng@sunnybs.com
 * @date: 2021年03月22日 下午8:27:29
 */
@Component
public class JedisUtil {
    @Autowired
    private JedisPool jedisPool;
    //    ============================== Key ==============================
    /**
     * 检查给定 key 是否存在。
     *
     * @param key
     * @return
     */
    public Boolean exists(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Boolean exists = jedis.exists(key);
            return exists;
        } finally {
            returnToPool(jedis);
        }
    }
    //    ============================== Set ==============================
    /**
     * SADD key member [member ...]
     * 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
     * 假如 key 不存在,则创建一个只包含 member 元素作成员的集合。
     * 当 key 不是集合类型时,返回一个错误。
     */
    public Long sadd(String key, String... members) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long smove = jedis.sadd(key, members);
            return smove;
        } finally {
            returnToPool(jedis);
        }
    }
    /**
     * SMEMBERS key
     * 返回集合 key 中的所有成员。
     * 不存在的 key 被视为空集合。
     */
    public Set<String> smembers(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Set<String> smembers = jedis.smembers(key);
            return smembers;
        } finally {
            returnToPool(jedis);
        }
    }
    /**
     * SREM key member1 [member2]
     * 移除集合中一个或多个成员
     */
    public Long srem(String key, String... member) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long srem = jedis.srem(key, member);
            return srem;
        } finally {
            returnToPool(jedis);
        }
    }
    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
@@ -415,10 +415,10 @@
     *
     * @param key
     * @param value
     * @param score
     * @param delta -1 表示减 1 表示加1
     */
    public Double zIncrScore(Object key, Object value, double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    public Double zIncrScore(Object key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }
    /**
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
@@ -2,13 +2,12 @@
import com.alibaba.fastjson.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
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;
@@ -37,7 +36,7 @@
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -89,7 +88,7 @@
        // 获取可用的zlm
        Device device = storager.queryVideoDevice(deviceId);
        IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        PlayResult playResult = playService.play(newMediaServerItem, deviceId, channelId, null, null);
        return playResult.getResult();
@@ -174,7 +173,7 @@
            logger.warn("视频转码API调用失败!, 视频流已经停止!");
            return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
        }
        IMediaServerItem mediaInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
        MediaServerItem mediaInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
        if (!rtpInfo.getBoolean("exist")) {
            logger.warn("视频转码API调用失败!, 视频流已停止推流!");
@@ -219,7 +218,7 @@
            result.put("msg", "mediaServerId is null");
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.BAD_REQUEST);
        }
        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (mediaInfo == null) {
            result.put("code", 0);
            result.put("msg", "使用的流媒体已经停止运行");
@@ -307,16 +306,16 @@
            logger.debug("获取所有的ssrc");
        }
        JSONArray objects = new JSONArray();
        for(Map.Entry<String, String> entry: streamSession.getSsrcMap().entrySet()) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        List<SsrcTransaction> allSsrc = streamSession.getAllSsrc();
        for (SsrcTransaction transaction : allSsrc) {
            JSONObject jsonObject = new JSONObject();
            String[] keyArray = entry.getKey().split("_");
            jsonObject.put("deviceId", keyArray[0]);
            jsonObject.put("channelId", keyArray[1]);
            jsonObject.put("ssrc", entry.getValue());
            jsonObject.put("streamId", streamSession.getStreamIdMap().get(entry.getKey()));
            jsonObject.put("deviceId", transaction.getDeviceId());
            jsonObject.put("channelId", transaction.getChannelId());
            jsonObject.put("ssrc", transaction.getSsrc());
            jsonObject.put("streamId", transaction.getStreamId());
            objects.add(jsonObject);
        }
        WVPResult<JSONObject> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java
@@ -3,9 +3,9 @@
import com.genersoft.iot.vmp.common.StreamInfo;
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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IPlayService;
import io.swagger.annotations.Api;
@@ -58,6 +58,9 @@
    @Autowired
    private DeferredResultHolder resultHolder;
    @Autowired
    private IMediaServerService mediaServerService;
    @ApiOperation("开始历史媒体下载")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@@ -90,7 +93,7 @@
            cmder.streamByeCmd(deviceId, channelId);
        }
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
        IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        if (newMediaServerItem == null) {
            logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            RequestMessage msg = new RequestMessage();
@@ -99,7 +102,10 @@
            resultHolder.invokeResult(msg);
            return result;
        }
        cmder.downloadStreamCmd(newMediaServerItem, device, channelId, startTime, endTime, downloadSpeed, (IMediaServerItem mediaServerItem, JSONObject response) -> {
        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null);
        cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (MediaServerItem mediaServerItem, JSONObject response) -> {
            logger.info("收到订阅消息: " + response.toJSONString());
            playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString());
        }, event -> {
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
@@ -4,8 +4,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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IPlayService;
import io.swagger.annotations.Api;
@@ -58,6 +59,9 @@
    @Autowired
    private DeferredResultHolder resultHolder;
    @Autowired
    private IMediaServerService mediaServerService;
    @ApiOperation("开始视频回放")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@@ -74,6 +78,15 @@
        }
        UUID uuid = UUID.randomUUID();
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
        Device device = storager.queryVideoDevice(deviceId);
        if (device == null) {
            result.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
            return result;
        }
        MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null);
        // 超时处理
        result.onTimeout(()->{
            logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
@@ -82,14 +95,14 @@
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
        });
        Device device = storager.queryVideoDevice(deviceId);
        StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId);
        if (streamInfo != null) {
            // 停止之前的回放
            cmder.streamByeCmd(deviceId, channelId);
        }
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
        IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        if (newMediaServerItem == null) {
            logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            RequestMessage msg = new RequestMessage();
@@ -98,7 +111,8 @@
            resultHolder.invokeResult(msg);
            return result;
        }
        cmder.playbackStreamCmd(newMediaServerItem, device, channelId, startTime, endTime, (IMediaServerItem mediaServerItem, JSONObject response) -> {
        cmder.playbackStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, (MediaServerItem mediaServerItem, JSONObject response) -> {
            logger.info("收到订阅消息: " + response.toJSONString());
            playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString());
        }, event -> {
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/session/PlayTypeEnum.java
New file
@@ -0,0 +1,23 @@
package com.genersoft.iot.vmp.vmanager.gb28181.session;
public enum PlayTypeEnum {
    PLAY("0", "直播"),
    PLAY_BACK("1", "回放");
    private String value;
    private String name;
    PlayTypeEnum(String value, String name) {
        this.value = value;
        this.name = name;
    }
    public String getValue() {
        return value;
    }
    public String getName() {
        return name;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
@@ -1,10 +1,8 @@
package com.genersoft.iot.vmp.vmanager.server;
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.media.zlm.dto.MediaServerItem;
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;
@@ -17,7 +15,6 @@
import javax.sip.ListeningPoint;
import javax.sip.ObjectInUseException;
import javax.sip.SipProvider;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -38,19 +35,30 @@
    @ApiOperation("流媒体服务列表")
    @GetMapping(value = "/media_server/list")
    @ResponseBody
    public WVPResult<List<IMediaServerItem>> getMediaServerList(){
        WVPResult<List<IMediaServerItem>> result = new WVPResult<>();
    public WVPResult<List<MediaServerItem>> getMediaServerList(){
        WVPResult<List<MediaServerItem>> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
        result.setData(mediaServerService.getAll());
        return result;
    }
    @ApiOperation("在线流媒体服务列表")
    @GetMapping(value = "/media_server/online/list")
    @ResponseBody
    public WVPResult<List<MediaServerItem>> getOnlineMediaServerList(){
        WVPResult<List<MediaServerItem>> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
        result.setData(mediaServerService.getAllOnline());
        return result;
    }
    @ApiOperation("获取流媒体服务")
    @GetMapping(value = "/media_server/one/{id}")
    @ResponseBody
    public WVPResult<IMediaServerItem> getMediaServer(@PathVariable String id){
        WVPResult<IMediaServerItem> result = new WVPResult<>();
    public WVPResult<MediaServerItem> getMediaServer(@PathVariable String id){
        WVPResult<MediaServerItem> result = new WVPResult<>();
        result.setCode(0);
        result.setMsg("success");
        result.setData(mediaServerService.getOne(id));
src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java
@@ -1,7 +1,6 @@
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;
@@ -9,7 +8,6 @@
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;
@@ -86,7 +84,7 @@
    public WVPResult getFFmpegCMDs(@RequestParam String mediaServerId){
        logger.debug("获取节点[ {} ]ffmpeg.cmd模板", mediaServerId );
        IMediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
        MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
        JSONObject data = streamProxyService.getFFmpegCMDs(mediaServerItem);
        WVPResult<JSONObject> result = new WVPResult<>();
        result.setCode(0);
src/main/resources/all-application.yml
@@ -17,6 +17,12 @@
        password:
        # [可选] 超时时间
        timeout: 10000
        # [可选] 一个pool最多可分配多少个jedis实例
        poolMaxTotal: 1000
        # [可选] 一个pool最多有多少个状态为idle(空闲)的jedis实例
        poolMaxIdle: 500
        # [可选] 最大的等待时间(秒)
        poolMaxWait: 5
    # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置
    datasource:
        # 使用mysql 打开23-28行注释, 删除29-36行
@@ -124,6 +130,7 @@
    level:
        com.genersoft.iot: debug
        com.genersoft.iot.vmp.storager.dao: info
        com.genersoft.iot.vmp.gb28181: info
# [根据业务需求配置]
user-settings:
    # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true
src/main/resources/application-dev.yml
@@ -76,6 +76,8 @@
    level:
        com.genersoft.iot: debug
        com.genersoft.iot.vmp.storager.dao: info
        com.genersoft.iot.vmp.gb28181: info
# [根据业务需求配置]
user-settings:
    # 推流直播是否录制
src/main/resources/wvp.sqlite
Binary files differ
web_src/src/components/service/MediaServer.js
@@ -9,7 +9,7 @@
  getMediaServerList(callback){
    this.$axios({
      method: 'get',
      url:`/api/server/media_server/list`,
      url:`/api/server/media_server/online/list`,
    }).then(function (res) {
      if (typeof (callback) == "function") callback(res.data)
    }).catch(function (error) {