panlinlin
2021-01-05 34135cce5d59f6ad7653737dd035bb1d441e185f
Merge remote-tracking branch 'origin/master' into wvp-28181-2.0

# Conflicts:
# README.md
# src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
# src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
# src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
# src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
# src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
# src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
# src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
# src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
# src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
# src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
# src/main/resources/application-dev.yml
# web_src/src/components/gb28181/devicePlayer.vue
33个文件已修改
9个文件已添加
8个文件已删除
4025 ■■■■■ 已修改文件
pom.xml 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/PageResult.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 251 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/VodeoMannagerTask.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java 600 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/Device.java 401 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/DeviceChannel.java 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java 152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/wvp.sqlite 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/channelList.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/gb28181/devicePlayer.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/videoList.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -13,13 +13,38 @@
    <artifactId>wvp</artifactId>
    <name>web video platform</name>
    <repositories>
        <repository>
            <id>nexus-aliyun</id>
            <name>Nexus aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>nexus-aliyun</id>
            <name>Nexus aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 依赖版本 -->
        <mapper.version>4.1.5</mapper.version>
        <mybatis.version>3.5.5</mybatis.version>
        <mybatis.spring.version>2.0.5</mybatis.spring.version>
        <pagehelper.version>5.2.0</pagehelper.version>
        <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
        <asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
@@ -31,30 +56,16 @@
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!-- druid数据库连接池 -->
@@ -71,36 +82,25 @@
            <version>8.0.22</version>
        </dependency>
        <!--Mybatis -->
        <!-- 添加sqlite-jdbc数据库驱动 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.version}</version>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.32.3.2</version>
        </dependency>
        <!--分页插件 -->
        <!--Mybatis分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>${pagehelper.version}</version>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.10</version>
        </dependency>
        <!--通用Mapper -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${mapper.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.apache.commons</groupId>-->
<!--            <artifactId>commons-lang3</artifactId>-->
<!--            <version>3.11</version>-->
<!--        </dependency>-->
        <!--Swagger2 -->
        <!--在线文档 -->
src/main/java/com/genersoft/iot/vmp/common/PageResult.java
File was deleted
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
@@ -4,7 +4,6 @@
public class StreamInfo {
    private String ssrc;
    private String streamId;
    private String deviceID;
    private String cahnnelId;
@@ -19,14 +18,6 @@
    private String rtmp;
    private String rtsp;
    private JSONArray tracks;
    public String getSsrc() {
        return ssrc;
    }
    public void setSsrc(String ssrc) {
        this.ssrc = ssrc;
    }
    public String getDeviceID() {
        return deviceID;
src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java
New file
@@ -0,0 +1,63 @@
package com.genersoft.iot.vmp.conf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 *  对配置文件进行校验
 */
@Component
@Order(value=2)
public class ApplicationCheckRunner implements CommandLineRunner {
    private Logger logger = LoggerFactory.getLogger("ApplicationCheckRunner");
    @Value("${sip.ip}")
    private String sipIp;
    @Value("${media.ip}")
    private String mediaIp;
    @Value("${media.wanIp}")
    private String mediaWanIp;
    @Value("${media.hookIp}")
    private String mediaHookIp;
    @Value("${media.port}")
    private int mediaPort;
    @Value("${media.secret}")
    private String mediaSecret;
    @Value("${media.streamNoneReaderDelayMS}")
    private String streamNoneReaderDelayMS;
    @Value("${sip.ip}")
    private String sipIP;
    @Value("${server.port}")
    private String serverPort;
    @Value("${media.autoConfig}")
    private boolean autoConfig;
    @Override
    public void run(String... args) throws Exception {
        if (sipIP.equals("localhost") || sipIP.equals("127.0.0.1")) {
            logger.error("sip.ip不能使用 {} ,请使用类似192.168.1.44这样的来自网卡的IP!!!", sipIP );
            System.exit(1);
        }
        if (mediaIp.equals("localhost") || mediaIp.equals("127.0.0.1")) {
            logger.warn("mediaIp.ip使用 {} ,将无法收到网络内其他设备的推流!!!", mediaIp );
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -8,8 +8,10 @@
import java.util.concurrent.TimeUnit;
import javax.sip.*;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +35,9 @@
    @Autowired
    private SIPProcessorFactory processorFactory;
    @Autowired
    private SipSubscribe sipSubscribe;
    private SipStack sipStack;
@@ -133,17 +138,34 @@
                // 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) {
                    SipSubscribe.Event subscribe = sipSubscribe.getOkSubscribe(callIdHeader.getCallId());
                    if (subscribe != null) {
                        subscribe.response(evt);
                    }
                }
            }
        // } else if (status == Response.TRYING) {
            // trying不会回复
        } else if ((status >= 100) && (status < 200)) {
            // 增加其它无需回复的响应,如101、180等
        } else {
            logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()/* .getContent().toString()*/);
            if (evt.getResponse() != null && sipSubscribe.getErrorSubscribesSize() > 0 ) {
                CallIdHeader callIdHeader = (CallIdHeader)evt.getResponse().getHeader(CallIdHeader.NAME);
                if (callIdHeader != null) {
                    SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
                    if (subscribe != null) {
                        subscribe.response(evt);
                    }
                }
            }
        }
        // trying不会回复
        // if (status == Response.TRYING) {
        // }
    }
    /**
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
@@ -27,6 +27,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Random;
@@ -103,9 +104,12 @@
                    .createWWWAuthenticateHeader(DEFAULT_SCHEME);
            proxyAuthenticate.setParameter("realm", realm);
            proxyAuthenticate.setParameter("nonce", generateNonce());
            proxyAuthenticate.setParameter("opaque", "");
            proxyAuthenticate.setParameter("stale", "FALSE");
            proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM);
//            proxyAuthenticate.setParameter("qop", "auth");
            response.setHeader(proxyAuthenticate);
        } catch (Exception ex) {
            InternalErrorHandler.handleException(ex);
@@ -170,42 +174,116 @@
    public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
        AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
        if ( authHeader == null ) return false;
        String realm = authHeader.getRealm();
        String username = authHeader.getUsername();
        String realm = authHeader.getRealm().trim();
        String username = authHeader.getUsername().trim();
        if ( username == null || realm == null ) {
            return false;
        }
        String nonce = authHeader.getNonce();
        URI uri = authHeader.getURI();
        if (uri == null) {
           return false;
        }
        // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
        String qop = authHeader.getQop();
        // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
        // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
        String cNonce = authHeader.getCNonce();
        // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
        int nc = authHeader.getNonceCount();
        String ncStr = new DecimalFormat("00000000").format(nc);
//        String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16));
        String A1 = username + ":" + realm + ":" + pass;
        String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
        byte mdbytes[] = messageDigest.digest(A1.getBytes());
        String HA1 = toHexString(mdbytes);
        System.out.println("A1: " + A1);
        System.out.println("A2: " + A2);
        mdbytes = messageDigest.digest(A2.getBytes());
        String HA2 = toHexString(mdbytes);
        System.out.println("HA1: " + HA1);
        System.out.println("HA2: " + HA2);
        String cnonce = authHeader.getCNonce();
        System.out.println("nonce: " + nonce);
        System.out.println("nc: " + ncStr);
        System.out.println("cnonce: " + cnonce);
        System.out.println("qop: " + qop);
        String KD = HA1 + ":" + nonce;
        if (cnonce != null) {
            KD += ":" + cnonce;
        if (qop != null && qop.equals("auth") ) {
            if (nc != -1) {
                KD += ":" + ncStr;
            }
            if (cnonce != null) {
                KD += ":" + cnonce;
            }
            KD += ":" + qop;
        }
        KD += ":" + HA2;
        System.out.println("KD: " + KD);
        mdbytes = messageDigest.digest(KD.getBytes());
        String mdString = toHexString(mdbytes);
        System.out.println("mdString: " + mdString);
        String response = authHeader.getResponse();
        System.out.println("response: " + response);
        return mdString.equals(response);
        
    }
    public static void main(String[] args) throws NoSuchAlgorithmException {
        MessageDigest  messageDigest2 = MessageDigest.getInstance(DEFAULT_ALGORITHM);
        String realm = "DS-2CD2520F";
        String username = "admin";
        String passwd = "12345";
        String nonce = "4d6a553452444d30525441364e6d4d304e6a68684e47553d";
        String uri = "/ISAPI/Streaming/channels/101/picture";
        // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
        String qop = "auth";
        // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
        // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
        String cNonce = "C1A5298F939E87E8F962A5EDFC206918";
        // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
        int nc = 1;
        String A1 = username + ":" + realm + ":" + passwd;
        System.out.println("A1: " + A1);
        String A2 = "GET" + ":" + uri.toString();
        System.out.println("A2: " + A2);
        byte mdbytes[] = messageDigest2.digest(A1.getBytes());
        String HA1 = toHexString(mdbytes);
        System.out.println("HA1: " + HA1);
        mdbytes = messageDigest2.digest(A2.getBytes());
        String HA2 = toHexString(mdbytes);
        System.out.println("HA2: " + HA2);
        String cnonce = "93d4d37df32e1a85";
        String KD = HA1 + ":" + nonce;
        if (nc != -1) {
            KD += ":" + "00000001";
        }
        if (cnonce != null) {
            KD += ":" + cnonce;
        }
        if (qop != null) {
            KD += ":" + qop;
        }
        KD += ":" + HA2;
        System.out.println("KD: " + KD);
        mdbytes = messageDigest2.digest(KD.getBytes());
        String mdString = toHexString(mdbytes);
        String response = "3993a815e5cdaf4470e9b4f9bd41cf4a";
        System.out.println(mdString);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java
@@ -21,6 +21,6 @@
        // TODO 后续处理,只有第一次注册时调用查询设备信息,如需更新调用更新API接口
        cmder.deviceInfoQuery(device);
        
        cmder.catalogQuery(device);
        cmder.catalogQuery(device, null);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
@@ -1,9 +1,6 @@
package com.genersoft.iot.vmp.gb28181.bean;
import java.util.List;
import java.util.Map;
public class Device {
    /**
@@ -46,23 +43,35 @@
    private String streamMode;
    /**
     * wan地址_ip
     */
    private String  ip;
    /**
     * wan地址_port
     */
    private int port;
    /**
     * wan地址
     */
    private Host host;
    private String  hostAddress;
    
    /**
     * 在线
     */
    private int online;
    /**
     * 通道列表
     * 注册时间
     */
//    private Map<String,DeviceChannel> channelMap;
    private Long registerTimeMillis;
    /**
     * 通道个数
     */
    private int channelCount;
    private List<String> channelList;
    public String getDeviceId() {
        return deviceId;
@@ -120,12 +129,28 @@
        this.streamMode = streamMode;
    }
    public Host getHost() {
        return host;
    public String getIp() {
        return ip;
    }
    public void setHost(Host host) {
        this.host = host;
    public void setIp(String ip) {
        this.ip = ip;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    public String getHostAddress() {
        return hostAddress;
    }
    public void setHostAddress(String hostAddress) {
        this.hostAddress = hostAddress;
    }
    public int getOnline() {
@@ -144,11 +169,11 @@
        this.channelCount = channelCount;
    }
    public List<String> getChannelList() {
        return channelList;
    public Long getRegisterTimeMillis() {
        return registerTimeMillis;
    }
    public void setChannelList(List<String> channelList) {
        this.channelList = channelList;
    public void setRegisterTimeMillis(Long registerTimeMillis) {
        this.registerTimeMillis = registerTimeMillis;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
@@ -2,10 +2,17 @@
public class DeviceChannel {
    /**
     * 通道id
     */
    private String channelId;
    /**
     * 设备id
     */
    private String deviceId;
    
    /**
     * 通道名
@@ -141,18 +148,20 @@
    /**
     * 流唯一编号,存在表示正在直播
     */
    private String  ssrc;
    private String  streamId;
    /**
     *  是否含有音频
     */
    private  boolean hasAudio;
    private boolean hasAudio;
    /**
     *  是否正在播放
     */
    private  boolean play;
    public String getDeviceId() {
        return deviceId;
    }
    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }
    public void setPTZType(int PTZType) {
        this.PTZType = PTZType;
@@ -379,14 +388,6 @@
        this.subCount = subCount;
    }
    public String getSsrc() {
        return ssrc;
    }
    public void setSsrc(String ssrc) {
        this.ssrc = ssrc;
    }
    public boolean isHasAudio() {
        return hasAudio;
    }
@@ -395,11 +396,11 @@
        this.hasAudio = hasAudio;
    }
    public boolean isPlay() {
        return play;
    public String getStreamId() {
        return streamId;
    }
    public void setPlay(boolean play) {
        this.play = play;
    public void setStreamId(String streamId) {
        this.streamId = streamId;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
New file
@@ -0,0 +1,50 @@
package com.genersoft.iot.vmp.gb28181.event;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.sip.ResponseEvent;
import javax.sip.message.Request;
import java.util.EventObject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class SipSubscribe {
    private final static Logger logger = LoggerFactory.getLogger(SipSubscribe.class);
    private Map<String, SipSubscribe.Event> errorSubscribes = new ConcurrentHashMap<>();
    private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>();
    public interface Event {
        void response(ResponseEvent event);
    }
    public void addErrorSubscribe(String key, SipSubscribe.Event event) {
        errorSubscribes.put(key, event);
    }
    public void addOkSubscribe(String key, SipSubscribe.Event event) {
        okSubscribes.put(key, event);
    }
    public SipSubscribe.Event getErrorSubscribe(String key) {
        return errorSubscribes.get(key);
    }
    public SipSubscribe.Event getOkSubscribe(String key) {
        return okSubscribes.get(key);
    }
    public int getErrorSubscribesSize(){
        return errorSubscribes.size();
    }
    public int getOkSubscribesSize(){
        return okSubscribes.size();
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
@@ -7,7 +7,9 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.alibaba.fastjson.JSON;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,7 +56,10 @@
    
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private EventPublisher publisher;
    
@@ -82,10 +87,11 @@
    @Autowired
    @Lazy
    private RegisterResponseProcessor registerResponseProcessor;
    @Autowired
    private OtherResponseProcessor otherResponseProcessor;
    // 注:这里使用注解会导致循环依赖注入,暂用springBean
    private SipProvider tcpSipProvider;
        
@@ -140,6 +146,7 @@
            processor.setOffLineDetector(offLineDetector);
            processor.setCmder(cmder);
            processor.setStorager(storager);
            processor.setRedisCatchStorage(redisCatchStorage);
            return processor;
        } else {
            return new OtherRequestProcessor();
@@ -147,6 +154,7 @@
    }
    
    public ISIPResponseProcessor createResponseProcessor(ResponseEvent evt) {
        Response response = evt.getResponse();
        CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
        String method = cseqHeader.getMethod();
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
@@ -2,6 +2,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -24,8 +25,10 @@
    public static final String CALLBACK_CMD_PlAY = "CALLBACK_PLAY";
    private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
    public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
    private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
    public void put(String key, DeferredResult result) {
        map.put(key, result);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -2,8 +2,8 @@
import com.genersoft.iot.vmp.common.StreamInfo;
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.gb28181.bean.ParentPlatform;
/**    
 * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -84,7 +84,7 @@
     * @param device  视频设备
     * @param channelId  预览通道
     */
    void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event);
    void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    
    /**
     * 请求回放视频流
@@ -94,15 +94,16 @@
     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */
    void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event);
    void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
    
    /**
     * 视频流停止
     * 
     * @param ssrc  ssrc
     */
    void streamByeCmd(String ssrc, SipSubscribe.Event okEvent);
    void streamByeCmd(String ssrc);
    /**
     * 语音广播
     * 
@@ -176,7 +177,7 @@
     * 
     * @param device 视频设备
     */
    boolean catalogQuery(Device device);
    boolean catalogQuery(Device device, SipSubscribe.Event errorEvent);
    
    /**
     * 查询录像信息
@@ -214,4 +215,11 @@
     * @param device 视频设备
     */
    boolean mobilePostitionQuery(Device device);
    /**
     * 释放rtpserver
     * @param device
     * @param channelId
     */
    void closeRTPServer(Device device, String channelId);
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
@@ -47,9 +47,8 @@
    
    public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException {
        Request request = null;
        Host host = device.getHost();
        // sipuri
        SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
        SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
        // via
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
@@ -75,22 +74,21 @@
        request = sipFactory.createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
                toHeader, viaHeaders, maxForwards);
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "MANSCDP+xml");
        request.setContent(content, contentTypeHeader);
        return request;
    }
    
    public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
        Request request = null;
        Host host = device.getHost();
        //请求行
        SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, host.getAddress());
        SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
        //via
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        // ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
        ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
        ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), device.getTransport(), viaTag);
        viaHeader.setRPort();
        viaHeaders.add(viaHeader);
        //from
        SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(),sipConfig.getSipDomain());
        Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
@@ -122,20 +120,18 @@
        // Subject
        SubjectHeader subjectHeader = sipFactory.createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getSipId(), 0));
        request.addHeader(subjectHeader);
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "SDP");
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
        request.setContent(content, contentTypeHeader);
        return request;
    }
    
    public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException {
        Request request = null;
        Host host = device.getHost();
        //请求行
        SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
        //via
        SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
        // via
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        // ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
        ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
        ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), device.getTransport(), viaTag);
        viaHeader.setRPort();
        viaHeaders.add(viaHeader);
        //from
@@ -167,7 +163,7 @@
        // Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(), device.getHost().getIp()+":"+device.getHost().getPort()));
        request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
        
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "SDP");
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
        request.setContent(content, contentTypeHeader);
        return request;
    }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -1,17 +1,14 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import java.text.ParseException;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipProvider;
import javax.sip.TransactionDoesNotExistException;
import javax.sip.*;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.header.Header;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
@@ -19,9 +16,13 @@
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
@@ -41,6 +42,8 @@
 */
@Component
public class SIPCommander implements ISIPCommander {
    private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
    
    @Autowired
    private SipConfig sipConfig;
@@ -53,6 +56,9 @@
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    
    @Autowired
    @Qualifier(value="tcpSipProvider")
@@ -63,13 +69,19 @@
    private SipProvider udpSipProvider;
    @Autowired
    private ZLMUtils zlmUtils;
    private ZLMRTPServerFactory zlmrtpServerFactory;
    @Value("${media.rtp.enable}")
    private boolean rtpEnable;
    @Value("${media.seniorSdp}")
    private boolean seniorSdp;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    @Autowired
    private SipSubscribe sipSubscribe;
@@ -176,19 +188,29 @@
    * @param moveSpeed  镜头移动速度 默认 0XFF (0-255)
    * @param zoomSpeed  镜头缩放速度 默认 0X1 (0-255)
    */
    public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
    /**
     * 云台指令码计算
     *
     * @param cmdCode 指令码
     * @param horizonSpeed    水平移动速度
     * @param verticalSpeed    垂直移动速度
     * @param zoomSpeed        缩放速度
     * @return
     */
    public static String frontEndCmdString(int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
        StringBuilder builder = new StringBuilder("A50F01");
        String strTmp;
        strTmp = String.format("%02X", cmdCode);
        builder.append(strTmp, 0, 2);
        strTmp = String.format("%02X", parameter1);
        strTmp = String.format("%02X", horizonSpeed);
        builder.append(strTmp, 0, 2);
        strTmp = String.format("%02X", parameter2);
        strTmp = String.format("%02X", verticalSpeed);
        builder.append(strTmp, 0, 2);
        strTmp = String.format("%X", combineCode2);
        strTmp = String.format("%X", zoomSpeed);
        builder.append(strTmp, 0, 1).append("0");
        //计算校验码
        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + horizonSpeed + verticalSpeed + (zoomSpeed & 0XF0)) % 0X100;
        strTmp = String.format("%02X", checkCode);
        builder.append(strTmp, 0, 2);
        return builder.toString();
@@ -237,14 +259,14 @@
     * @param device          控制设备
     * @param channelId        预览通道
     * @param cmdCode        指令码
     * @param parameter1    数据1
     * @param parameter2    数据2
     * @param combineCode2    组合码2
     * @param horizonSpeed    水平移动速度
     * @param verticalSpeed    垂直移动速度
     * @param zoomSpeed        缩放速度
     */
    @Override
    public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) {
    public boolean frontEndCmd(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
        try {
            String cmdStr= frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
            String cmdStr= frontEndCmdString(cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
            System.out.println("控制字符串:" + cmdStr);
            StringBuffer ptzXml = new StringBuffer(200);
            ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
@@ -258,7 +280,6 @@
            ptzXml.append("</Control>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag");
            transmitRequest(device, request);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
@@ -266,28 +287,39 @@
        } 
        return false;
    }
    /**
     * 请求预览视频流
     *
     *     请求预览视频流
     * @param device  视频设备
     * @param channelId  预览通道
     * @param event hook订阅
     * @param errorEvent sip错误订阅
     */
    @Override
    public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event) {
    public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
        try {
            String ssrc = streamSession.createPlaySsrc();
            String streamId = null;
            if (rtpEnable) {
                streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            }else {
                streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
            }
            String streamMode = device.getStreamMode().toUpperCase();
            MediaServerConfig mediaInfo = storager.getMediaInfo();
            MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
            if (mediaInfo == null) {
                logger.warn("点播时发现ZLM尚未连接...");
                return;
            }
            String mediaPort = null;
            // 使用动态udp端口
            if (rtpEnable) {
                mediaPort = zlmUtils.getNewRTPPort(ssrc) + "";
                mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + "";
            }else {
                mediaPort = mediaInfo.getRtpProxyPort();
            }
            String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
            // 添加订阅
            JSONObject subscribeKey = new JSONObject();
            subscribeKey.put("app", "rtp");
@@ -297,7 +329,8 @@
            //
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
            content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
//            content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
            content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
            content.append("s=Play\r\n");
            content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
            content.append("t=0 0\r\n");
@@ -327,17 +360,14 @@
            }
            content.append("y="+ssrc+"\r\n");//ssrc
//            String fromTag = UUID.randomUUID().toString();
//            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, fromTag, null, ssrc);
            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
            ClientTransaction transaction = transmitRequest(device, request);
            streamSession.put(ssrc, transaction);
            DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
            if (deviceChannel != null) {
                deviceChannel.setSsrc(ssrc);
                storager.updateChannel(device.getDeviceId(), deviceChannel);
            }
            ClientTransaction transaction = transmitRequest(device, request, errorEvent);
            streamSession.put(streamId, transaction);
            // TODO 订阅SIP response,处理对方的错误返回
        } catch ( SipException | ParseException | InvalidArgumentException e) {
@@ -354,9 +384,10 @@
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */ 
    @Override
    public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event) {
    public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
            , SipSubscribe.Event errorEvent) {
        try {
            MediaServerConfig mediaInfo = storager.getMediaInfo();
            MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
            String ssrc = streamSession.createPlayBackSsrc();
            String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
            // 添加订阅
@@ -378,57 +409,91 @@
            String mediaPort = null;
            // 使用动态udp端口
            if (rtpEnable) {
                mediaPort = zlmUtils.getNewRTPPort(ssrc) + "";
                mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + "";
            }else {
                mediaPort = mediaInfo.getRtpProxyPort();
            }
            String streamMode = device.getStreamMode().toUpperCase();
            if("TCP-PASSIVE".equals(streamMode)) {
                content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
            }else if ("TCP-ACTIVE".equals(streamMode)) {
                content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
            }else if("UDP".equals(streamMode)) {
                content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
            if (seniorSdp) {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
                content.append("a=rtpmap:126 H264/90000\r\n");
                content.append("a=rtpmap:125 H264S/90000\r\n");
                content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
                content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
                content.append("a=fmtp:99 profile-level-id=3\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
                    content.append("a=setup:active\r\n");
                    content.append("a=connection:new\r\n");
                }
            }else {
                if("TCP-PASSIVE".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" 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");
                }else if("UDP".equals(streamMode)) {
                    content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
                }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
                    content.append("a=setup:active\r\n");
                    content.append("a=connection:new\r\n");
                }
            }
            content.append("a=recvonly\r\n");
            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
            content.append("a=rtpmap:126 H264/90000\r\n");
            content.append("a=rtpmap:125 H264S/90000\r\n");
            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
            content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
            content.append("a=fmtp:99 profile-level-id=3\r\n");
            content.append("a=rtpmap:98 H264/90000\r\n");
            content.append("a=rtpmap:97 MPEG4/90000\r\n");
            content.append("a=rtpmap:96 PS/90000\r\n");
            if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
                content.append("a=setup:passive\r\n");
                content.append("a=connection:new\r\n");
            }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
                content.append("a=setup:active\r\n");
                content.append("a=connection:new\r\n");
            }
            content.append("y="+ssrc+"\r\n");//ssrc
            
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "playback", null);
            ClientTransaction transaction = transmitRequest(device, request);
            streamSession.put(ssrc, transaction);
            ClientTransaction transaction = transmitRequest(device, request, errorEvent);
            streamSession.put(streamId, transaction);
        } catch ( SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 视频流停止
     * 
     */
    @Override
    public void streamByeCmd(String ssrc) {
        streamByeCmd(ssrc, null);
    }
    @Override
    public void streamByeCmd(String streamId, SipSubscribe.Event okEvent) {
        
        try {
            ClientTransaction transaction = streamSession.get(ssrc);
            ClientTransaction transaction = streamSession.get(streamId);
            // 服务重启后
            if (transaction == null) {
                StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
                if (streamInfo != null) {
                }
                return;
            }
            
@@ -436,6 +501,9 @@
            if (dialog == null) {
                return;
            }
            Request byeRequest = dialog.createRequest(Request.BYE);
            SipURI byeURI = (SipURI) byeRequest.getRequestURI();
            String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString();
@@ -452,8 +520,16 @@
            } else if("UDP".equals(protocol)) {
                clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
            }
            CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
            if (okEvent != null) {
                sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
            }
            dialog.sendRequest(clientTransaction);
            streamSession.remove(ssrc);
            streamSession.remove(streamId);
            zlmrtpServerFactory.closeRTPServer(streamId);
        } catch (TransactionDoesNotExistException e) {
            e.printStackTrace();
        } catch (SipException e) {
@@ -571,6 +647,7 @@
            catalogXml.append("</Query>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag");
            transmitRequest(device, request);
            
        } catch (SipException | ParseException | InvalidArgumentException e) {
@@ -586,7 +663,7 @@
     * @param device 视频设备
     */ 
    @Override
    public boolean catalogQuery(Device device) {
    public boolean catalogQuery(Device device, SipSubscribe.Event errorEvent) {
        // 清空通道
        storager.cleanChannelsForDevice(device.getDeviceId());
        try {
@@ -598,8 +675,9 @@
            catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            catalogXml.append("</Query>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", "ToCatalogTag");
            transmitRequest(device, request);
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", null);
            transmitRequest(device, request, errorEvent);
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
@@ -631,7 +709,8 @@
            recordInfoXml.append("<Type>all</Type>\r\n");
            recordInfoXml.append("</Query>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
            Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", null);
            transmitRequest(device, request);
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
@@ -683,17 +762,45 @@
        // TODO Auto-generated method stub
        return false;
    }
    private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
        return transmitRequest(device, request, null, null);
    }
    private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent) throws SipException {
        return transmitRequest(device, request, errorEvent, null);
    }
    private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException {
        ClientTransaction clientTransaction = null;
        if("TCP".equals(device.getTransport())) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(request);
        } else if("UDP".equals(device.getTransport())) {
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
        }
        CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
        // 添加错误订阅
        if (errorEvent != null) {
            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), errorEvent);
        }
        // 添加订阅
        if (okEvent != null) {
            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
        }
        clientTransaction.sendRequest();
        return clientTransaction;
    }
    @Override
    public void closeRTPServer(Device device, String channelId) {
        if (rtpEnable) {
            String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
            zlmrtpServerFactory.closeRTPServer(streamId);
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -10,6 +10,7 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
@@ -47,6 +48,8 @@
    private SIPCommander cmder;
    private IVideoManagerStorager storager;
    private IRedisCatchStorage redisCatchStorage;
    private EventPublisher publisher;
@@ -294,7 +297,7 @@
                device.setStreamMode("UDP");
            }
            storager.updateDevice(device);
            cmder.catalogQuery(device);
            cmder.catalogQuery(device, null);
            // 回复200 OK
            responseAck(evt);
            if (offLineDetector.isOnline(deviceId)) {
@@ -315,12 +318,16 @@
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);
            if (offLineDetector.isOnline(deviceId)) {
                publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
            } else {
            // 检查设备是否存在, 不存在则不回复
            if (storager.exists(deviceId)) {
                // 回复200 OK
                responseAck(evt);
                if (offLineDetector.isOnline(deviceId)) {
                    publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
                } else {
                }
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
@@ -447,10 +454,10 @@
            String NotifyType =XmlUtil.getText(rootElement, "NotifyType");
            if (NotifyType.equals("121")){
                logger.info("媒体播放完毕,通知关流");
                StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, "*");
                StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, "*");
                if (streamInfo != null) {
                    storager.stopPlayback(streamInfo);
                    cmder.streamByeCmd(streamInfo.getSsrc());
                    redisCatchStorage.stopPlayback(streamInfo);
                    cmder.streamByeCmd(streamInfo.getStreamId());
                }
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
@@ -503,4 +510,11 @@
        this.offLineDetector = offLineDetector;
    }
    public IRedisCatchStorage getRedisCatchStorage() {
        return redisCatchStorage;
    }
    public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
        this.redisCatchStorage = redisCatchStorage;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
@@ -107,17 +107,15 @@
                    rPort = viaHeader.getPort();
                }
                //
                Host host = new Host();
                host.setIp(received);
                host.setPort(rPort);
                host.setAddress(received.concat(":").concat(String.valueOf(rPort)));
                AddressImpl address = (AddressImpl) fromHeader.getAddress();
                SipUri uri = (SipUri) address.getURI();
                String deviceId = uri.getUser();
                device = new Device();
                device.setStreamMode("UDP");
                device.setDeviceId(deviceId);
                device.setHost(host);
                device.setIp(received);
                device.setPort(rPort);
                device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
                // 注销成功
                if (expiresHeader != null && expiresHeader.getExpires() == 0) {
                    registerFlag = 2;
@@ -141,9 +139,15 @@
            // 下发catelog查询目录
            if (registerFlag == 1 && device != null) {
                logger.info("注册成功! deviceId:" + device.getDeviceId());
                boolean exists = storager.exists(device.getDeviceId());
                device.setRegisterTimeMillis(System.currentTimeMillis());
                storager.updateDevice(device);
                publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
                handler.onRegister(device);
                // 只有第一次注册才更新通道
                if (!exists) {
                    handler.onRegister(device);
                }
            } else if (registerFlag == 2) {
                logger.info("注销成功! deviceId:" + device.getDeviceId());
                publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java
@@ -12,6 +12,7 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import gov.nist.javax.sip.header.CSeq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@@ -23,14 +24,14 @@
/**
 * @Description:处理INVITE响应
 * @Description:处理INVITE响应
 * @author: swwheihei
 * @date: 2020年5月3日 下午4:43:52
 * @date: 2020年5月3日 下午4:43:52
 */
@Component
public class InviteResponseProcessor implements ISIPResponseProcessor {
    private final static Logger logger = LoggerFactory.getLogger(SIPProcessorFactory.class);
    private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
    /**
     * 处理invite响应
@@ -49,48 +50,16 @@
            // 成功响应
            // 下发ack
            if (statusCode == Response.OK) {
                // ClientTransaction clientTransaction = evt.getClientTransaction();
                // if(clientTransaction == null){
                // logger.error("回复ACK时,clientTransaction为null >>> {}",response);
                // return;
                // }
                // Dialog clientDialog = clientTransaction.getDialog();
                // CSeqHeader clientCSeqHeader = (CSeqHeader)
                // response.getHeader(CSeqHeader.NAME);
                // long cseqId = clientCSeqHeader.getSeqNumber();
                // /*
                // createAck函数,创建的ackRequest,会采用Invite响应的200OK,中的contact字段中的地址,作为目标地址。
                // 有的终端传上来的可能还是内网地址,会造成ack发送不出去。接受不到音视频流
                // 所以在此处统一替换地址。和响应消息的Via头中的地址保持一致。
                // */
                // Request ackRequest = clientDialog.createAck(cseqId);
                // SipURI requestURI = (SipURI) ackRequest.getRequestURI();
                // ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
                // try {
                // requestURI.setHost(viaHeader.getHost());
                // } catch (Exception e) {
                // e.printStackTrace();
                // }
                // requestURI.setPort(viaHeader.getPort());
                // clientDialog.sendAck(ackRequest);
                Dialog dialog = evt.getDialog();
                CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
                Request reqAck = dialog.createAck(cseq.getSeqNumber());
                SipURI requestURI = (SipURI) reqAck.getRequestURI();
                ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
                // String viaHost =viaHeader.getHost();
                //getHost()函数取回的IP地址是“[xxx.xxx.xxx.xxx:yyyy]”的格式,需用正则表达式截取为“xxx.xxx.xxx.xxx"格式
                // Pattern p = Pattern.compile("(?<=//|)((\\w)+\\.)+\\w+");
                // Matcher matcher = p.matcher(viaHeader.getHost());
                // if (matcher.find()) {
                //     requestURI.setHost(matcher.group());
                // }
                requestURI.setHost(viaHeader.getHost());
                requestURI.setPort(viaHeader.getPort());
                reqAck.setRequestURI(requestURI);
                dialog.sendAck(reqAck);
            }
        } catch (InvalidArgumentException | SipException e) {
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
@@ -2,6 +2,7 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,6 +30,9 @@
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Value("${media.port}")
    private int mediaHttpPort;
@@ -36,10 +40,10 @@
    @RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8")
    public Object proxy(HttpServletRequest request, HttpServletResponse response){
        if (storager.getMediaInfo() == null) {
        if (redisCatchStorage.getMediaInfo() == null) {
            return "未接入流媒体";
        }
        MediaServerConfig mediaInfo = storager.getMediaInfo();
        MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
        String requestURI = String.format("http://%s:%s%s?%s&%s",
                mediaInfo.getLocalIP(),
                mediaHttpPort,
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -4,13 +4,17 @@
import java.text.DecimalFormat;
import java.util.ArrayList;
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.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.IpUtil;
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,13 +48,22 @@
    private SIPCommander cmder;
    @Autowired
    private IPlayService playService;
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private ZLMHttpHookSubscribe subscribe;
    @Value("${media.autoApplyPlay}")
    private boolean autoApplyPlay;
    @Value("${media.ip}")
    private String mediaIp;
@@ -135,34 +148,6 @@
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
        if (subscribe != null) subscribe.response(json);
//        if ("rtp".equals(app)) {
//            String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
//            StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc);
//            if ("rtp".equals(app) && streamInfoForPlay != null ) {
//                MediaServerConfig mediaInfo = storager.getMediaInfo();
//                streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlay.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlay.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
//                streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
//                storager.startPlay(streamInfoForPlay);
//            }
//
//            StreamInfo streamInfoForPlayBack = storager.queryPlaybackBySSRC(ssrc);
//            if ("rtp".equals(app) && streamInfoForPlayBack != null ) {
//                MediaServerConfig mediaInfo = storager.getMediaInfo();
//                streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlayBack.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlayBack.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
//                streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
//                streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
//                storager.startPlayback(streamInfoForPlayBack);
//            }
//        }
        // TODO Auto-generated method stub
        
@@ -268,15 +253,13 @@
        String app = json.getString("app");
        String streamId = json.getString("stream");
        boolean regist = json.getBoolean("regist");
//        String ssrc = String.format("%10d", Integer.parseInt(streamId, 16)); // ZLM 要求大写且首位补零
        String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
        StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);
        StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
        if ("rtp".equals(app) && !regist ) {
            if (streamInfo!=null){
                storager.stopPlay(streamInfo);
                redisCatchStorage.stopPlay(streamInfo);
            }else{
                streamInfo = storager.queryPlaybackBySSRC(ssrc);
                storager.stopPlayback(streamInfo);
                streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
                redisCatchStorage.stopPlayback(streamInfo);
            }
        }
@@ -299,17 +282,15 @@
            logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
        }
        
        BigInteger bigint=new BigInteger(json.getString("stream"), 16);
        int numb=bigint.intValue();
        String ssrc = String.format("%010d", numb);
        cmder.streamByeCmd(ssrc);
        StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);
        String streamId = json.getString("stream");
        cmder.streamByeCmd(streamId);
        StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
        if (streamInfo!=null){
            storager.stopPlay(streamInfo);
            redisCatchStorage.stopPlay(streamInfo);
        }else{
            streamInfo = storager.queryPlaybackBySSRC(ssrc);
            storager.stopPlayback(streamInfo);
            streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
            redisCatchStorage.stopPlayback(streamInfo);
        }
        
        JSONObject ret = new JSONObject();
@@ -330,7 +311,31 @@
            logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        if (autoApplyPlay) {
            String app = json.getString("app");
            String streamId = json.getString("stream");
                StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
            if ("rtp".equals(app) && streamId.indexOf("gb_play") > -1 && streamInfo == null) {
                String[] s = streamId.split("_");
                if (s.length == 4) {
                    String deviceId = s[2];
                    String channelId = s[3];
                    Device device = storager.queryVideoDevice(deviceId);
                    if (device != null) {
                        UUID uuid = UUID.randomUUID();
                        cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
                            logger.info("收到订阅消息: " + response.toJSONString());
                            playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
                        }, null);
                    }
                }
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -354,7 +359,7 @@
//        MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0);
        MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class);
        mediaServerConfig.setLocalIP(mediaIp);
        storager.updateMediaInfo(mediaServerConfig);
        redisCatchStorage.updateMediaInfo(mediaServerConfig);
        // TODO Auto-generated method stub
        
        JSONObject ret = new JSONObject();
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
@@ -116,4 +116,8 @@
    public JSONObject openRtpServer(Map<String, Object> param){
        return sendPost("openRtpServer",param);
    }
    public JSONObject closeRtpServer(Map<String, Object> param) {
        return sendPost("closeRtpServer",param);
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
New file
@@ -0,0 +1,95 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ZLMRTPServerFactory {
    private Logger logger = LoggerFactory.getLogger("ZLMRTPServerFactory");
    @Value("${media.rtp.udpPortRange}")
    private String udpPortRange;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    private int[] udpPortRangeArray = new int[2];
    private int currentPort = 0;
    public int createRTPServer(String streamId) {
        Map<String, Object> param = new HashMap<>();
        int result = -1;
        int newPort = getPortFromUdpPortRange();
        param.put("port", newPort);
        param.put("enable_tcp", 1);
        param.put("stream_id", streamId);
        JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param);
        System.out.println(jsonObject);
        if (jsonObject != null) {
            switch (jsonObject.getInteger("code")){
                case 0:
                    result= newPort;
                    break;
                case -300: // id已经存在
                    result = newPort;
                    break;
                case -400: // 端口占用
                    result= createRTPServer(streamId);
                    break;
                default:
                    logger.error("创建RTP Server 失败: " + jsonObject.getString("msg"));
                    break;
            }
        }else {
            //  检查ZLM状态
            logger.error("创建RTP Server 失败: 请检查ZLM服务");
        }
        return result;
    }
    public boolean closeRTPServer(String streamId) {
        boolean result = false;
        Map<String, Object> param = new HashMap<>();
        param.put("stream_id", streamId);
        JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(param);
        if (jsonObject != null ) {
            if (jsonObject.getInteger("code") == 0) {
                result = jsonObject.getInteger("hit") == 1;
            }else {
                logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
            }
        }else {
            //  检查ZLM状态
            logger.error("关闭RTP Server 失败: 请检查ZLM服务");
        }
        return result;
    }
    private int getPortFromUdpPortRange() {
        if (currentPort == 0) {
            String[] udpPortRangeStrArray = udpPortRange.split(",");
            udpPortRangeArray[0] = Integer.parseInt(udpPortRangeStrArray[0]);
            udpPortRangeArray[1] = Integer.parseInt(udpPortRangeStrArray[1]);
        }
        if (currentPort == 0 || currentPort++ > udpPortRangeArray[1]) {
            currentPort = udpPortRangeArray[0];
            return udpPortRangeArray[0];
        } else {
            if (currentPort % 2 == 1) {
                currentPort++;
            }
            return currentPort++;
        }
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -4,6 +4,7 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import okhttp3.*;
import org.slf4j.Logger;
@@ -29,6 +30,9 @@
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Value("${media.ip}")
    private String mediaIp;
@@ -69,7 +73,7 @@
            logger.info("zlm接入成功...");
            if (autoConfig) saveZLMConfig();
            mediaServerConfig = getMediaServerConfig();
            storager.updateMediaInfo(mediaServerConfig);
            redisCatchStorage.updateMediaInfo(mediaServerConfig);
        }
    }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java
File was deleted
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
New file
@@ -0,0 +1,58 @@
package com.genersoft.iot.vmp.storager;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import java.util.Map;
public interface IRedisCatchStorage {
    /**
     * 开始播放时将流存入
     *
     * @param stream 流信息
     * @return
     */
    boolean startPlay(StreamInfo stream);
    /**
     * 停止播放时删除
     *
     * @return
     */
    boolean stopPlay(StreamInfo streamInfo);
    /**
     * 查询播放列表
     * @return
     */
    StreamInfo queryPlay(StreamInfo streamInfo);
    StreamInfo queryPlayByStreamId(String steamId);
    StreamInfo queryPlaybackByStreamId(String steamId);
    StreamInfo queryPlayByDevice(String deviceId, String code);
    /**
     * 更新流媒体信息
     * @param mediaServerConfig
     * @return
     */
    boolean updateMediaInfo(MediaServerConfig mediaServerConfig);
    /**
     * 获取流媒体信息
     * @return
     */
    MediaServerConfig getMediaInfo();
    Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
    boolean startPlayback(StreamInfo stream);
    boolean stopPlayback(StreamInfo streamInfo);
    StreamInfo queryPlaybackByDevice(String deviceId, String code);
}
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -1,15 +1,10 @@
package com.genersoft.iot.vmp.storager;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.PageResult;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.github.pagehelper.PageInfo;
/**    
 * @Description:视频设备数据存储接口
@@ -17,19 +12,6 @@
 * @date:   2020年5月6日 下午2:14:31     
 */
public interface IVideoManagerStorager {
    /**
     * 更新流媒体信息
     * @param mediaServerConfig
     * @return
     */
    public boolean updateMediaInfo(MediaServerConfig mediaServerConfig);
    /**
     * 获取流媒体信息
     * @return
     */
    public MediaServerConfig getMediaInfo();
    /**   
     * 根据设备ID判断设备是否存在
@@ -79,7 +61,7 @@
     * @param count 每页数量
     * @return
     */
    public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count);
    public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count);
    /**
     * 获取某个设备的通道列表
@@ -88,6 +70,7 @@
     * @return
     */
    public List<DeviceChannel> queryChannelsByDeviceId(String deviceId);
    /**
     * 获取某个设备的通道
     * @param deviceId 设备ID
@@ -95,21 +78,20 @@
     */
    public DeviceChannel queryChannel(String deviceId, String channelId);
    /**
    /**
     * 获取多个设备
     *
     * @param deviceIds 设备ID数组
     * @param page 当前页数
     * @param count 每页数量
     * @return List<Device> 设备对象数组
     */
    public PageResult<Device> queryVideoDeviceList(String[] deviceIds, int page, int count);
    public PageInfo<Device> queryVideoDeviceList(int page, int count);
    /**
     * 获取多个设备
     *
     * @param deviceIds 设备ID数组
     * @return List<Device> 设备对象数组
     */
    public List<Device> queryVideoDeviceList(String[] deviceIds);
    public List<Device> queryVideoDeviceList();
    /**   
     * 删除设备
@@ -135,27 +117,6 @@
     */
    public boolean outline(String deviceId);
    /**
     * 开始播放时将流存入
     *
     * @param stream 流信息
     * @return
     */
    public boolean startPlay(StreamInfo stream);
    /**
     * 停止播放时删除
     *
     * @return
     */
    public boolean stopPlay(StreamInfo streamInfo);
    /**
     * 查找视频流
     *
     * @return
     */
    public StreamInfo queryPlay(StreamInfo streamInfo);
    /**
     * 查询子设备
@@ -166,12 +127,8 @@
     * @param count
     * @return
     */
    PageResult querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count);
    PageInfo querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count);
    /**
     * 更新缓存
     */
    public void updateCatch();
    /**
     * 清空通道
@@ -179,45 +136,4 @@
     */
    void cleanChannelsForDevice(String deviceId);
    StreamInfo queryPlayBySSRC(String ssrc);
    StreamInfo queryPlayByDevice(String deviceId, String code);
    Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
    boolean startPlayback(StreamInfo streamInfo);
    boolean stopPlayback(StreamInfo streamInfo);
    StreamInfo queryPlaybackByDevice(String deviceId, String channelId);
    StreamInfo queryPlaybackBySSRC(String ssrc);
    /**
     * 更新或添加上级平台
     * @param parentPlatform
     */
    boolean updateParentPlatform(ParentPlatform parentPlatform);
    /**
     * 删除上级平台
     * @param parentPlatform
     */
    boolean deleteParentPlatform(ParentPlatform parentPlatform);
    /**
     * 分页获取上级平台
     * @param page
     * @param count
     * @return
     */
    public PageResult<ParentPlatform> queryParentPlatformList(int page, int count);
    /**
     * 获取上级平台
     * @param platformGbId
     * @return
     */
    public ParentPlatform queryParentPlatById(String platformGbId);
}
src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java
File was deleted
src/main/java/com/genersoft/iot/vmp/storager/VodeoMannagerTask.java
File was deleted
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
New file
@@ -0,0 +1,51 @@
package com.genersoft.iot.vmp.storager.dao;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
 * 用于存储设备通道信息
 */
@Mapper
public interface DeviceChannelMapper {
    @Insert("INSERT INTO device_channel (channelId, deviceId, name, manufacture, model, owner, civilCode, block, " +
            "address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " +
            "ipAddress, port, password, PTZType, status) " +
            "VALUES ('${channelId}', '${deviceId}', '${name}', '${manufacture}', '${model}', '${owner}', '${civilCode}', '${block}'," +
            "'${address}', ${parental}, '${parentId}', ${safetyWay}, ${registerWay}, '${certNum}', ${certifiable}, ${errCode}, '${secrecy}', " +
            "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status})")
    int add(DeviceChannel channel);
    @Update("UPDATE device_channel " +
            "SET name=#{name}, manufacture=#{manufacture}, model=#{model}, owner=#{owner}, civilCode=#{civilCode}, " +
            "block=#{block}, address=#{address}, parental=#{parental}, parentId=#{parentId}, safetyWay=#{safetyWay}, " +
            "registerWay=#{registerWay}, certNum=#{certNum}, certifiable=#{certifiable}, errCode=#{errCode}, secrecy=#{secrecy}, " +
            "ipAddress=#{ipAddress}, port=#{port}, password=#{password}, PTZType=#{PTZType}, status=#{status}, streamId=#{streamId}, " +
            "hasAudio=#{hasAudio}" +
            "WHERE deviceId=#{deviceId} AND channelId=#{channelId}")
    int update(DeviceChannel channel);
    @Select(value = {" <script>" +
            "SELECT * FROM ( "+
            " SELECT * , (SELECT count(0) FROM device_channel WHERE parentId=dc.channelId) as subCount FROM device_channel dc " +
            " WHERE dc.deviceId=#{deviceId} " +
            " <if test=\"query != null\"> AND (dc.channelId LIKE '%${query}%' OR dc.name LIKE '%${query}%' OR dc.name LIKE '%${query}%')</if> " +
            " <if test=\"parentChannelId != null\"> AND dc.parentId=#{parentChannelId} </if> " +
            " <if test=\"online == true\" > AND dc.status=1</if>" +
            " <if test=\"online == false\" > AND dc.status=0</if>) dcr" +
            " WHERE 1=1 " +
            " <if test=\"hasSubChannel == true\" >  AND subCount >0</if>" +
            " <if test=\"hasSubChannel == false\" >  AND subCount=0</if>" +
            " </script>"})
    List<DeviceChannel> queryChannelsByDeviceId(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online);
    @Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND channelId=#{channelId}")
    DeviceChannel queryChannel(String deviceId, String channelId);
    @Delete("DELETE FROM device_channel WHERE deviceId=#{deviceId}")
    int cleanChannelsByDeviceId(String deviceId);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
New file
@@ -0,0 +1,66 @@
package com.genersoft.iot.vmp.storager.dao;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
 * 用于存储设备信息
 */
@Mapper
@Repository
public interface DeviceMapper {
    @Select("SELECT * FROM device WHERE deviceId = #{deviceId}")
    Device getDeviceByDeviceId(String deviceId);
    @Insert("INSERT INTO device (" +
                "deviceId, " +
                "name, " +
                "manufacturer, " +
                "model, " +
                "firmware, " +
                "transport," +
                "streamMode," +
                "ip," +
                "port," +
                "hostAddress," +
                "online" +
            ") VALUES (" +
                "#{deviceId}," +
                "#{name}," +
                "#{manufacturer}," +
                "#{model}," +
                "#{firmware}," +
                "#{transport}," +
                "#{streamMode}," +
                "#{ip}," +
                "#{port}," +
                "#{hostAddress}," +
                "#{online}" +
            ")")
    int add(Device device);
    @Update("UPDATE device " +
            "SET name=#{name}, " +
            "manufacturer=#{manufacturer}," +
            "model=#{model}," +
            "firmware=#{firmware}, " +
            "transport=#{transport}," +
            "streamMode=#{streamMode}, " +
            "ip=#{ip}, " +
            "port=#{port}, " +
            "hostAddress=#{hostAddress}, " +
            "online=#{online} " +
            "WHERE deviceId=#{deviceId}")
    int update(Device device);
    @Select("SELECT *, (SELECT count(0) FROM device_channel WHERE deviceId=de.deviceId) as channelCount  FROM device de")
    List<Device> getDevices();
    @Delete("DELETE FROM device WHERE deviceId=#{deviceId}")
    int del(String deviceId);
}
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
New file
@@ -0,0 +1,166 @@
package com.genersoft.iot.vmp.storager.impl;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@Component
public class RedisCatchStorageImpl implements IRedisCatchStorage {
    @Autowired
    private RedisUtil redis;
    @Autowired
    private DeviceChannelMapper deviceChannelMapper;
    /**
     * 开始播放时将流存入redis
     *
     * @return
     */
    @Override
    public boolean startPlay(StreamInfo stream) {
        return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getCahnnelId()),
                stream);
    }
    /**
     * 停止播放时从redis删除
     *
     * @return
     */
    @Override
    public boolean stopPlay(StreamInfo streamInfo) {
        if (streamInfo == null) return false;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId());
        if (deviceChannel != null) {
            deviceChannel.setStreamId(null);
            deviceChannel.setDeviceId(streamInfo.getDeviceID());
            deviceChannelMapper.update(deviceChannel);
        }
        return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
                streamInfo.getStreamId(),
                streamInfo.getDeviceID(),
                streamInfo.getCahnnelId()));
    }
    /**
     * 查询播放列表
     * @return
     */
    @Override
    public StreamInfo queryPlay(StreamInfo streamInfo) {
        return (StreamInfo)redis.get(String.format("%S_%s_%s_%s",
                VideoManagerConstants.PLAYER_PREFIX,
                streamInfo.getStreamId(),
                streamInfo.getDeviceID(),
                streamInfo.getCahnnelId()));
    }
    @Override
    public StreamInfo queryPlayByStreamId(String steamId) {
        List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId));
        if (playLeys == null || playLeys.size() == 0) return null;
        return (StreamInfo)redis.get(playLeys.get(0).toString());
    }
    @Override
    public StreamInfo queryPlaybackByStreamId(String steamId) {
        List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId));
        if (playLeys == null || playLeys.size() == 0) return null;
        return (StreamInfo)redis.get(playLeys.get(0).toString());
    }
    @Override
    public StreamInfo queryPlayByDevice(String deviceId, String code) {
//        List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
        List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
                deviceId,
                code));
        if (playLeys == null || playLeys.size() == 0) return null;
        return (StreamInfo)redis.get(playLeys.get(0).toString());
    }
    /**
     * 更新流媒体信息
     * @param mediaServerConfig
     * @return
     */
    @Override
    public boolean updateMediaInfo(MediaServerConfig mediaServerConfig) {
        return redis.set(VideoManagerConstants.MEDIA_SERVER_PREFIX,mediaServerConfig);
    }
    /**
     * 获取流媒体信息
     * @return
     */
    @Override
    public MediaServerConfig getMediaInfo() {
        return (MediaServerConfig)redis.get(VideoManagerConstants.MEDIA_SERVER_PREFIX);
    }
    @Override
    public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
        Map<String, StreamInfo> streamInfos = new HashMap<>();
//        List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId));
        List<Object> players = redis.scan(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId));
        if (players.size() == 0) return streamInfos;
        for (int i = 0; i < players.size(); i++) {
            String key = (String) players.get(i);
            StreamInfo streamInfo = (StreamInfo)redis.get(key);
            streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getCahnnelId(), streamInfo);
        }
        return streamInfos;
    }
    @Override
    public boolean startPlayback(StreamInfo stream) {
        return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getCahnnelId()),
                stream);
    }
    @Override
    public boolean stopPlayback(StreamInfo streamInfo) {
        if (streamInfo == null) return false;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId());
        if (deviceChannel != null) {
            deviceChannel.setStreamId(null);
            deviceChannel.setDeviceId(streamInfo.getDeviceID());
            deviceChannelMapper.update(deviceChannel);
        }
        return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
                streamInfo.getStreamId(),
                streamInfo.getDeviceID(),
                streamInfo.getCahnnelId()));
    }
    @Override
    public StreamInfo queryPlaybackByDevice(String deviceId, String code) {
        String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
                deviceId,
                code);
        List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
                deviceId,
                code));
        if (playLeys == null || playLeys.size() == 0) {
            playLeys = redis.scan(String.format("%S_*_*_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
                    deviceId));
        }
        if (playLeys == null || playLeys.size() == 0) return null;
        return (StreamInfo)redis.get(playLeys.get(0).toString());
    }
}
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
New file
@@ -0,0 +1,202 @@
package com.genersoft.iot.vmp.storager.impl;
import java.util.*;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.springframework.util.StringUtils;
/**
 * @Description:视频设备数据存储-jdbc实现
 * @author: swwheihei
 * @date:   2020年5月6日 下午2:31:42
 */
@Component
public class VideoManagerStoragerImpl implements IVideoManagerStorager {
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private DeviceChannelMapper deviceChannelMapper;
    /**
     * 根据设备ID判断设备是否存在
     *
     * @param deviceId 设备ID
     * @return true:存在  false:不存在
     */
    @Override
    public boolean exists(String deviceId) {
        return deviceMapper.getDeviceByDeviceId(deviceId) != null;
    }
    /**
     * 视频设备创建
     *
     * @param device 设备对象
     * @return true:创建成功  false:创建失败
     */
    @Override
    public synchronized boolean create(Device device) {
        return deviceMapper.add(device) > 0;
    }
    /**
     * 视频设备更新
     *
     * @param device 设备对象
     * @return true:更新成功  false:更新失败
     */
    @Override
    public synchronized boolean updateDevice(Device device) {
        Device deviceByDeviceId = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
        if (deviceByDeviceId == null) {
            return deviceMapper.add(device) > 0;
        }else {
            return deviceMapper.update(device) > 0;
        }
    }
    @Override
    public synchronized void updateChannel(String deviceId, DeviceChannel channel) {
        String channelId = channel.getChannelId();
        channel.setDeviceId(deviceId);
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId);
        if (deviceChannel == null) {
            deviceChannelMapper.add(channel);
        }else {
            deviceChannelMapper.update(channel);
        }
    }
    /**
     * 获取设备
     *
     * @param deviceId 设备ID
     * @return Device 设备对象
     */
    @Override
    public Device queryVideoDevice(String deviceId) {
        return deviceMapper.getDeviceByDeviceId(deviceId);
    }
    @Override
    public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count) {
        // 获取到所有正在播放的流
        PageHelper.startPage(page, count);
        List<DeviceChannel> all = deviceChannelMapper.queryChannelsByDeviceId(deviceId, null, query, hasSubChannel, online);
        return new PageInfo<>(all);
    }
    @Override
    public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) {
        return deviceChannelMapper.queryChannelsByDeviceId(deviceId, null,null, null, null);
    }
    @Override
    public PageInfo<DeviceChannel> querySubChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, String online, int page, int count) {
        PageHelper.startPage(page, count);
        List<DeviceChannel> all = deviceChannelMapper.queryChannelsByDeviceId(deviceId, parentChannelId, null, null, null);
        return new PageInfo<>(all);
    }
    @Override
    public DeviceChannel queryChannel(String deviceId, String channelId) {
        return deviceChannelMapper.queryChannel(deviceId, channelId);
    }
    /**
     * 获取多个设备
     *
     * @param page 当前页数
     * @param count 每页数量
     * @return PageInfo<Device> 分页设备对象数组
     */
    @Override
    public PageInfo<Device> queryVideoDeviceList(int page, int count) {
        PageHelper.startPage(page, count);
        List<Device> all = deviceMapper.getDevices();
        return new PageInfo<>(all);
    }
    /**
     * 获取多个设备
     *
     * @return List<Device> 设备对象数组
     */
    @Override
    public List<Device> queryVideoDeviceList() {
        List<Device> deviceList =  deviceMapper.getDevices();
        return deviceList;
    }
    /**
     * 删除设备
     *
     * @param deviceId 设备ID
     * @return true:删除成功  false:删除失败
     */
    @Override
    public boolean delete(String deviceId) {
        int result = deviceMapper.del(deviceId);
        return result > 0;
    }
    /**
     * 更新设备在线
     *
     * @param deviceId 设备ID
     * @return true:更新成功  false:更新失败
     */
    @Override
    public synchronized boolean online(String deviceId) {
        Device device = deviceMapper.getDeviceByDeviceId(deviceId);
        device.setOnline(1);
        System.out.println("更新设备在线");
        if (device == null) {
            return false;
        }
        return deviceMapper.update(device) > 0;
    }
    /**
     * 更新设备离线
     *
     * @param deviceId 设备ID
     * @return true:更新成功  false:更新失败
     */
    @Override
    public synchronized boolean outline(String deviceId) {
        Device device = deviceMapper.getDeviceByDeviceId(deviceId);
        device.setOnline(0);
        System.out.println("更新设备离线");
        return deviceMapper.update(device) > 0;
    }
    @Override
    public void cleanChannelsForDevice(String deviceId) {
        int result = deviceChannelMapper.cleanChannelsByDeviceId(deviceId);
    }
}
src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
File was deleted
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
File was deleted
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java
@@ -34,6 +34,7 @@
     * 获取对象 这里重写了bean方法,起主要作用
     */
    public static Object getBean(String beanId) throws BeansException {
        if (applicationContext == null) return null;
        return applicationContext.getBean(beanId);
    }
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
@@ -1,14 +1,14 @@
package com.genersoft.iot.vmp.vmanager.device;
import java.util.List;
import com.genersoft.iot.vmp.common.PageResult;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@@ -18,6 +18,8 @@
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import javax.sip.message.Response;
@CrossOrigin
@RestController
@@ -50,13 +52,13 @@
    }
    
    @GetMapping("/devices")
    public PageResult<Device> devices(int page, int count){
    public PageInfo<Device> devices(int page, int count){
        
        if (logger.isDebugEnabled()) {
            logger.debug("查询所有视频设备API调用");
        }
        
        return storager.queryVideoDeviceList(null, page, count);
        return storager.queryVideoDeviceList(page, count);
    }
    /**
@@ -66,18 +68,33 @@
     * @param count 每页条数
     * @return 通道列表
     */
    /**
     * 分页查询通道数
     *
     * @param deviceId 设备id
     * @param page 当前页
     * @param count 每页条数
     * @param query 查询内容
     * @param online 是否在线  在线 true / 离线 false
     * @param channelType 设备 false/子目录 true
     * @return 通道列表
     */
    @GetMapping("/devices/{deviceId}/channels")
    public ResponseEntity<PageResult> channels(@PathVariable String deviceId,
    public ResponseEntity<PageInfo> channels(@PathVariable String deviceId,
                                               int page, int count,
                                               @RequestParam(required = false) String query,
                                               @RequestParam(required = false) String online,
                                               @RequestParam(required = false) Boolean online,
                                               @RequestParam(required = false) Boolean channelType
    ){
        if (logger.isDebugEnabled()) {
            logger.debug("查询所有视频设备API调用");
        }
        PageResult pageResult = storager.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count);
        if (StringUtils.isEmpty(query)) {
            query = null;
        }
        PageInfo pageResult = storager.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count);
        return new ResponseEntity<>(pageResult,HttpStatus.OK);
    }
    
@@ -86,11 +103,25 @@
        
        if (logger.isDebugEnabled()) {
        }
            logger.debug("设备信息同步API调用,deviceId:" + deviceId);
            logger.debug("设备通道信息同步API调用,deviceId:" + deviceId);
        Device device = storager.queryVideoDevice(deviceId);
        cmder.catalogQuery(device);
        DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>();
        cmder.catalogQuery(device, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId);
            msg.setData(String.format("同步通道失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>(2*1000L);
        result.onTimeout(()->{
            logger.warn(String.format("设备通道信息同步超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId);
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
        return result;
    }
@@ -124,7 +155,7 @@
     * @return 子通道列表
     */
    @GetMapping("/subChannels/{deviceId}/{channelId}/channels")
    public ResponseEntity<PageResult> subChannels(@PathVariable String deviceId,
    public ResponseEntity<PageInfo> subChannels(@PathVariable String deviceId,
                                                  @PathVariable String channelId,
                                                  int page,
                                                  int count,
@@ -137,23 +168,23 @@
        }
        DeviceChannel deviceChannel = storager.queryChannel(deviceId,channelId);
        if (deviceChannel == null) {
            PageResult<DeviceChannel> deviceChannelPageResult = new PageResult<>();
            PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>();
            return new ResponseEntity<>(deviceChannelPageResult,HttpStatus.OK);
        }
        PageResult pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count);
        PageInfo pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count);
        return new ResponseEntity<>(pageResult,HttpStatus.OK);
    }
    @PostMapping("/channel/update/{deviceId}")
    public ResponseEntity<PageResult> updateChannel(@PathVariable String deviceId,DeviceChannel channel){
    public ResponseEntity<PageInfo> updateChannel(@PathVariable String deviceId,DeviceChannel channel){
        storager.updateChannel(deviceId, channel);
        return new ResponseEntity<>(null,HttpStatus.OK);
    }
    @GetMapping("/devices/{deviceId}/transport/{streamMode}")
    @PostMapping("/devices/{deviceId}/transport/{streamMode}")
    public ResponseEntity<PageResult> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){
    public ResponseEntity<PageInfo> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){
        Device device = storager.queryVideoDevice(deviceId);
        device.setStreamMode(streamMode);
        storager.updateDevice(device);
src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/Device.java
File was deleted
src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/DeviceChannel.java
File was deleted
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
@@ -7,6 +7,8 @@
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +29,7 @@
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
import java.text.DecimalFormat;
import java.util.UUID;
@@ -44,6 +47,9 @@
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
@@ -58,18 +64,11 @@
        Device device = storager.queryVideoDevice(deviceId);
        StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId);
        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
        UUID uuid = UUID.randomUUID();
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
        // 超时处理
        result.onTimeout(()->{
            logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
        });
        // 录像查询以channelId作为deviceId查询
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
@@ -78,9 +77,15 @@
            cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
                logger.info("收到订阅消息: " + response.toJSONString());
                playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
            }, event -> {
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                Response response = event.getResponse();
                msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
                resultHolder.invokeResult(msg);
            });
        } else {
            String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
            String streamId = streamInfo.getStreamId();
            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
            if (rtpInfo.getBoolean("exist")) {
                RequestMessage msg = new RequestMessage();
@@ -88,58 +93,107 @@
                msg.setData(JSON.toJSONString(streamInfo));
                resultHolder.invokeResult(msg);
            } else {
                storager.stopPlay(streamInfo);
                // TODO playStreamCmd 超时处理
                redisCatchStorage.stopPlay(streamInfo);
                cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
                    logger.info("收到订阅消息: " + response.toJSONString());
                    playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
                }, event -> {
                    RequestMessage msg = new RequestMessage();
                    msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                    Response response = event.getResponse();
                    msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
                    resultHolder.invokeResult(msg);
                });
            }
        }
        // 超时处理
        result.onTimeout(()->{
            logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
            // 释放rtpserver
            cmder.closeRTPServer(device, channelId);
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
        });
        return result;
    }
    @PostMapping("/play/{ssrc}/stop")
    public ResponseEntity<String> playStop(@PathVariable String ssrc) {
    @PostMapping("/play/{streamId}/stop")
    public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String streamId) {
        cmder.streamByeCmd(ssrc);
        StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);
        if (streamInfo == null)
            return new ResponseEntity<String>("ssrc not found", HttpStatus.OK);
        storager.stopPlay(streamInfo);
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("设备预览停止API调用,ssrc:%s", ssrc));
        }
        logger.debug(String.format("设备预览/回放停止API调用,streamId:%s", streamId));
        if (ssrc != null) {
        UUID uuid = UUID.randomUUID();
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
        // 录像查询以channelId作为deviceId查询
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result);
        cmder.streamByeCmd(streamId, event -> {
            StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
            if (streamInfo == null) {
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                msg.setData("streamId not found");
                resultHolder.invokeResult(msg);
                redisCatchStorage.stopPlay(streamInfo);
            }
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
            Response response = event.getResponse();
            msg.setData(String.format("success"));
            resultHolder.invokeResult(msg);
        });
        if (streamId != null) {
            JSONObject json = new JSONObject();
            json.put("ssrc", ssrc);
            return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
            json.put("streamId", streamId);
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
            msg.setData(json.toString());
            resultHolder.invokeResult(msg);
        } else {
            logger.warn("设备预览停止API调用失败!");
            return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
            logger.warn("设备预览/回放停止API调用失败!");
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
            msg.setData("streamId null");
            resultHolder.invokeResult(msg);
        }
        // 超时处理
        result.onTimeout(()->{
            logger.warn(String.format("设备预览/回放停止超时,streamId:%s ", streamId));
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
        });
        return result;
    }
    /**
     * 将不是h264的视频通过ffmpeg 转码为h264 + aac
     * @param ssrc
     * @param streamId 流ID
     * @return
     */
    @PostMapping("/play/{ssrc}/convert")
    public ResponseEntity<String> playConvert(@PathVariable String ssrc) {
        StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);
    @PostMapping("/play/{streamId}/convert")
    public ResponseEntity<String> playConvert(@PathVariable String streamId) {
        StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
        if (streamInfo == null) {
            logger.warn("视频转码API调用失败!, 视频流已经停止!");
            return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
        }
        String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
        if (!rtpInfo.getBoolean("exist")) {
            logger.warn("视频转码API调用失败!, 视频流已停止推流!");
            return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK);
        } else {
            MediaServerConfig mediaInfo = storager.getMediaInfo();
            MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
            String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(),
                    streamId );
            String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId);
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
@@ -6,6 +6,7 @@
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.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +28,7 @@
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
import java.util.UUID;
@CrossOrigin
@@ -41,6 +43,9 @@
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
@@ -69,15 +74,21 @@
            resultHolder.invokeResult(msg);
        });
        Device device = storager.queryVideoDevice(deviceId);
        StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId);
        StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId);
        if (streamInfo != null) {
            // 停止之前的回放
            cmder.streamByeCmd(streamInfo.getSsrc());
            cmder.streamByeCmd(streamInfo.getStreamId());
        }
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
        cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
            logger.info("收到订阅消息: " + response.toJSONString());
            playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString());
        }, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
            msg.setData(String.format("回放失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        return result;
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
@@ -29,15 +29,14 @@
    private IVideoManagerStorager storager;
    /***
     * http://localhost:8080/api/ptz/34020000001320000002_34020000001320000008?leftRight=1&upDown=0&inOut=0&moveSpeed=50&zoomSpeed=0
     * @param deviceId
     * @param channelId
     * @param leftRight
     * @param upDown
     * @param inOut
     * @param moveSpeed
     * @param zoomSpeed
     * @return
     * 云台控制
     * @param deviceId 设备id
     * @param channelId 通道id
     * @param cmdCode        指令码
     * @param horizonSpeed    水平移动速度
     * @param verticalSpeed    垂直移动速度
     * @param zoomSpeed        缩放速度
     * @return String 控制结果
     */
    @PostMapping("/ptz/{deviceId}/{channelId}")
    public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed){
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java
@@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.vmanager.record;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,7 +33,7 @@
    
    @Autowired
    private DeferredResultHolder resultHolder;
    @GetMapping("/record/{deviceId}/{channelId}")
    public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId,@PathVariable String channelId, String startTime,  String endTime){
        
@@ -42,9 +43,17 @@
        
        Device device = storager.queryVideoDevice(deviceId);
        cmder.recordInfoQuery(device, channelId, startTime, endTime);
        DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>();
        // 指定超时时间 1分钟30秒
        DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>(90*1000L);
        // 录像查询以channelId作为deviceId查询
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_RECORDINFO+channelId, result);
        result.onTimeout(()->{
            RequestMessage msg = new RequestMessage();
            msg.setDeviceId(deviceId);
            msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
            msg.setData("timeout");
            resultHolder.invokeResult(msg);
        });
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
@@ -4,8 +4,10 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.play.PlayController;
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
@@ -25,6 +27,9 @@
    private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private DeferredResultHolder resultHolder;
    @Override
@@ -33,7 +38,13 @@
        msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
        StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
        if (streamInfo != null) {
            storager.startPlay(streamInfo);
            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
            if (deviceChannel != null) {
                deviceChannel.setStreamId(streamInfo.getStreamId());
                storager.updateChannel(deviceId, deviceChannel);
            }
            redisCatchStorage.startPlay(streamInfo);
            msg.setData(JSON.toJSONString(streamInfo));
            resultHolder.invokeResult(msg);
        } else {
@@ -49,7 +60,7 @@
        msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
        StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
        if (streamInfo != null) {
            storager.startPlayback(streamInfo);
            redisCatchStorage.startPlayback(streamInfo);
            msg.setData(JSON.toJSONString(streamInfo));
            resultHolder.invokeResult(msg);
        } else {
@@ -61,13 +72,11 @@
    public StreamInfo onPublishHandler(JSONObject resonse, String deviceId, String channelId, String uuid) {
        String streamId = resonse.getString("id");
        String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
        StreamInfo streamInfo = new StreamInfo();
        streamInfo.setSsrc(ssrc);
        streamInfo.setStreamId(streamId);
        streamInfo.setDeviceID(deviceId);
        streamInfo.setCahnnelId(channelId);
        MediaServerConfig mediaServerConfig = storager.getMediaInfo();
        MediaServerConfig mediaServerConfig = redisCatchStorage.getMediaInfo();
        streamInfo.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
        streamInfo.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java
@@ -1,22 +1,17 @@
package com.genersoft.iot.vmp.web;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.PageResult;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.device.DeviceController;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -65,12 +60,12 @@
        JSONObject result = new JSONObject();
        List<Device> devices;
        if (start == null || limit ==null) {
            devices = storager.queryVideoDeviceList(null);
            devices = storager.queryVideoDeviceList();
            result.put("DeviceCount", devices.size());
        }else {
            PageResult<Device> deviceList = storager.queryVideoDeviceList(null, start/limit, limit);
            PageInfo<Device> deviceList = storager.queryVideoDeviceList(start/limit, limit);
            result.put("DeviceCount", deviceList.getTotal());
            devices = deviceList.getData();
            devices = deviceList.getList();
        }
        JSONArray deviceJSONList = new JSONArray();
@@ -86,8 +81,8 @@
            deviceJsonObject.put("Online", device.getOnline() == 1);
            deviceJsonObject.put("Password", "");
            deviceJsonObject.put("MediaTransport", device.getTransport());
            deviceJsonObject.put("RemoteIP", device.getHost().getIp());
            deviceJsonObject.put("RemotePort", device.getHost().getPort());
            deviceJsonObject.put("RemoteIP", device.getIp());
            deviceJsonObject.put("RemotePort", device.getPort());
            deviceJsonObject.put("LastRegisterAt", "");
            deviceJsonObject.put("LastKeepaliveAt", "");
            deviceJsonObject.put("UpdatedAt", "");
@@ -123,9 +118,9 @@
            deviceChannels = storager.queryChannelsByDeviceId(serial);
            result.put("ChannelCount", deviceChannels.size());
        }else {
            PageResult<DeviceChannel> pageResult = storager.queryChannelsByDeviceId(serial, null, null, null,start/limit, limit);
            PageInfo<DeviceChannel> pageResult = storager.queryChannelsByDeviceId(serial, null, null, null,start/limit, limit);
            result.put("ChannelCount", pageResult.getTotal());
            deviceChannels = pageResult.getData();
            deviceChannels = pageResult.getList();
        }
        JSONArray channleJSONList = new JSONArray();
@@ -159,7 +154,7 @@
            deviceJOSNChannel.put("PTZType ", deviceChannel.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球,
                                                                            //   3 - 固定枪机, 4 - 遥控枪机
            deviceJOSNChannel.put("CustomPTZType", "");
            deviceJOSNChannel.put("StreamID", deviceChannel.getSsrc()); // StreamID 直播流ID, 有值表示正在直播
            deviceJOSNChannel.put("StreamID", deviceChannel.getStreamId()); // StreamID 直播流ID, 有值表示正在直播
            deviceJOSNChannel.put("NumOutputs ", -1); // 直播在线人数
            channleJSONList.add(deviceJOSNChannel);
        }
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
@@ -8,6 +8,7 @@
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.play.PlayController;
import org.slf4j.Logger;
@@ -17,6 +18,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
/**
 * 兼容LiveGBS的API:实时直播
@@ -34,11 +36,16 @@
    @Autowired
    private IVideoManagerStorager storager;
    private boolean closeWaitRTPInfo = false;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private PlayController playController;
    /**
     * 实时直播 - 开始直播
@@ -54,126 +61,54 @@
     * @return
     */
    @RequestMapping(value = "/start")
    private JSONObject start(String serial ,
                             @RequestParam(required = false)Integer channel ,
                             @RequestParam(required = false)String code,
                             @RequestParam(required = false)String cdn,
                             @RequestParam(required = false)String audio,
                             @RequestParam(required = false)String transport,
                             @RequestParam(required = false)String checkchannelstatus ,
                             @RequestParam(required = false)String transportmode,
                             @RequestParam(required = false)String timeout
    private DeferredResult<JSONObject> start(String serial ,
                                             @RequestParam(required = false)Integer channel ,
                                             @RequestParam(required = false)String code,
                                             @RequestParam(required = false)String cdn,
                                             @RequestParam(required = false)String audio,
                                             @RequestParam(required = false)String transport,
                                             @RequestParam(required = false)String checkchannelstatus ,
                                             @RequestParam(required = false)String transportmode,
                                             @RequestParam(required = false)String timeout
    ){
        int getEncoding = closeWaitRTPInfo?  1: 0;
        DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>();
        Device device = storager.queryVideoDevice(serial);
        if (device == null ) {
            JSONObject result = new JSONObject();
            result.put("error","device[ " + serial + " ]未找到");
            return result;
            resultDeferredResult.setResult(result);
        }else if (device.getOnline() == 0) {
            JSONObject result = new JSONObject();
            result.put("error","device[ " + code + " ]offline");
            return result;
            resultDeferredResult.setResult(result);
        }
        resultDeferredResult.onTimeout(()->{
            logger.info("播放等待超时");
            JSONObject result = new JSONObject();
            result.put("error","timeout");
            resultDeferredResult.setResult(result);
             // 清理RTP server
        });
        DeviceChannel deviceChannel = storager.queryChannel(serial, code);
        if (deviceChannel == null) {
            JSONObject result = new JSONObject();
            result.put("error","channel[ " + code + " ]未找到");
            return result;
            resultDeferredResult.setResult(result);
        }else if (deviceChannel.getStatus() == 0) {
            JSONObject result = new JSONObject();
            result.put("error","channel[ " + code + " ]offline");
            return result;
            resultDeferredResult.setResult(result);
        }
        DeferredResult<ResponseEntity<String>> play = playController.play(serial, code);
        // 查询是否已经在播放
        StreamInfo streamInfo = storager.queryPlayByDevice(device.getDeviceId(), code);
        if (streamInfo == null) {
            logger.debug("streamInfo 等于null, 重新点播");
//            streamInfo = cmder.playStreamCmd(device, code);
        }else {
            logger.debug("streamInfo 不等于null, 向流媒体查询是否正在推流");
            String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
            if (rtpInfo.getBoolean("exist")) {
                logger.debug("向流媒体查询正在推流, 直接返回: " + streamInfo.getRtsp());
                JSONObject result = new JSONObject();
                result.put("StreamID", streamInfo.getSsrc());
                result.put("DeviceID", device.getDeviceId());
                result.put("ChannelID", code);
                result.put("ChannelName", deviceChannel.getName());
                result.put("ChannelCustomName", "");
                result.put("FLV", streamInfo.getFlv());
                result.put("WS_FLV", streamInfo.getWs_flv());
                result.put("RTMP", streamInfo.getRtmp());
                result.put("HLS", streamInfo.getHls());
                result.put("RTSP", streamInfo.getRtsp());
                result.put("CDN", "");
                result.put("SnapURL", "");
                result.put("Transport", device.getTransport());
                result.put("StartAt", "");
                result.put("Duration", "");
                result.put("SourceVideoCodecName", "");
                result.put("SourceVideoWidth", "");
                result.put("SourceVideoHeight", "");
                result.put("SourceVideoFrameRate", "");
                result.put("SourceAudioCodecName", "");
                result.put("SourceAudioSampleRate", "");
                result.put("AudioEnable", "");
                result.put("Ondemand", "");
                result.put("InBytes", "");
                result.put("InBitRate", "");
                result.put("OutBytes", "");
                result.put("NumOutputs", "");
                result.put("CascadeSize", "");
                result.put("RelaySize", "");
                result.put("ChannelPTZType", 0);
                return result;
            } else {
                logger.debug("向流媒体查询没有推流, 重新点播");
                storager.stopPlay(streamInfo);
//                streamInfo = cmder.playStreamCmd(device, code);
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",serial, code));
            logger.debug("设备预览 API调用,ssrc:"+streamInfo.getSsrc()+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(streamInfo.getSsrc())));
        }
        boolean lockFlag = true;
        long startTime = System.currentTimeMillis();
        while (lockFlag) {
            try {
                if (System.currentTimeMillis() - startTime > 10 * 1000) {
                    storager.stopPlay(streamInfo);
                    logger.info("播放等待超时");
                    JSONObject result = new JSONObject();
                    result.put("error","timeout");
                    return result;
                } else {
                    StreamInfo streamInfoNow = storager.queryPlayByDevice(serial, code);
                    logger.debug("正在向流媒体查询");
                    if (streamInfoNow != null && streamInfoNow.getFlv() != null) {
                        streamInfo = streamInfoNow;
                        logger.debug("向流媒体查询到: " + streamInfoNow.getRtsp());
                        lockFlag = false;
                        continue;
                    } else {
                        Thread.sleep(2000);
                        continue;
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if(streamInfo!=null) {
        play.setResultHandler((Object o)->{
            ResponseEntity<String> responseEntity = (ResponseEntity)o;
            StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class);
            JSONObject result = new JSONObject();
            result.put("StreamID", streamInfo.getSsrc());
            result.put("StreamID", streamInfo.getStreamId());
            result.put("DeviceID", device.getDeviceId());
            result.put("ChannelID", code);
            result.put("ChannelName", deviceChannel.getName());
@@ -203,13 +138,9 @@
            result.put("CascadeSize", "");
            result.put("RelaySize", "");
            result.put("ChannelPTZType", 0);
            return result;
        } else {
            logger.warn("设备预览API调用失败!");
            JSONObject result = new JSONObject();
            result.put("error","调用失败");
            return result;
        }
            resultDeferredResult.setResult(result);
        });
        return resultDeferredResult;
    }
    /**
@@ -228,14 +159,15 @@
                             @RequestParam(required = false)String check_outputs
    ){
        StreamInfo streamInfo = storager.queryPlayByDevice(serial, code);
        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code);
        if (streamInfo == null) {
            JSONObject result = new JSONObject();
            result.put("error","未找到流信息");
            return result;
        }
        cmder.streamByeCmd(streamInfo.getSsrc());
        storager.stopPlay(streamInfo);
        cmder.streamByeCmd(streamInfo.getStreamId());
        redisCatchStorage.stopPlay(streamInfo);
        return null;
    }
src/main/resources/application-dev.yml
@@ -20,12 +20,20 @@
        timeout: 10000
    # [不可用] jdbc数据库配置, 暂不支持
    datasource:
        #        name: eiot
        #        url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
        #        username:
        #        password:
        #        type: com.alibaba.druid.pool.DruidDataSource
        #        driver-class-name: com.mysql.jdbc.Driver
        name: eiot
        url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
        url: jdbc:sqlite::resource:wvp.sqlite
        username:
        password:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        driver-class-name:  org.sqlite.JDBC
        max-active: 1
        min-idle: 1
# [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
server:
@@ -34,18 +42,18 @@
# 作为28181服务器的配置
sip:
    # [必须修改] 本机的IP, 必须是网卡上的IP
    ip: 192.168.0.100
    ip: 192.168.1.44
    # [可选] 28181服务监听的端口
    port: 5060
    # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
    # 后两位为行业编码,定义参照附录D.3
    # 3701020049标识山东济南历下区 信息行业接入
    # [可选]
    domain: 4401020049
    domain: 3402000000
    # [可选]
    id: 44010200492000000001
    id: 34020000002000000001
    # [可选] 默认设备认证密码,后续扩展使用设备单独密码
    password: admin123
    password: 12345678
# 登陆的用户名密码
auth:
@@ -57,7 +65,7 @@
#zlm服务器配置
media:
    # [必须修改] zlm服务器的内网IP
    ip: 192.168.0.100
    ip: 192.168.1.44
    # [可选] zlm服务器的公网IP, 内网部署置空即可
    wanIp:
    # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip
@@ -69,12 +77,12 @@
    # [可选] zlm服务器的hook.admin_params=secret
    secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
    # [可选] zlm服务器的general.streamNoneReaderDelayMS
    streamNoneReaderDelayMS:  18000  # 无人观看多久自动关闭流
    # [可选] 关闭等待收到流编码信息后在返回,
    # 设为false可以获得更好的兼容性,保证返回后流就可以播放,
    # 设为true可以快速打开播放窗口,可以获得更好的体验
    closeWaitRTPInfo: false
    # 启用udp多端口模式
    streamNoneReaderDelayMS:  600000  # 无人观看多久自动关闭流, -1表示永不自动关闭,即 关闭按需拉流
    # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true
    autoApplyPlay: true
    # [可选] 部分设备需要扩展SDP,需要打开此设置
    seniorSdp: false
    # 启用udp多端口模式, 详细解释参考: https://github.com/xia-chu/ZLMediaKit/wiki/GB28181%E6%8E%A8%E6%B5%81 下的高阶使用
    rtp:
        # [可选] 是否启用udp多端口模式, 开启后会在udpPortRange范围内选择端口用于媒体流传输
        enable: true
src/main/resources/wvp.sqlite
Binary files differ
web_src/src/components/channelList.vue
@@ -19,12 +19,12 @@
                    <el-option label="设备" value="false"></el-option>
                    <el-option label="子目录" value="true"></el-option>
                </el-select>
                在线状态: <el-select size="mini" @change="search" v-model="online" placeholder="请选择" default-first-option>
                在线状态: <el-select size="mini" style="margin-right: 1rem;" @change="search" v-model="online" placeholder="请选择" default-first-option>
                    <el-option label="全部" value=""></el-option>
                    <el-option label="在线" value="on"></el-option>
                    <el-option label="离线" value="off"></el-option>
                    <el-option label="在线" value="true"></el-option>
                    <el-option label="离线" value="false"></el-option>
                </el-select>
                <el-checkbox size="mini" style="margin-right: 1rem; float: right;" v-model="autoList" @change="autoListChange">自动刷新</el-checkbox>
            </div>
            <devicePlayer ref="devicePlayer" v-loading="isLoging"></devicePlayer>
            <!--设备列表-->
@@ -56,7 +56,7 @@
                        <el-button-group>
                            <!-- <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> -->
                            <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button>
                            <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button>
                            <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="!!scope.row.streamId" @click="stopDevicePush(scope.row)">停止</el-button>
                            <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看</el-button>
                            <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录象</el-button>
                            <!--                             <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
@@ -98,13 +98,17 @@
            count: parseInt(this.$route.params.count),
            total: 0,
            beforeUrl: "/videoList",
            isLoging: false
            isLoging: false,
            autoList: false
        };
    },
    mounted() {
        this.initData();
        this.updateLooper = setInterval(this.initData, 10000);
        if (this.autoList) {
            this.updateLooper = setInterval(this.initData, 1500);
        }
    },
    destroyed() {
        this.$destroy('videojs');
@@ -161,7 +165,7 @@
                .then(function (res) {
                    console.log(res);
                    that.total = res.data.total;
                    that.deviceChannelList = res.data.data;
                    that.deviceChannelList = res.data.list;
                    // 防止出现表格错位
                    that.$nextTick(() => {
                        that.$refs.channelListTable.doLayout();
@@ -179,17 +183,16 @@
            let deviceId = this.deviceId;
            this.isLoging = true;
            let channelId = itemData.channelId;
            let getEncoding = itemData.hasAudio ? '1' : '0'
            console.log("通知设备推流1:" + deviceId + " : " + channelId + ":" + getEncoding);
            console.log("通知设备推流1:" + deviceId + " : " + channelId );
            let that = this;
            this.$axios({
                method: 'get',
                url: '/api/play/' + deviceId + '/' + channelId + '?getEncoding=' + getEncoding
                url: '/api/play/' + deviceId + '/' + channelId
            }).then(function (res) {
                console.log(res.data)
                let ssrc = res.data.ssrc;
                let streamId = res.data.streamId;
                that.isLoging = false;
                if (!!ssrc) {
                if (!!streamId) {
                    // that.$refs.devicePlayer.play(res.data, deviceId, channelId, itemData.hasAudio);
                    that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
                        streamInfo: res.data,
@@ -212,7 +215,7 @@
            var that = this;
            this.$axios({
                method: 'post',
                url: '/api/play/' + itemData.ssrc + '/stop'
                url: '/api/play/' + itemData.streamId + '/stop'
            }).then(function (res) {
                console.log(JSON.stringify(res));
                that.initData();
@@ -258,7 +261,7 @@
                })
                .then(function (res) {
                    that.total = res.data.total;
                    that.deviceChannelList = res.data.data;
                    that.deviceChannelList = res.data.list;
                    // 防止出现表格错位
                    that.$nextTick(() => {
                        that.$refs.channelListTable.doLayout();
@@ -283,6 +286,13 @@
            }).then(function (res) {
                console.log(JSON.stringify(res));
            });
        },
        autoListChange: function () {
            if (this.autoList) {
                this.updateLooper = setInterval(this.initData, 1500);
            }else{
                window.clearInterval(this.updateLooper);
            }
        }
    }
web_src/src/components/gb28181/devicePlayer.vue
@@ -1,6 +1,6 @@
<template>
<div id="devicePlayer" v-loading="isLoging">
    <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()">
        <!-- <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> -->
        <player ref="videoPlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></player>
@@ -121,7 +121,7 @@
                                <p>采样率: {{item.sample_rate}}</p>
                            </div>
                        </div>
                    </div>
                </el-tab-pane>
@@ -158,7 +158,6 @@
                searchHistoryResult: [] //媒体流历史记录搜索结果
            },
            showVideoDialog: false,
            ssrc: '',
            streamId: '',
            convertKey: '',
            deviceId: '',
@@ -210,7 +209,6 @@
            this.tabActiveName = tab;
            this.channelId = channelId;
            this.deviceId = deviceId;
            this.ssrc = "";
            this.streamId = "";
            this.videoUrl = ""
            if (!!this.$refs.videoPlayer) {
@@ -234,11 +232,10 @@
            console.log(val)
        },
        play: function (streamInfo, hasAudio) {
            this.hasaudio = hasAudio;
            this.isLoging = false;
            this.videoUrl = streamInfo.ws_flv;
            this.ssrc = streamInfo.ssrc;
            this.streamId = streamInfo.streamId;
            this.playFromStreamInfo(false, streamInfo)
        },
@@ -248,7 +245,7 @@
            this.$refs.videoPlayer.pause()
            that.$axios({
                method: 'post',
                url: '/api/play/' + that.ssrc + '/convert'
                url: '/api/play/' + that.streamId + '/convert'
                }).then(function (res) {
                    if (res.data.code == 0) {
                        that.convertKey = res.data.key;
@@ -317,7 +314,7 @@
            }
            this.convertKey = ''
        },
        copySharedInfo: function (data) {
            console.log('复制内容:' + data);
            this.coverPlaying = false;
@@ -368,9 +365,9 @@
        },
        playRecord: function (row) {
            let that = this;
            if (that.ssrc != "") {
            if (that.streamId != "") {
                that.stopPlayRecord(function () {
                    that.ssrc = "",
                    that.streamId = "",
                        that.playRecord(row);
                })
            } else {
@@ -380,7 +377,7 @@
                        row.endTime
                }).then(function (res) {
                    var streamInfo = res.data;
                    that.ssrc = streamInfo.ssrc;
                    that.streamId = streamInfo.streamId;
                    that.videoUrl = streamInfo.ws_flv;
                });
            }
@@ -390,7 +387,7 @@
            this.videoUrl = '';
            this.$axios({
                method: 'get',
                url: '/api/playback/' + this.ssrc + '/stop'
                url: '/api/playback/' + this.streamId + '/stop'
            }).then(function (res) {
                if (callback) callback()
            });
web_src/src/components/videoList.vue
@@ -8,7 +8,7 @@
                <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
                    <span style="font-size: 1rem; font-weight: bold;">设备列表</span>
                    <div style="position: absolute; right: 1rem; top: 0.3rem;">
                        <el-button icon="el-icon-refresh-right" circle size="mini" @click="getDeviceList()"></el-button>
                        <el-button icon="el-icon-refresh-right" circle size="mini" :loading="getDeviceListLoading" @click="getDeviceList()"></el-button>
                    </div>
                </div>
                <devicePlayer ref="devicePlayer"></devicePlayer>
@@ -21,7 +21,7 @@
                    <el-table-column label="地址" width="180" align="center">
                        <template slot-scope="scope">
                            <div slot="reference" class="name-wrapper">
                                <el-tag size="medium">{{ scope.row.host.address }}</el-tag>
                                <el-tag size="medium">{{ scope.row.hostAddress }}</el-tag>
                            </div>
                        </template>
                    </el-table-column>
@@ -51,7 +51,7 @@
                    <el-table-column label="操作" width="240" align="center" fixed="right">
                        <template slot-scope="scope">
                            <el-button size="mini" icon="el-icon-refresh"  @click="refDevice(scope.row)">刷新通道</el-button>
                            <el-button size="mini" :ref="scope.row.deviceId + 'refbtn' " icon="el-icon-refresh"  @click="refDevice(scope.row)">刷新通道</el-button>
                            <el-button size="mini" icon="el-icon-s-open"  type="primary" @click="showChannelList(scope.row)">查看通道</el-button>
                        </template>
                    </el-table-column>
@@ -90,7 +90,8 @@
                winHeight: window.innerHeight - 200,
                currentPage:1,
                count:15,
                total:0
                total:0,
                getDeviceListLoading: false
            };
        },
        computed: {
@@ -130,7 +131,7 @@
            },
            getDeviceList: function() {
                let that = this;
                this.getDeviceListLoading = true;
                this.$axios.get(`/api/devices`,{
                    params: {
                        page: that.currentPage - 1,
@@ -139,11 +140,14 @@
                } )
                .then(function (res) {
                    console.log(res);
                    console.log(res.data.list);
                    that.total = res.data.total;
                    that.deviceList = res.data.data;
                    that.deviceList = res.data.list;
                    that.getDeviceListLoading = false;
                })
                .catch(function (error) {
                    console.log(error);
                    that.getDeviceListLoading = false;
                });
            },
@@ -158,17 +162,31 @@
            refDevice: function(itemData) {
                ///api/devices/{deviceId}/sync
                console.log("刷新对应设备:" + itemData.deviceId);
                var that = this;
                that.$refs[itemData.deviceId + 'refbtn' ].loading = true;
                this.$axios({
                    method: 'post',
                    url: '/api/devices/' + itemData.deviceId + '/sync'
                }).then(function(res) {
                    // console.log("刷新设备结果:"+JSON.stringify(res));
                    console.log("刷新设备结果:"+JSON.stringify(res));
                    if (!res.data.deviceId) {
                        that.$message({
                            showClose: true,
                            message: res.data,
                            type: 'error'
                        });
                    }else{
                        that.$message({
                            showClose: true,
                            message: '请求成功',
                            type: 'success'
                        });
                    }
                    that.initData()
                    that.$refs[itemData.deviceId + 'refbtn' ].loading = false;
                }).catch(function(e) {
                    that.$message({
                        showClose: true,
                        message: '请求成功',
                        type: 'success'
                    });
                    console.error(e)
                    that.$refs[itemData.deviceId + 'refbtn' ].loading = false;
                });;
            },
            //通知设备上传媒体流