diff --git a/sip/pom.xml b/sip/pom.xml
new file mode 100644
index 0000000..7e62790
--- /dev/null
+++ b/sip/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+
+ com.dite.znpt
+ parent
+ 1.0.0-SNAPSHOT
+
+ sip
+ 1.0.0-SNAPSHOT
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+ com.dite.znpt
+ core
+ 1.0.0-SNAPSHOT
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+ javax.sip
+ jain-sip-ri
+ 1.3.0-91
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ org.slf4j
+ log4j-over-slf4j
+ 1.7.36
+
+
+
+
\ No newline at end of file
diff --git a/sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java b/sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java
new file mode 100644
index 0000000..2c0dd0c
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java
@@ -0,0 +1,30 @@
+package com.dite.znpt.monitor.config;
+
+import cn.hutool.core.collection.CollUtil;
+import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * 媒体格式配置
+ *
+ * @author huise23
+ * @since 2023-07-31 09:08:05
+ */
+@Configuration
+public class MediaFormatConfig {
+
+ @Bean
+ public static List streamMediaFormatList() {
+ List formatList = CollUtil.newArrayList();
+ formatList.add(new StreamMediaFormat("flv",null,"1"));
+ formatList.add(new StreamMediaFormat("mp4",null,"0"));
+ formatList.add(new StreamMediaFormat("hls",null,"0"));
+ formatList.add(new StreamMediaFormat("webrtc",null,"1"));
+ return formatList;
+ }
+
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java b/sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java
new file mode 100644
index 0000000..d1ee938
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java
@@ -0,0 +1,37 @@
+package com.dite.znpt.monitor.constant;
+
+/**
+ * @author yunp
+ * @since 2022/7/14
+ */
+public class Constants {
+ /**
+ * UTF-8 字符集
+ */
+ public static final String UTF8 = "UTF-8";
+
+ /**
+ * GBK 字符集
+ */
+ public static final String GBK = "GBK";
+
+ /**
+ * 路径拼接符 /
+ */
+ public static final String File_SEPARATOR = "/";
+
+ /**
+ * http请求
+ */
+ public static final String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ public static final String HTTPS = "https://";
+
+ /**
+ * 默认字符串分隔符
+ */
+ public static final String DEFAULT_DELIMITER = ",";
+}
\ No newline at end of file
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java b/sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java
new file mode 100644
index 0000000..743494a
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java
@@ -0,0 +1,37 @@
+package com.dite.znpt.monitor.constant;
+
+/**
+ * @author yunp
+ * @since 2022/8/4
+ * @description 缓存key定义:key全部以iot开头
+ */
+public class IotCacheConstants {
+
+ /**
+ * 参数管理 cache key
+ */
+ public static final String SYS_CONFIG_KEY = "sys_config:";
+
+ /**
+ * 字典管理 cache key
+ */
+ public static final String SYS_DICT_KEY = "sys_dict:";
+
+ /**
+ * 图标库 cache key
+ */
+ public static final String SYS_ICON_KEY = "sys_icon";
+
+ private static final String IOT_DEVICE_VIDEO_PREFIX = "vs_device_video:";
+
+ public static String getIotDeviceVideoKey(String deviceCode){
+ return IOT_DEVICE_VIDEO_PREFIX + deviceCode;
+ }
+
+ private final static String CLIENT_TRANSACTION_CACHE_PREFIX = "IOT_CLIENT_TRANSACTION_CACHE:";
+
+ public static String getClientTransactionCacheKey(String ssrc){
+ return CLIENT_TRANSACTION_CACHE_PREFIX + ssrc;
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java b/sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java
new file mode 100644
index 0000000..4e3d10d
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java
@@ -0,0 +1,20 @@
+package com.dite.znpt.monitor.constant;
+
+/**
+ * @author yunp
+ * @since 2022/8/4
+ * @description 字典类型定义
+ */
+public class IotDictConstants {
+
+ /**
+ * 设备状态-在线
+ */
+ public static final String IOT_DEVICE_STATUS_ONLINE = "2";
+
+ /**
+ * 设备状态-离线
+ */
+ public static final String IOT_DEVICE_STATUS_OFFLINE = "3";
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java b/sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java
new file mode 100644
index 0000000..7cdf9eb
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java
@@ -0,0 +1,113 @@
+package com.dite.znpt.monitor.constant;
+
+/**
+ * @author yunp
+ * @since 2022/8/4
+ * @description 响应文案定义
+ */
+public class IotRespMessage {
+
+ public static final String UNKNOWN_FAIL = "未知错误";
+
+ public static final String ID_NOT_FOUND = "数据不存在";
+
+ public static final String PARAMETER_ERROR = "参数有误";
+
+ public static final String NO_PERMISSION = "没有访问权限";
+
+ public static final String PARENT_AREA_NOT_FOUND = "上级分区不存在";
+
+ public static final String CAR_TYPE_HAS_CARS = "该车型下仍有车辆,无法删除";
+
+ public static final String CAR_BRAND_HAS_CARS = "该品牌下仍有车辆,无法删除";
+
+ public static final String CAR_PLATE_NUMBER_EXIST = "该车牌号车辆已存在";
+
+ public static final String CAR_PLATE_NUMBER_NOT_EXIST = "该车牌号车辆不存在";
+
+ public static final String CAR_HAS_BIND_TAG_OR_SPEED_MONITOR = "车辆已绑定车载标签或速度检测仪,禁止删除";
+
+ public static final String CAR_FREIGHT_NOT_EXIST = "货物不存在";
+
+ public static final String CAR_HAS_BIND_ALARM_TEMPLATE = "该车辆已绑定其他告警模板";
+
+ public static final String USER_HAS_BIND_ALARM_TEMPLATE = "该人员已绑定其他告警模板";
+
+ public static final String REPETITION_ALARM_FOR_AREA = "该厂区分区下已有同类型告警,无法重复添加";
+
+ public static final String REPETITION_ALARM_NOTIFY_CONFIG = "已存在相同部门层级的告警消息推送配置";
+
+ public static final String DEVICE_TERMINAL_HAS_BEEN_BIND = "该标签卡已被其他人绑定";
+
+ public static final String DEVICE_TERMINAL_TYPE_ERROR = "标签卡类型有误";
+
+ public static final String DEVICE_TERMINAL_NOT_FOUND_OR_STATUS_ERROR = "标签卡未找到或状态异常";
+
+ public static final String DEVICE_CANNOT_EDIT = "设备未启用或已停用才可编辑";
+
+ public static final String VIDEO_DEVICE_CANNOT_DELETE = "视频设备禁止删除";
+
+ public static final String DEVICE_CANNOT_DELETE = "未启用的设备才可删除";
+
+ public static final String DEVICE_HAS_BIND_TO_GROUP = "设备已经绑定至该分组";
+
+ public static final String DEVICE_VIDEO_CANNOT_DELETE = "禁止删除在线视频设备";
+
+ public static final String DEVICE_VIDEO_CANNOT_SYNC = "禁止更新离线视频设备";
+
+ public static final String DEVICE_INACTIVE = "设备未启用";
+
+ public static final String CODE_HAS_BEEN_USED = "编号已被占用";
+
+ public static final String NAME_HAS_BEEN_USED = "名称已被占用";
+
+ public static final String FLAG_HAS_BEEN_USED = "标识已被占用";
+
+ public static final String GROUP_HAS_CHILD_CANNOT_REMOVE = "分组有下级分组,无法删除";
+
+ public static final String GROUP_HAS_DEVICE_CANNOT_REMOVE = "分组下有绑定设备,无法删除";
+
+ public static final String PRODUCT_PUBLISH_CANNOT_DELETE = "该产品已发布,不可删除";
+
+ public static final String PRODUCT_HAS_DEVICE_CANNOT_REMOVE = "产品下存在设备关联,需删除设备后进行操作";
+
+ public static final String MSG_PROTOCOL_PUBLISH_CANNOT_DELETE = "消息协议未发布,发布协议后操作";
+
+ public static final String CATEGORY_CANNOT_DELETE = "该产品分类信息已被产品关联,不可删除";
+
+ public static final String SCENE_CANNOT_DELETE = "该场景信息已被产品关联,不可删除";
+
+ public static final String SWITCH_PARAM_HAS_BEEN_USED = "该参数在设备服务中只能定义一个";
+
+ public static final String CONFIG_CANNOT_DELETE = "该配置已被通知模板关联,不可删除, 请取消关联后重试。";
+
+ public static final String TEMPLATE_CANNOT_DELETE = "该通知模板已被场景联动关联,不可删除, 请取消关联后操作。";
+
+ public static final String PROTOCOL_CANNOT_DELETE_BY_PUBLISHED = "当前协议状态为已发布,不可删除.";
+
+ public static final String PROTOCOL_CANNOT_DELETE_WITH_PRODUCT = "该协议已被产品关联,不可删除,请删除关联后操作!";
+
+ public static final String PROTOCOL_CANNOT_UN_PUBLISH = "协议已被产品发布,不可取消!";
+
+ public static final String PROTOCOL_TYPE_CANNOT_NULL = "协议类型不能为空";
+
+ public static final String PROTOCOL_CLASS_NAME_CANNOT_EMPTY = "协议类型为Jar或者Local类型时,必须指定协议类名.";
+
+ public static final String PROTOCOL_FILE_PATH_CANNOT_EMPTY = "协议类型为Jar或者Local类型时,必须上传或者指定协议文件路径.";
+
+ public static final String DATA_ILLEGAL = "数据非法";
+
+ public static final String IMPORT_ERROR = "导入出错";
+
+ public static final String COMMON_DEVICE_ATTR_DUPLICATE_ERROR = "订阅的通用设备点位全局不唯一";
+
+ public static final String PROTOCOL_NOT_EXISTS = "协议不存在";
+
+ public static final String RULE_NOT_EXISTS = "上报规则不存在";
+
+ public static final String DEVICE_NOT_EXISTS = "设备不存在";
+
+ public static final String DATA_DATA_TREND_TIME_TYPE_NOT_EXISTS = "设备数据趋势时间类型不存在";
+ public static final String DATA_DATA_TREND_DATA_TYPE_NOT_EXISTS = "设备数据趋势数据类型不存在";
+ public static final String CRON_ERROR = "cron表达式输入错误:";
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java
new file mode 100644
index 0000000..a05e191
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java
@@ -0,0 +1,32 @@
+package com.dite.znpt.monitor.constant.dict;
+
+/**
+ * 摄像头类型
+ *
+ * @author huise23
+ * @since 2023-07-28 15:30:10
+ */
+public enum CameraType implements ValueAndLabel {
+
+ UNKNOWN("0","未知"),
+ BALLHEAD("1","球机");
+
+ private final String value;
+ private final String label;
+
+ CameraType(String value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java
new file mode 100644
index 0000000..2101f2b
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java
@@ -0,0 +1,34 @@
+package com.dite.znpt.monitor.constant.dict;
+
+/**
+ * 设备状态
+ *
+ * @author huise23
+ * @since 2023-07-28 15:30:10
+ */
+public enum DeviceStatus implements ValueAndLabel {
+
+ INACTIV("1", "未启用"),
+ ONLINE("2", "在线"),
+ OFFLINE("3", "离线"),
+ STOP("4", "停用");
+
+ private final String value;
+ private final String label;
+
+ DeviceStatus(String value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java
new file mode 100644
index 0000000..e621c06
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java
@@ -0,0 +1,26 @@
+package com.dite.znpt.monitor.constant.dict;
+
+public enum SipTransferMode implements ValueAndLabel {
+
+ UDP("UDP","UDP"),
+ TCP("TCP","TCP");
+
+ private final String value;
+ private final String label;
+
+ SipTransferMode(String value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java
new file mode 100644
index 0000000..fea526a
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java
@@ -0,0 +1,27 @@
+package com.dite.znpt.monitor.constant.dict;
+
+public enum StreamTransferMode implements ValueAndLabel {
+
+ UDP("UDP","UDP"),
+ TCP_ACTIVE("TCP-ACTIVE","TCP主动"),
+ TCP_PASSIVE("TCP-PASSIVE", "TCP被动");
+
+ private final String value;
+ private final String label;
+
+ StreamTransferMode(String value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java
new file mode 100644
index 0000000..88b5b1b
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.constant.dict;
+
+
+/**
+ * 字典常量接口
+ *
+ * @author huise23
+ * @since 2023-07-28 15:28:55
+ */
+public interface ValueAndLabel {
+ String getValue();
+ String getLabel();
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java
new file mode 100644
index 0000000..62889fd
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java
@@ -0,0 +1,32 @@
+package com.dite.znpt.monitor.constant.dict;
+
+/**
+ * YesOrNo
+ *
+ * @author huise23
+ * @since 2023-07-28 15:30:10
+ */
+public enum YesOrNo implements ValueAndLabel {
+
+ YES("Y","是"),
+ NO("N", "否");
+
+ private final String value;
+ private final String label;
+
+ YesOrNo(String value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java b/sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java
new file mode 100644
index 0000000..bf145cc
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.domain;
+
+/**
+ * @Author: cuizhibin
+ * @Date: 2023/1/16 14:36:36
+ * @Description:
+ */
+public interface CustomFunction {
+ /**
+ * 执行的方法
+ * @return
+ */
+ T get();
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java
new file mode 100644
index 0000000..4b263aa
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java
@@ -0,0 +1,124 @@
+package com.dite.znpt.monitor.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 17:29
+ * @Description:
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("vs_device_video_channel")
+@ApiModel(value="DeviceVideoChannelEntity", description="视频通道表")
+public class DeviceVideoChannelEntity implements Serializable {
+
+ private static final long serialVersionUID = -4175177624487756818L;
+
+ @ApiModelProperty(value = "主键id")
+ @TableId(value = "channel_id", type = IdType.AUTO)
+ private Long channelId;
+
+ @ApiModelProperty(value = "视频设备id")
+ private Long videoId;
+
+ @ApiModelProperty(value = "通道国标编号")
+ private String channelCode;
+
+ @ApiModelProperty(value = "通道名")
+ private String channelName;
+
+ @ApiModelProperty(value = "生产厂商")
+ private String manufacture;
+
+ @ApiModelProperty(value = "型号")
+ private String model;
+
+ @ApiModelProperty(value = "设备归属")
+ private String owner;
+
+ @ApiModelProperty(value = "行政区域")
+ private String civilCode;
+
+ @ApiModelProperty(value = "警区")
+ private String block;
+
+ @ApiModelProperty(value = "安装位置")
+ private String address;
+
+ @ApiModelProperty(value = "是否有子设备 1有, 0没有")
+ private int parental;
+
+ @ApiModelProperty(value = "父级id")
+ private String parentId;
+
+ @ApiModelProperty(value = "信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式")
+ private int safetyWay;
+
+ @ApiModelProperty(value = "注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式")
+ private int registerWay;
+
+ @ApiModelProperty("证书序列号")
+ private String certNum;
+
+ @ApiModelProperty("证书有效标识 缺省为0;证书有效标识:0:无效1: 有效")
+ private int certifiable;
+
+ @ApiModelProperty("证书无效原因码")
+ private int errCode;
+
+ @ApiModelProperty( "证书终止有效期")
+ private String endTime;
+
+ @ApiModelProperty("保密属性 缺省为0; 0:不涉密, 1:涉密")
+ private String secrecy;
+
+ @ApiModelProperty("IP地址")
+ private String ipAddress;
+
+ @ApiModelProperty("端口号")
+ private int port;
+
+ @ApiModelProperty("密码")
+ private String password;
+
+ @ApiModelProperty("摄像头类型")
+ private String cameraType;
+
+ @ApiModelProperty("云台控制")
+ private String ptzControl;
+
+ @ApiModelProperty(value = "状态")
+ private String status;
+
+ @ApiModelProperty("经度")
+ private double longitude;
+
+ @ApiModelProperty("纬度")
+ private double latitude;
+
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+ @ApiModelProperty("创建时间")
+ private LocalDateTime createTime;
+
+ @ApiModelProperty("创建人id")
+ @TableField(fill = FieldFill.INSERT)
+ private Long createBy;
+
+ @ApiModelProperty("更新时间")
+ private LocalDateTime updateTime;
+
+ @ApiModelProperty("更新人id")
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Long updateBy;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java
new file mode 100644
index 0000000..9b35a56
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java
@@ -0,0 +1,96 @@
+package com.dite.znpt.monitor.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 10:24
+ * @Description:
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("vs_device_video")
+@ApiModel(value="DeviceVideoEntity", description="视频设备表")
+public class DeviceVideoEntity implements Serializable {
+
+ private static final long serialVersionUID = -182441901641147882L;
+
+ @ApiModelProperty(value = "主键id")
+ @TableId(value = "video_id", type = IdType.AUTO)
+ private Long videoId;
+
+ @ApiModelProperty(value = "视频设备国标编码")
+ private String videoCode;
+
+ @ApiModelProperty(value = "视频设备名称")
+ private String videoName;
+
+ @ApiModelProperty(value = "生产厂商")
+ private String manufacturer;
+
+ @ApiModelProperty(value = "型号")
+ private String model;
+
+ @ApiModelProperty(value = "固件版本")
+ private String firmware;
+
+ @ApiModelProperty(value = "传输协议(UDP/TCP),默认UDP")
+ private String transport;
+
+ @ApiModelProperty(value = "数据流传输模式(默认UDP)")
+ private String streamMode;
+
+ @ApiModelProperty(value = "设备状态")
+ private String status;
+
+ @ApiModelProperty(value = "注册时间")
+ private LocalDateTime registerTime;
+
+ @ApiModelProperty(value = "心跳时间")
+ private LocalDateTime KeepaliveTime;
+
+ @ApiModelProperty("通道个数")
+ private int channelCount;
+
+ @ApiModelProperty(value = "ip")
+ private String ip;
+
+ @ApiModelProperty(value = "端口")
+ private Integer port;
+
+ @ApiModelProperty(value = "地址")
+ private String hostAddress;
+
+ @ApiModelProperty(value = "注册有效期")
+ private Integer expires;
+
+ @ApiModelProperty(value = "符集, 支持 UTF-8 与 GB2312")
+ private String charset;
+
+ @ApiModelProperty(value = "产品id")
+ private Long productId;
+
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+ @ApiModelProperty("创建时间")
+ private LocalDateTime createTime;
+
+ @ApiModelProperty("创建人id")
+ @TableField(fill = FieldFill.INSERT)
+ private Long createBy;
+
+ @ApiModelProperty("更新时间")
+ private LocalDateTime updateTime;
+
+ @ApiModelProperty("更新人id")
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Long updateBy;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java
new file mode 100644
index 0000000..04c67b4
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java
@@ -0,0 +1,35 @@
+package com.dite.znpt.monitor.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * @Date: 2023/09/05 16:39
+ * @Description: 监控设备IP配置表实体类
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("vs_ip_config")
+@ApiModel(value="VsIpConfigEntity对象", description="监控设备IP配置表")
+public class IpConfigEntity implements Serializable {
+
+ @ApiModelProperty("${column.comment}")
+ @TableId(type = IdType.AUTO)
+ private Long configId;
+
+ @ApiModelProperty("ip地址")
+ private String ip;
+
+ @ApiModelProperty("ip地址前三位")
+ private String ipTopThree;
+
+
+}
+
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java
new file mode 100644
index 0000000..be2a92e
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.domain.req;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Date:2023/9/5 16:19
+ * @Description: 视频服务配置新增对象
+ */
+@Data
+public class MonitorConfigAddReq {
+ private List ipAddresses;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java
new file mode 100644
index 0000000..0dc809c
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java
@@ -0,0 +1,23 @@
+package com.dite.znpt.monitor.domain.req;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 查询视频信息参数
+ *
+ * @author huise23
+ * @since 2024-12-03 14:03:29
+ */
+@Data
+public class VideoInfoReq implements Serializable {
+
+ @ApiModelProperty(value = "视频对接方式 1.摄像头直连 2.级联")
+ private Integer videoConnection;
+
+ @ApiModelProperty(value = "级联分隔符(默认/)")
+ private String cascadeSeparator = "/";
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java
new file mode 100644
index 0000000..f8a76ae
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java
@@ -0,0 +1,19 @@
+package com.dite.znpt.monitor.domain.resp;
+
+import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @Date:2023/9/7 10:26
+ * @Description:
+ */
+@Data
+public class DeviceVideoResp extends DeviceVideoEntity {
+ @ApiModelProperty(value = "设备状态label")
+ private String statusLabel;
+ @ApiModelProperty(value = "流传输模式label")
+ private String streamModeLabel;
+ @ApiModelProperty(value = "传输模式label")
+ private String transportLabel;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java
new file mode 100644
index 0000000..7ba594c
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java
@@ -0,0 +1,58 @@
+package com.dite.znpt.monitor.domain.resp;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 查询视频信息返回信息
+ *
+ * @author huise23
+ * @since 2024-12-03 14:03:29
+ */
+@Data
+public class VideoInfoResp implements Serializable {
+
+ @ApiModelProperty(value = "视频设备id")
+ private Long videoId;
+
+ @ApiModelProperty(value = "视频设备名称")
+ private String videoName;
+
+ @ApiModelProperty(value = "通道id")
+ private Long channelId;
+
+ @ApiModelProperty(value = "国标编码")
+ private String channelCode;
+
+ @ApiModelProperty(value = "通道名称")
+ private String channelName;
+
+ @ApiModelProperty(value = "生产厂商")
+ private String manufacture;
+
+ @ApiModelProperty(value = "型号")
+ private String model;
+
+ @ApiModelProperty(value = "安装位置")
+ private String address;
+
+ @ApiModelProperty("IP地址")
+ private String ipAddress;
+
+ @ApiModelProperty("端口号")
+ private int port;
+
+ @ApiModelProperty(value = "云台控制label")
+ private String ptzControl;
+
+ @ApiModelProperty("经度")
+ private double longitude;
+
+ @ApiModelProperty("纬度")
+ private double latitude;
+
+ @ApiModelProperty(value = "状态 DeviceStatus")
+ private String status;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java
new file mode 100644
index 0000000..1ec735a
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java
@@ -0,0 +1,34 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:12
+ * @Description:
+ */
+@Data
+@ApiModel("视频通道编辑请求")
+public class DeviceVideoChannelEditReq implements Serializable {
+
+ private static final long serialVersionUID = 719557164910393807L;
+
+ @ApiModelProperty(value = "通道名称")
+ private String channelName;
+
+ @ApiModelProperty(value = "安装位置")
+ private String address;
+
+ @ApiModelProperty(value = "摄像头类型")
+ private String cameraType;
+
+ @ApiModelProperty(value = "云台控制,Y表示是,N表示否")
+ private String ptzControl;
+
+ @ApiModelProperty(value = "描述")
+ private String remark;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java
new file mode 100644
index 0000000..31234fb
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java
@@ -0,0 +1,55 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:12
+ * @Description:
+ */
+@Data
+@ApiModel("视频通道列表响应")
+public class DeviceVideoChannelListResp implements Serializable {
+
+ private static final long serialVersionUID = -8053965410352257803L;
+
+ @ApiModelProperty(value = "主键id")
+ private Long channelId;
+
+ @ApiModelProperty(value = "所属产品id")
+ private Long productId;
+
+ @ApiModelProperty(value = "通道国标编码")
+ private String channelCode;
+
+ @ApiModelProperty(value = "通道名称")
+ private String channelName;
+
+ @ApiModelProperty(value = "安装位置")
+ private String address;
+
+ @ApiModelProperty(value = "摄像头类型label")
+ private String cameraType;
+
+ @ApiModelProperty(value = "摄像头类型")
+ private String cameraTypeLabel;
+
+ @ApiModelProperty(value = "云台控制label")
+ private String ptzControl;
+
+ @ApiModelProperty(value = "云台控制,Y表示是,N表示否")
+ private String ptzControlLabel;
+
+ @ApiModelProperty(value = "状态")
+ private String status;
+
+ @ApiModelProperty(value = "状态label")
+ private String statusLabel;
+
+ @ApiModelProperty(value = "描述")
+ private String remark;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java
new file mode 100644
index 0000000..1f22085
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java
@@ -0,0 +1,43 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:12
+ * @Description:
+ */
+@Data
+@ApiModel("视频通道响应")
+public class DeviceVideoChannelResp implements Serializable {
+
+ private static final long serialVersionUID = 1140851083577845760L;
+
+ @ApiModelProperty(value = "主键id")
+ private Long channelId;
+
+ @ApiModelProperty(value = "通道国标编码")
+ private String channelCode;
+
+ @ApiModelProperty(value = "通道名称")
+ private String channelName;
+
+ @ApiModelProperty(value = "安装位置")
+ private String address;
+
+ @ApiModelProperty(value = "摄像头类型")
+ private String cameraType;
+
+ @ApiModelProperty(value = "云台控制,Y表示是,N表示否")
+ private String ptzControl;
+
+ @ApiModelProperty(value = "状态")
+ private String status;
+
+ @ApiModelProperty(value = "描述")
+ private String remark;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java
new file mode 100644
index 0000000..a1f2b8e
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java
@@ -0,0 +1,29 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:13
+ * @Description:
+ */
+@Data
+@ApiModel("视频设备编辑请求参数")
+public class DeviceVideoEditReq implements Serializable {
+
+ private static final long serialVersionUID = -3387666090991548317L;
+
+ @ApiModelProperty(value = "设备名称")
+ private String videoName;
+
+ @ApiModelProperty(value = "所属产品")
+ private Long productId;
+
+ @ApiModelProperty(value = "说明")
+ private String remark;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java
new file mode 100644
index 0000000..026f873
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java
@@ -0,0 +1,74 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:13
+ * @Description:
+ */
+@Data
+@ApiModel("视频设备列表响应")
+public class DeviceVideoListResp implements Serializable {
+
+ private static final long serialVersionUID = -5568664011265192343L;
+
+ @ApiModelProperty(value = "主键id")
+ private Long videoId;
+
+ @ApiModelProperty(value = "视频设备国标编码")
+ private String videoCode;
+
+ @ApiModelProperty(value = "视频设备名称")
+ private String videoName;
+
+ @ApiModelProperty(value = "传输模式")
+ private String transport;
+
+ @ApiModelProperty(value = "传输模式label")
+ private String transportLabel;
+
+ @ApiModelProperty(value = "流传输模式")
+ private String streamMode;
+
+ @ApiModelProperty(value = "流传输模式label")
+ private String streamModeLabel;
+
+ @ApiModelProperty(value = "通道数量")
+ private Integer channelCount;
+
+ @ApiModelProperty(value = "设备状态")
+ private String status;
+
+ @ApiModelProperty(value = "设备状态label")
+ private String statusLabel;
+
+ @ApiModelProperty(value = "设备ip")
+ private String ip;
+
+ @ApiModelProperty(value = "设备端口")
+ private String port;
+
+ @ApiModelProperty(value = "设备地址(ip+端口)")
+ private String hostAddress;
+
+ @ApiModelProperty(value = "生产厂商")
+ private String manufacturer;
+
+ @ApiModelProperty(value = "备注")
+ private String remark;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "创建时间")
+ private LocalDateTime createTime;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "心跳时间")
+ private LocalDateTime keepAliveTime;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java
new file mode 100644
index 0000000..f56b8c1
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java
@@ -0,0 +1,24 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/16 9:34
+ * @Description:
+ */
+@Data
+@ApiModel("视频设备数量响应")
+public class DeviceVideoNumResp {
+
+ @ApiModelProperty(value = "设备总数量")
+ private Long allDevice;
+
+ @ApiModelProperty(value = "设备在线数量")
+ private Long onlineDevice;
+
+ @ApiModelProperty(value = "设备离线数量")
+ private Long offlineDevice;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java
new file mode 100644
index 0000000..5c5f457
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java
@@ -0,0 +1,77 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
+import com.dite.znpt.monitor.media.zlm.enums.MediaFormatType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 10:25
+ * @Description:
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@ApiModel(value = "StreamMediaFormat对象", description = "流媒体格式")
+public class StreamMediaFormat implements Serializable {
+
+ private static final long serialVersionUID = -4177962876536716643L;
+
+ @ApiModelProperty(value = "流媒体格式")
+ private String mediaFormat;
+
+ @ApiModelProperty(value = "端口")
+ private Integer port;
+
+ @ApiModelProperty(value = "是否开启tls,1表示ture,0表示false")
+ private String openTls;
+
+ @TableField(exist = false)
+ @ApiModelProperty(value = "WebSocket播放地址")
+ private String wsUrl;
+
+ @TableField(exist = false)
+ @ApiModelProperty(value = "Http播放地址")
+ private String httpUrl;
+
+ @TableField(exist = false)
+ @ApiModelProperty(value = "WebSocket播放地址")
+ private String wssUrl;
+
+ @TableField(exist = false)
+ @ApiModelProperty(value = "Http播放地址")
+ private String httpsUrl;
+
+ @TableField(exist = false)
+ @ApiModelProperty(value = "相对播放地址")
+ private String relativePath;
+
+ public StreamMediaFormat(String mediaFormat,Integer port,String openTls){
+ this.mediaFormat = mediaFormat;
+ this.port = port;
+ this.openTls = openTls;
+ }
+
+ public void generateUrl(String host, String streamId, ServerConfig config, String mediaRouter) {
+ if("webrtc".equals(this.mediaFormat)){
+ this.httpUrl = StrUtil.format("http://{}:{}/index/api/webrtc?app=rtp&stream={}&type=play", host, config.getHttpPort(), streamId);
+ this.httpsUrl = StrUtil.format("https://{}:{}/index/api/webrtc?app=rtp&stream={}&type=play", host, config.getHttpSslPort(), streamId);
+ this.relativePath = StrUtil.format("{}/index/api/webrtc?app=rtp&stream={}&type=play", mediaRouter, streamId);
+ return;
+ }
+ String suffix = MediaFormatType.getSuffix(this.mediaFormat);
+ if (config.getHttpSslPort() != null && config.getHttpSslPort() > 0) {
+ this.wssUrl = StrUtil.format("wss://{}:{}/rtp/{}{}", host, config.getHttpSslPort(), streamId, suffix);
+ this.httpsUrl = StrUtil.format("https://{}:{}/rtp/{}{}", host, config.getHttpSslPort(), streamId, suffix);
+ }
+ this.wsUrl = StrUtil.format("ws://{}:{}/rtp/{}{}", host, config.getHttpPort(), streamId, suffix);
+ this.httpUrl = StrUtil.format("http://{}:{}/rtp/{}{}", host, config.getHttpPort(), streamId, suffix);
+ this.relativePath = StrUtil.format("{}/rtp/{}{}", mediaRouter, streamId, suffix);
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java
new file mode 100644
index 0000000..d3ce278
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java
@@ -0,0 +1,28 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 14:40
+ * @Description:
+ */
+@Data
+@ApiModel("流媒体格式请求")
+public class StreamMediaFormatReq implements Serializable {
+
+ private static final long serialVersionUID = 6627383994019834279L;
+
+ @ApiModelProperty(value = "流媒体格式")
+ private String mediaFormat;
+
+ @ApiModelProperty(value = "端口")
+ private Integer port;
+
+ @ApiModelProperty(value = "是否开启TLS,1表示true,0表示false")
+ private String openTls;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java
new file mode 100644
index 0000000..ad9304b
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java
@@ -0,0 +1,31 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 14:40
+ * @Description:
+ */
+@Data
+@ApiModel("流媒体格式响应")
+public class StreamMediaFormatResp implements Serializable {
+
+ private static final long serialVersionUID = -5714327034173930078L;
+
+ @ApiModelProperty(value = "流媒体格式主键")
+ private Long formatId;
+
+ @ApiModelProperty(value = "流媒体格式")
+ private String mediaFormat;
+
+ @ApiModelProperty(value = "端口")
+ private Integer port;
+
+ @ApiModelProperty(value = "是否开启TLS,1表示true,0表示false")
+ private String openTls;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java
new file mode 100644
index 0000000..36c15fd
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java
@@ -0,0 +1,59 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 14:16
+ * @Description:
+ */
+@Data
+@ApiModel("流媒体服务配置请求")
+public class StreamMediaServerConfigReq implements Serializable {
+
+ private static final long serialVersionUID = -1228005085084886474L;
+
+ @NotNull(message = "流媒体名称不能为空")
+ @ApiModelProperty(value = "流媒体名称")
+ private String mediaName;
+
+ @ApiModelProperty(value = "流媒体服务")
+ private String mediaService;
+
+ @ApiModelProperty(value = "公网 HOST")
+ private String publicHost;
+
+ @ApiModelProperty(value = "API HOST")
+ private String apiHost;
+
+ @ApiModelProperty(value = "API 端口")
+ private Integer apiPort;
+
+ @ApiModelProperty(value = "密钥")
+ private Integer secretKey;
+
+ @ApiModelProperty(value = "流ID前缀")
+ private String streamPrefix;
+
+ @ApiModelProperty(value = "RTP IP")
+ private String rtpHost;
+
+ @ApiModelProperty(value = "RTP 端口")
+ private Integer rtpPort;
+
+ @ApiModelProperty(value = "动态端口起始值")
+ private Integer dynamicPortStart;
+
+ @ApiModelProperty(value = "动态端口结束值")
+ private Integer dynamicPortEnd;
+
+ @ApiModelProperty(value = "流媒体格式")
+ private List streamMediaFormatReqList;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java
new file mode 100644
index 0000000..6e3b185
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java
@@ -0,0 +1,60 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 14:16
+ * @Description:
+ */
+@Data
+@ApiModel("流媒体服务配置响应")
+public class StreamMediaServerConfigResp implements Serializable {
+
+ private static final long serialVersionUID = 3464085768355214710L;
+
+ @ApiModelProperty(value = "'流媒体配置主键'")
+ private Long configId;
+
+ @ApiModelProperty(value = "'流媒体名称'")
+ private String mediaName;
+
+ @ApiModelProperty(value = "流媒体服务")
+ private String mediaService;
+
+ @ApiModelProperty(value = "公网 HOST")
+ private String publicHost;
+
+ @ApiModelProperty(value = "API HOST")
+ private String apiHost;
+
+ @ApiModelProperty(value = "API 端口")
+ private Integer apiPort;
+
+ @ApiModelProperty(value = "密钥")
+ private String secretKey;
+
+ @ApiModelProperty(value = "流ID前缀")
+ private String streamPrefix;
+
+ @ApiModelProperty(value = "RTP IP")
+ private String rtpHost;
+
+ @ApiModelProperty(value = "RTP 端口")
+ private Integer rtpPort;
+
+ @ApiModelProperty(value = "动态端口起始值")
+ private Integer dynamicPortStart;
+
+ @ApiModelProperty(value = "动态端口结束值")
+ private Integer dynamicPortEnd;
+
+ @ApiModelProperty(value = "流媒体格式")
+ private List streamMediaFormatRespList;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java
new file mode 100644
index 0000000..26809bc
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java
@@ -0,0 +1,24 @@
+package com.dite.znpt.monitor.domain.vo.video;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 视频播放响应
+ * @author huise23
+ * @since 2024-11-26 14:03:41
+ */
+@Data
+@Builder
+public class VideoPayResp {
+
+ @ApiModelProperty(value = "播放方式")
+ private String mediaType;
+
+ @ApiModelProperty(value = "流媒体播放地址")
+ private List streamMediaFormatList;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java
new file mode 100644
index 0000000..2c211e8
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java
@@ -0,0 +1,55 @@
+package com.dite.znpt.monitor.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity;
+import com.dite.znpt.monitor.domain.req.VideoInfoReq;
+import com.dite.znpt.monitor.domain.resp.VideoInfoResp;
+import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp;
+import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:08
+ * @Description:
+ */
+public interface DeviceVideoChannelMapper extends BaseMapper {
+
+ /**
+ * 查询视频通道列表
+ *
+ * @param videoId 视频id
+ * @param keyword 插叙条件
+ * @return {@link List< DeviceVideoChannelListResp>}
+ */
+ List selectDeviceVideoChannel(@Param("videoId") Long videoId, @Param("keyword") String keyword);
+
+ /**
+ * 查询所有视频通道列表
+ *
+ * @param keyword 插叙条件
+ * @return {@link List< DeviceVideoChannelListResp>}
+ */
+ List selectAllDeviceVideoChannel(@Param("keyword") String keyword);
+
+ /**
+ * 查询视频通道详情
+ *
+ * @param channelCode 通道code
+ * @return {@link DeviceVideoChannelResp}
+ */
+ DeviceVideoChannelResp getDeviceVideoChannelDetail(@Param("channelCode") String channelCode);
+
+ /**
+ * 查询通道及视频信息
+ *
+ * @param videoInfoReq 查询参数
+ * @return {@link VideoInfoResp }
+ * @author huise23
+ * @since 2024-12-03 13:54:52
+ */
+ List selectVideoInfoList(VideoInfoReq videoInfoReq);
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java
new file mode 100644
index 0000000..cc14697
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java
@@ -0,0 +1,24 @@
+package com.dite.znpt.monitor.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
+import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 18:09
+ * @Description:
+ */
+public interface DeviceVideoMapper extends BaseMapper {
+ /**
+ * 条件查询视频设备列表
+ * @param status 是否在线
+ * @param keyword 设备名称或者编码
+ * @return {@link List< DeviceVideoListResp>}
+ */
+ List selectDeviceVideoList(@Param("status") String status, @Param("keyword") String keyword, @Param("hostAddress") String hostAddress);
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java
new file mode 100644
index 0000000..1332af0
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java
@@ -0,0 +1,13 @@
+package com.dite.znpt.monitor.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dite.znpt.monitor.domain.entity.IpConfigEntity;
+
+/**
+ * @Date: 2023/09/05 16:39
+ * @Description: 监控设备IP配置表数据库访问层
+ */
+public interface IpConfigMapper extends BaseMapper {
+}
+
diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java
new file mode 100644
index 0000000..a3ce77a
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java
@@ -0,0 +1,12 @@
+package com.dite.znpt.monitor.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/11 15:00
+ * @Description:
+ */
+public interface StreamMediaFormatMapper extends BaseMapper {
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java
new file mode 100644
index 0000000..f8604da
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java
@@ -0,0 +1 @@
+package com.dite.znpt.monitor.media.zlm;
import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.ServerInfo;
import com.dite.znpt.monitor.media.zlm.dto.req.*;
import com.dite.znpt.monitor.media.zlm.dto.resp.*;
import java.io.IOException;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/29 10:14
* @Description: Zlm客户端启动类
*/
public interface ZlmApi {
/**
* 获取API列表
* api: /index/api/getApiList
*
* @return Api列表
*/
List getApiList(ServerInfo server);
/**
* 获取各epoll(或select)线程负载以及延时
* api: /index/api/getThreadsLoad
*
* @return 各epoll(或select)线程负载以及延时
*/
List getThreadsLoad(ServerInfo server);
/**
* 获取各后台epoll(或select)线程负载以及延时
* api: /index/api/getWorkThreadsLoad
*
* @return 各后台epoll(或select)线程负载以及延时
*/
List getWorkThreadsLoad(ServerInfo server);
/**
* 获取服务器配置
* api: /index/api/getServerConfig
*
* @return 服务器配置
*/
List getServerConfig(ServerInfo server);
/**
* 设置服务器配置
* api: /index/api/setServerConfig
*
* @param config 服务器配置
* @return 操作结果
*/
Integer setServerConfig(ServerInfo server, ServerConfig config);
/**
* 重启服务器,只有Daemon方式才能重启,否则是直接关闭!
* api: /index/api/restartServer
*
* @return 操作结果
*/
Boolean restartServer(ServerInfo server);
/**
* 获取流列表,可选筛选参数
* api: /index/api/getMediaList
*
* @param req 请求参数
* @return 操作结果
*/
List getMediaList(ServerInfo server, StreamReq req);
/**
* 关闭流(目前所有类型的流都支持关闭)
* api: /index/api/close_streams
*
* @param req 请求参数
* @return 操作结果
*/
CloseStreamResp closeStreams(ServerInfo server, CloseStreamReq req);
/**
* 获取所有TcpSession列表(获取所有tcp客户端相关信息)
* api: /index/api/getAllSession
*
* @param req 请求参数
* @return 所有TcpSession列表
*/
List getAllSession(ServerInfo server, GetAllSessionReq req);
/**
* 断开tcp连接,比如说可以断开rtsp、rtmp播放器等
* api: /index/api/kick_session
*
* @param id 客户端唯一id,可以通过getAllSession接口获取
* @return 操作结果
*/
Boolean kickSession(ServerInfo server, Long id);
/**
* 断开tcp连接,比如说可以断开rtsp、rtmp播放器等
* api: /index/api/kick_sessions
*
* @param req 请求参数
* @return 操作结果
*/
Integer kickSession(ServerInfo server, GetAllSessionReq req);
/**
* 动态添加rtsp/rtmp/hls拉流代理(只支持H264/H265/aac/G711负载)
* api: /index/api/addStreamProxy
*
* @param req 请求参数
* @return 唯一Key
*/
String addStreamProxy(ServerInfo server, StreamProxyReq req);
/**
* 关闭拉流代理
* api: /index/api/delStreamProxy
*
* @param key addStreamProxy接口返回的key
* @return 操作结果
*/
Boolean delStreamProxy(ServerInfo server, String key);
/**
* 通过fork FFmpeg进程的方式拉流代理,支持任意协议
* api: /index/api/addFFmpegSource
*
* @param req 请求参数
* @return 唯一Key
*/
String addFfMpegSource(ServerInfo server, FFmpegSourceReq req);
/**
* 关闭ffmpeg拉流代理
* api: /index/api/delFFmpegSource
*
* @param key addFFmpegSource接口返回的key
* @return 操作结果
*/
Boolean delFfMpegSource(ServerInfo server, String key);
/**
* 获取rtp代理时的某路ssrc rtp信息
* api: /index/api/getRtpInfo
*
* @param streamId RTP的ssrc,16进制字符串或者是流的id(openRtpServer接口指定)
* @return 操作结果
*/
RtpInfoResp getRtpInfo(ServerInfo server, String streamId);
/**
* 搜索文件系统,获取流对应的录像文件列表或日期文件夹列表
* api: /index/api/getMp4RecordFile
*
* @param req 请求参数
* @return 操作结果
*/
Mp4RecordFileResp getMp4RecordFile(ServerInfo server, GetMp4RecordFileReq req);
/**
* 开始录制hls或MP4
* api: /index/api/startRecord
*
* @param req 请求参数
* @return 操作结果
*/
Boolean startRecord(ServerInfo server, RecordReq req);
/**
* 停止录制流
* api: /index/api/stopRecord
*
* @param req 请求参数
* @return 操作结果
*/
Boolean stopRecord(ServerInfo server, RecordReq req);
/**
* 获取流录制状态
* api: /index/api/isRecording
*
* @param req 请求参数
* @return 操作结果
*/
Boolean isRecording(ServerInfo server, RecordReq req);
/**
* 获取截图或生成实时截图并返回
* api: /index/api/getSnap
*
* @param req 请求参数
* @return jpeg格式的图片,可以在浏览器直接打开
*/
void getSnap(ServerInfo server, SnapReq req) throws IOException;
/**
* 创建GB28181 RTP接收端口,如果该端口接收数据超时,则会自动被回收(不用调用closeRtpServer接口)
* api: /index/api/openRtpServer
*
* @param req 请求参数
* @return 接收端口,方便获取随机端口号
*/
Integer openRtpServer(ServerInfo server, RtpServerReq req);
/**
* 关闭GB28181 RTP接收端口
* api: /index/api/closeRtpServer
*
* @param streamId 该端口绑定的流ID,该端口只能创建这一个流(而不是根据ssrc创建多个)
* @return 是否找到记录并关闭
*/
Boolean closeRtpServer(ServerInfo server, String streamId);
/**
* 获取openRtpServer接口创建的所有RTP服务器
* api: /index/api/listRtpServer
*
* @return 是否找到记录并关闭
*/
List listRtpServer(ServerInfo server);
/**
* 作为GB28181客户端,启动ps-rtp推流,支持rtp/udp方式;
* 该接口支持rtsp/rtmp等协议转ps-rtp推流。第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试。
* api: /index/api/startSendRtp
*
* @param req 请求参数
* @return 使用的本地端口号
*/
Integer startSendRtp(ServerInfo server, SendRtpReq req);
/**
* 作为GB28181 Passive TCP服务器;
* 该接口支持rtsp/rtmp等协议转ps-rtp被动推流。
* 调用该接口,zlm会启动tcp服务器等待连接请求,
* 连接建立后,zlm会关闭tcp服务器,然后源源不断的往客户端推流。
* 第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试(不停地建立tcp监听,超时后再关闭)。
* api: /index/api/startSendRtpPassive
*
* @param req 请求参数
* @return 使用的本地端口号
*/
Integer startSendRtpPassive(ServerInfo server, SendRtpReq req);
/**
* 停止GB28181 ps-rtp推流
* api: /index/api/stopSendRtp
*
* @param req 请求参数
* @return 操作结果
*/
Boolean stopSendRtp(ServerInfo server, SendRtpReq req);
/**
* 获取主要对象个数统计,主要用于分析内存性能
* api: /index/api/getStatistic
*
* @return 操作结果
*/
StatisticResp getStatistic(ServerInfo server);
/**
* 添加rtsp/rtmp主动推流(把本服务器的直播流推送到其他服务器去)
* api: /index/api/addStreamPusherProxy
*
* @param req 请求参数
* @return 流的唯一标识
*/
String addStreamPusherProxy(ServerInfo server, StreamPusherProxyReq req);
/**
* 关闭推流
* api: /index/api/delStreamPusherProxy
*
* @param key 流的唯一标识
* @return 操作结果
*/
Boolean delStreamPusherProxy(ServerInfo server, String key);
}
\ No newline at end of file
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java
new file mode 100644
index 0000000..a9c1b11
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java
@@ -0,0 +1,135 @@
+package com.dite.znpt.monitor.media.zlm;
+
+import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
+import com.dite.znpt.monitor.media.zlm.dto.event.*;
+import com.dite.znpt.monitor.media.zlm.impl.ZlmHookService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 10:22
+ * @Description:
+ */
+@Slf4j
+@RestController
+@RequestMapping("/index/hook")
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class ZlmHook {
+ private final ZlmHookService service;
+
+ /**
+ * 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件,
+ * 阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。
+ */
+ @PostMapping(value = "/on_flow_report")
+ public BaseEventResp onFlowReport(@RequestBody FlowReportReq req) {
+ return service.onFlowReport(req);
+ }
+
+ /**
+ * 访问http文件服务器上hls之外的文件时触发。
+ */
+ @PostMapping(value = "/on_http_access")
+ public HttpAccessResp onHttpAccess(@RequestBody HttpAccessReq req) {
+ return service.onHttpAccess(req);
+ }
+
+ /**
+ * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件;
+ * 如果流不存在,那么先触发on_play事件然后触发on_stream_not_found事件。
+ * 播放rtsp流时,如果该流启动了rtsp专属鉴权(on_rtsp_realm)那么将不再触发on_play事件。
+ */
+ @PostMapping(value = "/on_play")
+ public BaseEventResp onPlay(@RequestBody PlayReq req) {
+ return service.onPlay(req);
+ }
+
+ /**
+ * rtsp/rtmp/rtp推流鉴权事件。
+ */
+ @PostMapping(value = "/on_publish")
+ public PublishResp onPublish(@RequestBody PublishReq req) {
+ return service.onPublish(req);
+ }
+
+
+ /**
+ * 录制mp4完成后通知事件;此事件对回复不敏感。
+ */
+ @PostMapping(value = "/on_record_mp4")
+ public BaseEventResp onRecordMp4(@RequestBody RecordMp4Req req) {
+ return service.onRecordMp4(req);
+ }
+
+ /**
+ * 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。
+ * 需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权。
+ */
+ @PostMapping(value = "/on_rtsp_realm")
+ public BaseEventResp onRtspRealm(@RequestBody RtspRealmReq req) {
+ return service.onRtspRealm(req);
+ }
+
+ /**
+ * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
+ */
+ @PostMapping(value = "/on_rtsp_auth")
+ public RtspAuthResp onRtspAuth(@RequestBody RtspAuthReq req) {
+ return service.onRtspAuth(req);
+ }
+
+ /**
+ * shell登录鉴权,ZLMediaKit提供简单的telnet调试方式
+ * 使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面。
+ */
+ @PostMapping(value = "/on_shell_login")
+ public BaseEventResp onShellLogin(@RequestBody ShellLoginReq req) {
+ return service.onShellLogin(req);
+ }
+
+ /**
+ * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
+ */
+ @PostMapping(value = "/on_stream_changed")
+ public BaseEventResp onStreamChanged(@RequestBody StreamChangedReq req) {
+ return service.onStreamChanged(req);
+ }
+
+ /**
+ * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
+ */
+ @PostMapping(value = "/on_stream_none_reader")
+ public BaseEventResp onStreamNoneReader(@RequestBody StreamNoneReaderReq req) {
+ return service.onStreamNoneReader(req);
+ }
+
+ /**
+ * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
+ */
+ @PostMapping(value = "/on_stream_not_found")
+ public BaseEventResp onStreamNotFound(@RequestBody StreamNotFoundReq req) {
+ return service.onStreamNotFound(req);
+ }
+
+ /**
+ * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
+ */
+ @PostMapping(value = "/on_server_started")
+ public BaseEventResp onServerStarted(@RequestBody ServerConfig req) {
+ return service.onServerStarted(req);
+ }
+
+ /**
+ * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
+ */
+ @PostMapping(value = "/on_server_keepalive")
+ public BaseEventResp onServerKeepalive(@RequestBody ServerKeepaliveReq req) {
+ return service.onServerKeepalive(req);
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java
new file mode 100644
index 0000000..b56e1e1
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java
@@ -0,0 +1,42 @@
+package com.dite.znpt.monitor.media.zlm;
+
+import com.dite.znpt.monitor.media.zlm.dto.MediaItem;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 10:39
+ * @Description: 流媒体服务管理主业务
+ */
+public interface ZlmService {
+ /**
+ * 点播视频
+ *
+ * @param deviceCode 设备编码
+ * @param channelCode 通道编码
+ * @return 流信息
+ */
+ MediaItem play(String deviceCode, String channelCode);
+
+ /**
+ * 失败的时候释放流媒体资源
+ *
+ * @param deviceCode 设备编码
+ * @param channelCode 通道编码
+ */
+ void release(String deviceCode, String channelCode);
+
+ /**
+ * 失败的时候释放流媒体资源
+ *
+ * @param media 流媒体信息
+ */
+ void release(MediaItem media);
+
+ /**
+ * 停止点播
+ *
+ * @param mediaServerId 流媒体服务器id,通过配置文件设置
+ * @param streamId 流ID
+ */
+ void display(String mediaServerId, String streamId);
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java
new file mode 100644
index 0000000..4f13dad
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java
@@ -0,0 +1,38 @@
+package com.dite.znpt.monitor.media.zlm.cache;
+
+import com.dite.znpt.monitor.media.zlm.dto.ServerItem;
+import com.dite.znpt.service.impl.RedisService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 15:46
+ * @Description:
+ */
+@Component
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class MediaServerCache {
+ private final RedisService redisService;
+ private final String zlm_key = "zlm_media_server";
+
+
+ public void putLoad(ServerItem serverItem) {
+ redisService.setCacheObject(zlm_key, serverItem);
+ }
+
+ /**
+ * 获取zlm节点
+ */
+ public ServerItem getLoad() {
+ return redisService.getCacheObject(zlm_key);
+ }
+
+ public void releaseSsrc(String ssrc) {
+ ServerItem item = getLoad();
+ item.releaseSsrc(ssrc);
+ putLoad(item);
+ }
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java
new file mode 100644
index 0000000..27b7fbb
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java
@@ -0,0 +1,54 @@
+package com.dite.znpt.monitor.media.zlm.cache;
+
+import cn.hutool.core.util.StrUtil;
+import com.dite.znpt.monitor.media.zlm.dto.MediaItem;
+import com.dite.znpt.service.impl.RedisService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 15:46
+ * @Description:
+ */
+@Component
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class MediaServerChannelCache {
+ private final RedisService redisService;
+
+ private String getKey(String deviceCode, String channelCode) {
+ return StrUtil.format("zlm_media_server_channel:{}:{}", deviceCode, channelCode);
+ }
+
+ private String getStreamKey(String mediaServerId, String streamId) {
+ return StrUtil.format("zlm_media_server_channel_stream_key:{}:{}", mediaServerId, streamId);
+ }
+
+ public boolean has(String deviceCode, String channelCode) {
+ return redisService.hasKey(getKey(deviceCode, channelCode));
+ }
+
+ public MediaItem get(String deviceCode, String channelCode) {
+ return redisService.getCacheObject(getKey(deviceCode, channelCode));
+ }
+
+ public void put(String deviceCode, String channelCode, MediaItem media) {
+ String key = getKey(deviceCode, channelCode);
+ redisService.setCacheObject(key, media);
+ redisService.setCacheObject(getStreamKey(media.getConfig().getGeneralMediaServerId(), media.getStreamId()), key);
+ }
+
+ public void delete(MediaItem media) {
+ redisService.deleteObject(getKey(media.getDeviceCode(), media.getChannelCode()));
+ redisService.deleteObject(getStreamKey(media.getConfig().getGeneralMediaServerId(), media.getStreamId()));
+ }
+
+ public MediaItem getByStream(String mediaServerId, String streamId) {
+ String key = redisService.getCacheObject(getStreamKey(mediaServerId, streamId));
+ if (StrUtil.isNotBlank(key)) {
+ return redisService.getCacheObject(key);
+ }
+ return null;
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java
new file mode 100644
index 0000000..e2a7d07
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java
@@ -0,0 +1,46 @@
+package com.dite.znpt.monitor.media.zlm.config;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "zlm-config")
+public class StreamMediaServerConfig {
+
+ @ApiModelProperty(value = "'流媒体名称'")
+ private String mediaName;
+
+ @ApiModelProperty(value = "流媒体服务商")
+ private String mediaService;
+
+ @ApiModelProperty(value = "公网ip")
+ private String publicHost;
+
+ @ApiModelProperty(value = "接口ip")
+ private String apiHost;
+
+ @ApiModelProperty(value = "接口端口")
+ private Integer apiPort;
+
+ @ApiModelProperty(value = "密钥")
+ private String secretKey;
+
+ @ApiModelProperty(value = "流id前缀")
+ private String streamPrefix;
+
+ @ApiModelProperty(value = "rtp ip")
+ private String rtpHost;
+
+ @ApiModelProperty(value = "rtp 端口")
+ private Integer rtpPort;
+
+ @ApiModelProperty(value = "动态端口起始值")
+ private String dynamicPortStart;
+
+ @ApiModelProperty(value = "动态端口结束值")
+ private String dynamicPortEnd;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java
new file mode 100644
index 0000000..a39c8c4
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java
@@ -0,0 +1,130 @@
+package com.dite.znpt.monitor.media.zlm.dto;
+
+import cn.hutool.core.util.StrUtil;
+import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
+import com.dite.znpt.monitor.media.zlm.dto.resp.RtpInfoResp;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 15:41
+ * @Description:
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class MediaItem implements Serializable {
+ private static final long serialVersionUID = -6679610697837602559L;
+ /**
+ * 设备编码
+ */
+ private String deviceCode;
+ /**
+ * 通道编码
+ */
+ private String channelCode;
+ /**
+ * 节点信息ID
+ */
+ private String configId;
+ /**
+ * 节点信息ID
+ */
+ private ServerInfo server;
+ /**
+ * 节点格式信息
+ */
+ private List formatList;
+ /**
+ * 节点配置信息
+ */
+ private ServerConfig config;
+ /**
+ * 流ID
+ */
+ private String streamId;
+ /**
+ * 播放流信息
+ */
+ private RtpInfoResp rtp;
+ /**
+ * Rtp服务监听端口
+ */
+ private Integer rtpPort;
+ /**
+ * SSRC源地址
+ */
+ private String ssrc;
+ /**
+ * rtmp播放地址
+ */
+ private String rtmpUrl;
+ /**
+ * rtmpSsl播放地址
+ */
+ private String rtmpSslUrl;
+ /**
+ * rtsp播放地址
+ */
+ private String rtspUrl;
+ /**
+ * rtspSsl播放地址
+ */
+ private String rtspSslUrl;
+
+ /**
+ * rtc流地址
+ */
+ private String rtc;
+
+ /**
+ * rtcs流地址
+ */
+ private String rtcs;
+
+ /**
+ * 是否缓存
+ */
+ private Boolean isCache;
+
+ public List getFormatList(String mediaRouter) {
+ if (StrUtil.isNotBlank(streamId)) {
+ formatList.forEach(item -> item.generateUrl(server.getApiHost(), streamId, config, mediaRouter));
+ }
+ return formatList;
+ }
+
+ public String getRtmpUrl() {
+ if (StrUtil.isBlank(streamId)) {
+ return "";
+ }
+ return StrUtil.format("rtmp://{}:{}/rtp/{}", server.getApiHost(), config.getRtmpPort(), streamId);
+ }
+
+ public String getRtmpSslUrl() {
+ if (StrUtil.isBlank(streamId)) {
+ return "";
+ }
+ return config.getRtspSslPort() > 0 ? StrUtil.format("rtmps://{}:{}/rtp/{}", server.getApiHost(), config.getRtspSslPort(), streamId) : "";
+ }
+
+
+ public String getRtspUrl() {
+ if (StrUtil.isBlank(streamId)) {
+ return "";
+ }
+ return StrUtil.format("rtsp://{}:{}/rtp/{}", server.getApiHost(), config.getRtspPort(), streamId);
+ }
+
+ public String getRtspSslUrl() {
+ if (StrUtil.isBlank(streamId)) {
+ return "";
+ }
+ return config.getRtspSslPort() > 0 ? StrUtil.format("rtsps://{}:{}/rtp/{}", server.getApiHost(), config.getRtspSslPort(), streamId) : "";
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java
new file mode 100644
index 0000000..62b51d7
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java
@@ -0,0 +1,852 @@
+package com.dite.znpt.monitor.media.zlm.dto;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig;
+import com.dite.znpt.monitor.media.zlm.dto.req.BaseReq;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 10:54
+ * @Description: 服务器配置
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Accessors(chain = true)
+public class ServerConfig extends BaseReq {
+ // ----------------------------------------------- api -----------------------------------------------
+ /**
+ * 是否调试http api,启用调试后,会打印每次http请求的内容和回复
+ * apiDebug=1
+ */
+ @JSONField(name = "api.apiDebug")
+ private Integer apiDebug;
+ /**
+ * 一些比较敏感的http api在访问时需要提供secret,否则无权限调用
+ * 如果是通过127.0.0.1访问,那么可以不提供secret
+ * secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
+ */
+ @JSONField(name = "api.secret")
+ private String apiSecret;
+ /**
+ * 截图保存路径根目录,截图通过http api(/index/api/getSnap)生成和获取
+ * snapRoot=./www/snap/
+ */
+ @JSONField(name = "api.snapRoot")
+ private String apiSnapRoot;
+ /**
+ * 默认截图图片,在启动FFmpeg截图后但是截图还未生成时,可以返回默认的预设图片
+ * defaultSnap=./www/logo.png
+ */
+ @JSONField(name = "api.defaultSnap")
+ private String apiDefaultSnap;
+ // ----------------------------------------------- ffmpeg -----------------------------------------------
+ /**
+ * FFmpeg可执行程序路径,支持相对路径/绝对路径
+ * bin=/usr/bin/ffmpeg
+ */
+ @JSONField(name = "ffmpeg.bin")
+ private String ffmpegBin;
+ /**
+ * FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
+ * cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
+ */
+ @JSONField(name = "ffmpeg.cmd")
+ private String ffmpegCmd;
+ /**
+ * FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量
+ * snap=%s -i %s -y -f mjpeg -t 0.001 %s
+ */
+ @JSONField(name = "ffmpeg.snap")
+ private String ffmpegSnap;
+ /**
+ * FFmpeg日志的路径,如果置空则不生成FFmpeg日志
+ * 可以为相对(相对于本可执行程序目录)或绝对路径
+ * log=./ffmpeg/ffmpeg.log
+ */
+ @JSONField(name = "ffmpeg.log")
+ private String ffmpegLog;
+ /**
+ * 自动重启的时间(秒), 默认为0, 也就是不自动重启. 主要是为了避免长时间ffmpeg拉流导致的不同步现象
+ * restart_sec=0
+ */
+ @JSONField(name = "ffmpeg.restart_sec")
+ private String ffmpegRestartSec;
+ // ----------------------------------------------- general -----------------------------------------------
+ /**
+ * 是否启用虚拟主机
+ * enableVhost=0
+ */
+ @JSONField(name = "general.enableVhost")
+ private Integer enableVhost;
+ /**
+ * 播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件),
+ * flowThreshold参数控制触发hook.on_flow_report事件阈值,使用流量超过该阈值后才触发,单位KB
+ * flowThreshold=1024
+ */
+ @JSONField(name = "general.flowThreshold")
+ private Integer generalFlowThreshold;
+ /**
+ * 播放最多等待时间,单位毫秒
+ * 播放在播放某个流时,如果该流不存在,
+ * ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
+ * 如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功
+ * 否则返回播放器未找到该流,该机制的目的是可以先播放再推流
+ * maxStreamWaitMS=15000
+ */
+ @JSONField(name = "general.maxStreamWaitMS")
+ private Integer generalMaxStreamWaitMs;
+ /**
+ * 某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
+ * 在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
+ * streamNoneReaderDelayMS=20000
+ */
+ @JSONField(name = "general.streamNoneReaderDelayMS")
+ private Integer generalStreamNoneReaderDelayMs;
+ /**
+ * 是否全局添加静音aac音频,转协议时有效
+ * 有些播放器在打开单视频流时不能秒开,添加静音音频可以加快秒开速度
+ * addMuteAudio=1
+ */
+ @JSONField(name = "general.addMuteAudio")
+ private Integer generalAddMuteAudio;
+ /**
+ * 拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
+ * 如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
+ * resetWhenRePlay=1
+ */
+ @JSONField(name = "general.resetWhenRePlay")
+ private Integer generalResetWhenRePlay;
+ /**
+ * 是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
+ * publishToHls=1
+ */
+ @JSONField(name = "general.publishToHls")
+ private Integer generalPublishToHls;
+ /**
+ * 是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
+ * publishToMP4=0
+ */
+ @JSONField(name = "general.publishToMP4")
+ private Integer generalPublishToMP4;
+ /**
+ * 合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
+ * 开启后会同时关闭TCP_NODELAY并开启MSG_MORE
+ * mergeWriteMS=0
+ */
+ @JSONField(name = "general.mergeWriteMS")
+ private Integer generalMergeWriteMS;
+ /**
+ * 全局的时间戳覆盖开关,在转协议时,对frame进行时间戳覆盖
+ * 该开关对rtsp/rtmp/rtp推流、rtsp/rtmp/hls拉流代理转协议时生效
+ * 会直接影响rtsp/rtmp/hls/mp4/flv等协议的时间戳
+ * 同协议情况下不影响(例如rtsp/rtmp推流,那么播放rtsp/rtmp时不会影响时间戳)
+ * modifyStamp=0
+ */
+ @JSONField(name = "general.modifyStamp")
+ private Integer generalModifyStamp;
+ /**
+ * 服务器唯一id,用于触发hook时区别是哪台服务器
+ * mediaServerId=your_server_id
+ */
+ @JSONField(name = "general.mediaServerId")
+ private String generalMediaServerId;
+ /**
+ * 转协议是否全局开启或关闭音频
+ * enable_audio=1
+ */
+ @JSONField(name = "general.enable_audio")
+ private Integer generalEnableAudio;
+ // ###### 以下是按需转协议的开关,在测试ZLMediaKit的接收推流性能时,请把下面开关置1
+ // ###### 如果某种协议你用不到,你可以把以下开关置1以便节省资源(但是还是可以播放,只是第一个播放者体验稍微差点),
+ // ###### 如果某种协议你想获取最好的用户体验,请置0(第一个播放者可以秒开,且不花屏)
+ /**
+ * hls协议是否按需生成,如果hls.segNum配置为0(意味着hls录制),那么hls将一直生成(不管此开关)
+ * hls_demand=0
+ */
+ @JSONField(name = "general.hls_demand")
+ private Integer generalHlsDemand;
+ /**
+ * rtsp[s]协议是否按需生成
+ * rtsp_demand=0
+ */
+ @JSONField(name = "general.rtsp_demand")
+ private Integer generalRtspDemand;
+ /**
+ * rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成
+ * rtmp_demand=0
+ */
+ @JSONField(name = "general.rtmp_demand")
+ private Integer generalRtmpDemand;
+ /**
+ * http[s]-ts协议是否按需生成
+ * ts_demand=0
+ */
+ @JSONField(name = "general.ts_demand")
+ private Integer generalTsDemand;
+ /**
+ * http[s]-fmp4、ws[s]-fmp4协议是否按需生成
+ * fmp4_demand=0
+ */
+ @JSONField(name = "general.fmp4_demand")
+ private Integer generalFmp4Demand;
+ /**
+ * 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track
+ * wait_track_ready_ms=10000
+ */
+ @JSONField(name = "general.wait_track_ready_ms")
+ private Integer generalWaitTrackReadyMs;
+ /**
+ * 如果流只有单Track,最多等待若干毫秒,超时后未收到其他Track的数据,则认为是单Track
+ * 如果协议元数据有声明特定track数,那么无此等待时间
+ * wait_add_track_ms=3000
+ */
+ @JSONField(name = "general.wait_add_track_ms")
+ private Integer generalWaitAddTrackMs;
+ /**
+ * 如果track未就绪,我们先缓存帧数据,但是有最大个数限制,防止内存溢出
+ * unready_frame_cache=100
+ */
+ @JSONField(name = "general.unready_frame_cache")
+ private Integer generalUnreadyFrameCache;
+ /**
+ * 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
+ * 置0关闭此特性(推流断开会导致立即断开播放器)
+ * 此参数不应大于播放器超时时间
+ * continue_push_ms=15000
+ */
+ @JSONField(name = "general.continue_push_ms")
+ private Integer generalContinuePushMs;
+ // ----------------------------------------------- hls -----------------------------------------------
+ /**
+ * hls写文件的buf大小,调整参数可以提高文件io性能
+ * fileBufSize=65536
+ */
+ @JSONField(name = "hls.fileBufSize")
+ private Integer hlsFileBufSize;
+ /**
+ * hls保存文件路径
+ * 可以为相对(相对于本可执行程序目录)或绝对路径
+ * filePath=./www
+ */
+ @JSONField(name = "hls.filePath")
+ private String hlsFilePath;
+ /**
+ * hls最大切片时间
+ * segDur=2
+ */
+ @JSONField(name = "hls.segDur")
+ private Integer hlsSegDur;
+ /**
+ * m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
+ * 如果设置为0,则不删除切片,而是保存为点播
+ * segNum=3
+ */
+ @JSONField(name = "hls.segNum")
+ private Integer hlsSegNum;
+ /**
+ * HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
+ * segRetain=5
+ */
+ @JSONField(name = "hls.segRetain")
+ private Integer hlsSegRetain;
+ /**
+ * 是否广播 ts 切片完成通知
+ * broadcastRecordTs=0
+ */
+ @JSONField(name = "hls.broadcastRecordTs")
+ private Integer hlsBroadcastRecordTs;
+ /**
+ * 直播hls文件删除延时,单位秒,issue: #913
+ * deleteDelaySec=0
+ */
+ @JSONField(name = "hls.deleteDelaySec")
+ private Integer hlsDeleteDelaySec;
+ /**
+ * 是否保留hls文件,此功能部分等效于segNum=0的情况
+ * 不同的是这个保留不会在m3u8文件中体现
+ * 0为不保留,不起作用
+ * 1为保留,则不删除hls文件,如果开启此功能,注意磁盘大小,或者定期手动清理hls文件
+ * segKeep=0
+ */
+ @JSONField(name = "hls.segKeep")
+ private Integer hlsSegKeep;
+ // ----------------------------------------------- hook -----------------------------------------------
+ /**
+ * 在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
+ * 该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
+ * admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
+ */
+ @JSONField(name = "hook.admin_params")
+ private String hookAdminParams;
+ /**
+ * 是否启用hook事件,启用后,推拉流都将进行鉴权
+ * enable=0
+ */
+ @JSONField(name = "hook.enable")
+ private Integer hookHookEnable;
+ /**
+ * 播放器或推流器使用流量事件,置空则关闭
+ * on_flow_report=https://127.0.0.1/index/hook/on_flow_report
+ */
+ @JSONField(name = "hook.on_flow_report")
+ private String hookOnFlowReport;
+ /**
+ * 访问http文件鉴权事件,置空则关闭鉴权
+ * on_http_access=https://127.0.0.1/index/hook/on_http_access
+ */
+ @JSONField(name = "hook.on_http_access")
+ private String hookOnHttpAccess;
+ /**
+ * 播放鉴权事件,置空则关闭鉴权
+ * on_play=https://127.0.0.1/index/hook/on_play
+ */
+ @JSONField(name = "hook.on_play")
+ private String hookOnPlay;
+ /**
+ * 推流鉴权事件,置空则关闭鉴权
+ * on_publish=https://127.0.0.1/index/hook/on_publish
+ */
+ @JSONField(name = "hook.on_publish")
+ private String hookOnPublish;
+ /**
+ * 录制mp4切片完成事件
+ * on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
+ */
+ @JSONField(name = "hook.on_record_mp4")
+ private String hookOnRecordMp4;
+ /**
+ * 录制 hls ts 切片完成事件
+ * on_record_ts=https://127.0.0.1/index/hook/on_record_ts
+ */
+ @JSONField(name = "hook.on_record_ts")
+ private String hookOnRecordTs;
+ /**
+ * rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
+ * on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
+ */
+ @JSONField(name = "hook.on_rtsp_auth")
+ private String hookOnRtspAuth;
+ /**
+ * rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
+ * 建议开发者统一采用url参数方式鉴权,rtsp用户名密码鉴权一般在设备上用的比较多
+ * 开启rtsp专属鉴权后,将不再触发on_play鉴权事件
+ * on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm
+ */
+ @JSONField(name = "hook.on_rtsp_realm")
+ private String hookOnRtspRealm;
+ /**
+ * 远程telnet调试鉴权事件
+ * on_shell_login=https://127.0.0.1/index/hook/on_shell_login
+ */
+ @JSONField(name = "hook.on_shell_login")
+ private String hookOnShellLogin;
+ /**
+ * 直播流注册或注销事件
+ * on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed
+ */
+ @JSONField(name = "hook.on_stream_changed")
+ private String hookOnStreamChanged;
+ /**
+ * 服务器启动报告,可以用于服务器的崩溃重启事件监听
+ * on_server_started=https://127.0.0.1/index/hook/on_server_started
+ */
+ @JSONField(name = "hook.on_server_started")
+ private String hookOnServerStarted;
+ /**
+ * server保活上报
+ * on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive
+ */
+ @JSONField(name = "hook.on_server_keepalive")
+ private String hookOnServerKeepalive;
+ /**
+ * 无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
+ * on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
+ */
+ @JSONField(name = "hook.on_stream_none_reader")
+ private String hookOnStreamNoneReader;
+ /**
+ * 播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流
+ * on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found
+ */
+ @JSONField(name = "hook.on_stream_not_found")
+ private String hookOnStreamNotFound;
+ /**
+ * 发送rtp(startSendRtp)被动关闭时回调
+ * on_send_rtp_stopped=https://127.0.0.1/index/hook/on_send_rtp_stopped
+ */
+ @JSONField(name = "hook.on_send_rtp_stopped")
+ private String hookOnSendRtpStopped;
+ /**
+ * hook api最大等待回复时间,单位秒
+ * timeoutSec=10
+ */
+ @JSONField(name = "hook.timeoutSec")
+ private Integer hookTimeoutSec;
+ /**
+ * keepalive hook触发间隔,单位秒,float类型
+ * alive_interval=10.0
+ */
+ @JSONField(name = "hook.alive_interval")
+ private Float hookAliveInterval;
+ /**
+ * hook通知失败重试次数,正整数。为0不重试,1时重试一次,以此类推
+ * retry=1
+ */
+ @JSONField(name = "hook.retry")
+ private Integer hookRetry;
+ /**
+ * hook通知失败重试延时,单位秒,float型
+ * retry_delay=3.0
+ */
+ @JSONField(name = "hook.retry_delay")
+ private Float hookRetryDelay;
+ // ----------------------------------------------- cluster -----------------------------------------------
+ /**
+ * 设置源站拉流url模板, 格式跟printf类似,第一个%s指定app,第二个%s指定stream_id,
+ * 开启集群模式后,on_stream_not_found和on_stream_none_reader hook将无效.
+ * 溯源模式支持以下类型:
+ * rtmp方式: rtmp://127.0.0.1:1935/%s/%s
+ * rtsp方式: rtsp://127.0.0.1:554/%s/%s
+ * hls方式: http://127.0.0.1:80/%s/%s/hls.m3u8
+ * http-ts方式: http://127.0.0.1:80/%s/%s.live.ts
+ * 支持多个源站,不同源站通过分号(;)分隔
+ * origin_url=
+ */
+ @JSONField(name = "cluster.origin_url")
+ private String clusterOriginUrl;
+ /**
+ * 溯源总超时时长,单位秒,float型;假如源站有3个,那么单次溯源超时时间为timeout_sec除以3
+ * 单次溯源超时时间不要超过general.maxStreamWaitMS配置
+ * timeout_sec=15
+ */
+ @JSONField(name = "cluster.timeout_sec")
+ private Integer clusterTimeoutSec;
+ /**
+ * 溯源失败尝试次数,-1时永久尝试
+ * retry_count=3
+ */
+ @JSONField(name = "cluster.retry_count")
+ private Integer clusterRetryCount;
+ // ----------------------------------------------- http -----------------------------------------------
+ /**
+ * http服务器字符编码,windows上默认gb2312
+ * charSet=utf-8
+ */
+ @JSONField(name = "http.charSet")
+ private String httpCharSet;
+ /**
+ * http链接超时时间
+ * keepAliveSecond=30
+ */
+ @JSONField(name = "http.keepAliveSecond")
+ private Integer httpKeepAliveSecond;
+ /**
+ * http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
+ * maxReqSize=40960
+ */
+ @JSONField(name = "http.maxReqSize")
+ private Integer httpMaxReqSize;
+ /**
+ * 404网页内容,用户可以自定义404网页
+ * notFound=404 Not Found您访问的资源不存在!
ZLMediaKit-4.0
+ */
+ @JSONField(name = "http.notFound")
+ private String httpNotFound;
+ /**
+ * http服务器监听端口
+ * port=80
+ */
+ @JSONField(name = "http.port")
+ private Integer httpPort;
+ /**
+ * http文件服务器根目录
+ * 可以为相对(相对于本可执行程序目录)或绝对路径
+ * rootPath=./www
+ */
+ @JSONField(name = "http.rootPath")
+ private String httpRootPath;
+ /**
+ * http文件服务器读文件缓存大小,单位BYTE,调整该参数可以优化文件io性能
+ * sendBufSize=65536
+ */
+ @JSONField(name = "http.sendBufSize")
+ private Integer httpSendBufSize;
+ /**
+ * https服务器监听端口
+ * sslport=443
+ */
+ @JSONField(name = "http.sslport")
+ private Integer httpSslPort;
+ /**
+ * 是否显示文件夹菜单,开启后可以浏览文件夹
+ * dirMenu=1
+ */
+ @JSONField(name = "http.dirMenu")
+ private Integer httpDirMenu;
+ /**
+ * 虚拟目录, 虚拟目录名和文件路径使用","隔开,多个配置路径间用";"隔开
+ * 例如赋值为 app_a,/path/to/a;app_b,/path/to/b 那么
+ * 访问 http://127.0.0.1/app_a/file_a 对应的文件路径为 /path/to/a/file_a
+ * 访问 http://127.0.0.1/app_b/file_b 对应的文件路径为 /path/to/b/file_b
+ * 访问其他http路径,对应的文件路径还是在rootPath内
+ * virtualPath=
+ */
+ @JSONField(name = "http.virtualPath")
+ private String httpVirtualPath;
+ /**
+ * 禁止后缀的文件使用mmap缓存,使用“,”隔开
+ * 例如赋值为 .mp4,.flv
+ * 那么访问后缀为.mp4与.flv 的文件不缓存
+ * forbidCacheSuffix=
+ */
+ @JSONField(name = "http.forbidCacheSuffix")
+ private String httpForbidCacheSuffix;
+ /**
+ * 可以把http代理前真实客户端ip放在http头中:https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
+ * 切勿暴露此key,否则可能导致伪造客户端ip
+ * forwarded_ip_header=
+ */
+ @JSONField(name = "http.forwarded_ip_header")
+ private String httpForwardedIpHeader;
+ // ----------------------------------------------- multicast -----------------------------------------------
+ /**
+ * rtp组播截止组播ip地址
+ * addrMax=239.255.255.255
+ */
+ @JSONField(name = "multicast.addrMax")
+ private String multicastAddrMax;
+ /**
+ * rtp组播起始组播ip地址
+ * addrMin=239.0.0.0
+ */
+ @JSONField(name = "multicast.addrMin")
+ private String multicastAddrMin;
+ /**
+ * 组播udp ttl
+ * udpTTL=64
+ */
+ @JSONField(name = "multicast.udpTTL")
+ private Integer multicastUdpTtl;
+ // ----------------------------------------------- record -----------------------------------------------
+ /**
+ * mp4录制或mp4点播的应用名,通过限制应用名,可以防止随意点播
+ * 点播的文件必须放置在此文件夹下
+ * appName=record
+ */
+ @JSONField(name = "record.appName")
+ private String recordAppName;
+ /**
+ * mp4录制写文件缓存,单位BYTE,调整参数可以提高文件io性能
+ * fileBufSize=65536
+ */
+ @JSONField(name = "record.fileBufSize")
+ private Integer recordFileBufSize;
+ /**
+ * mp4录制保存、mp4点播根路径
+ * 可以为相对(相对于本可执行程序目录)或绝对路径
+ * filePath=./www
+ */
+ @JSONField(name = "record.filePath")
+ private String recordFilePath;
+ /**
+ * mp4录制切片时间,单位秒
+ * fileSecond=3600
+ */
+ @JSONField(name = "record.fileSecond")
+ private Integer recordFileSecond;
+ /**
+ * mp4点播每次流化数据量,单位毫秒,
+ * 减少该值可以让点播数据发送量更平滑,增大该值则更节省cpu资源
+ * sampleMS=500
+ */
+ @JSONField(name = "record.sampleMS")
+ private Integer recordSampleMs;
+ /**
+ * mp4录制完成后是否进行二次关键帧索引写入头部
+ * fastStart=0
+ */
+ @JSONField(name = "record.fastStart")
+ private Integer recordFastStart;
+ /**
+ * MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件
+ * fileRepeat=0
+ */
+ @JSONField(name = "record.fileRepeat")
+ private Integer recordFileRepeat;
+ /**
+ * MP4录制是否当做播放器参与播放人数统计
+ * mp4_as_player=0
+ */
+ @JSONField(name = "record.mp4_as_player")
+ private Integer recordMp4AsPlayer;
+ // ----------------------------------------------- rtmp -----------------------------------------------
+ /**
+ * rtmp必须在此时间内完成握手,否则服务器会断开链接,单位秒
+ * handshakeSecond=15
+ */
+ @JSONField(name = "rtmp.handshakeSecond")
+ private Integer rtmpHandshakeSecond;
+ /**
+ * rtmp超时时间,如果该时间内未收到客户端的数据,
+ * 或者tcp发送缓存超过这个时间,则会断开连接,单位秒
+ * keepAliveSecond=15
+ */
+ @JSONField(name = "rtmp.keepAliveSecond")
+ private Integer rtmpKeepAliveSecond;
+ /**
+ * 在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
+ * modifyStamp=0
+ */
+ @JSONField(name = "rtmp.modifyStamp")
+ private Integer rtmpModifyStamp;
+ /**
+ * rtmp服务器监听端口
+ * port=1935
+ */
+ @JSONField(name = "rtmp.port")
+ private Integer rtmpPort = 0;
+ /**
+ * rtmps服务器监听地址
+ * sslport=0
+ */
+ @JSONField(name = "rtmp.sslport")
+ private Integer rtmpSslPort = 0;
+ // ----------------------------------------------- rtp -----------------------------------------------
+ /**
+ * 音频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
+ * 加大该值会明显增加直播延时
+ * audioMtuSize=600
+ */
+ @JSONField(name = "rtp.audioMtuSize")
+ private Integer rtpAudioMtuSize;
+ /**
+ * 视频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
+ * videoMtuSize=1400
+ */
+ @JSONField(name = "rtp.videoMtuSize")
+ private Integer rtpVideoMtuSize;
+ /**
+ * rtp包最大长度限制,单位KB,主要用于识别TCP上下文破坏时,获取到错误的rtp
+ * rtpMaxSize=10
+ */
+ @JSONField(name = "rtp.rtpMaxSize")
+ private Integer rtpMaxSize;
+ // ----------------------------------------------- rtp_proxy -----------------------------------------------
+ /**
+ * 导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
+ * dumpDir=
+ */
+ @JSONField(name = "rtp_proxy.dumpDir")
+ private String proxyDumpDir;
+ /**
+ * udp和tcp代理服务器,支持rtp(必须是ts或ps类型)代理
+ * port=10000
+ */
+ @JSONField(name = "rtp_proxy.port")
+ private Integer proxyPort;
+ /**
+ * rtp超时时间,单位秒
+ * timeoutSec=15
+ */
+ @JSONField(name = "rtp_proxy.timeoutSec")
+ private Integer proxyTimeoutSec;
+ /**
+ * 随机端口范围,最少确保36个端口
+ * 该范围同时限制rtsp服务器udp端口范围
+ * port_range=30000-35000
+ */
+ @JSONField(name = "rtp_proxy.port_range")
+ private String proxyPortRange;
+ /**
+ * rtp h264 负载的pt
+ * h264_pt=98
+ */
+ @JSONField(name = "rtp_proxy.h264_pt")
+ private Integer proxyH264Pt;
+ /**
+ * rtp h265 负载的pt
+ * h265_pt=99
+ */
+ @JSONField(name = "rtp_proxy.h265_pt")
+ private Integer proxyH265Pt;
+ /**
+ * rtp ps 负载的pt
+ * ps_pt=96
+ */
+ @JSONField(name = "rtp_proxy.ps_pt")
+ private Integer proxyPsPt;
+ /**
+ * rtp ts 负载的pt
+ * ts_pt=33
+ */
+ @JSONField(name = "rtp_proxy.ts_pt")
+ private Integer proxyTsPt;
+ /**
+ * rtp opus 负载的pt
+ * opus_pt=100
+ */
+ @JSONField(name = "rtp_proxy.opus_pt")
+ private Integer proxyOpusPt;
+ /**
+ * rtp g711u 负载的pt
+ * g711u_pt=0
+ */
+ @JSONField(name = "rtp_proxy.g711u_pt")
+ private Integer proxyG711UPt;
+ /**
+ * rtp g711a 负载的pt
+ * g711a_pt=8
+ */
+ @JSONField(name = "rtp_proxy.g711a_pt")
+ private Integer proxyG711APt;
+ // ----------------------------------------------- rtc -----------------------------------------------
+ /**
+ * rtc播放推流、播放超时时间
+ * timeoutSec=15
+ */
+ @JSONField(name = "rtc.timeoutSec")
+ private Integer rtcTimeoutSec;
+ /**
+ * 本机对rtc客户端的可见ip,作为服务器时一般为公网ip,可有多个,用','分开,当置空时,会自动获取网卡ip
+ * 同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考:https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
+ * externIP=
+ */
+ @JSONField(name = "rtc.externIP")
+ private String rtcExternIp;
+ /**
+ * rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据,
+ * 该端口是多线程的,同时支持客户端网络切换导致的连接迁移
+ * 需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
+ * port=8000
+ */
+ @JSONField(name = "rtc.port")
+ private Integer rtcPort;
+ /**
+ * 设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质
+ * 目前已经实现twcc自动调整码率,关闭remb根据真实网络状况调整码率
+ * rembBitRate=0
+ */
+ @JSONField(name = "rtc.rembBitRate")
+ private Integer rtcRembBitRate;
+ /**
+ * rtc支持的音频codec类型,在前面的优先级更高
+ * 以下范例为所有支持的音频codec
+ * preferredCodecA=PCMU,PCMA,opus,mpeg4-generic
+ */
+ @JSONField(name = "rtc.preferredCodecA")
+ private String rtcPreferredCodecA;
+ /**
+ * rtc支持的视频codec类型,在前面的优先级更高
+ * 以下范例为所有支持的视频codec
+ * preferredCodecV=H264,H265,AV1X,VP9,VP8
+ */
+ @JSONField(name = "rtc.preferredCodecV")
+ private String rtcPreferredCodecV;
+ // ----------------------------------------------- srt -----------------------------------------------
+ /**
+ * srt播放推流、播放超时时间,单位秒
+ * timeoutSec=5
+ */
+ @JSONField(name = "srt.timeoutSec")
+ private Integer srtTimeoutSec;
+ /**
+ * srt udp服务器监听端口号,所有srt客户端将通过该端口传输srt数据,
+ * 该端口是多线程的,同时支持客户端网络切换导致的连接迁移
+ * port=9000
+ */
+ @JSONField(name = "srt.port")
+ private Integer srtPort;
+ /**
+ * srt 协议中延迟缓存的估算参数,在握手阶段估算rtt ,然后latencyMul*rtt 为最大缓存时长,此参数越大,表示等待重传的时长就越大
+ * latencyMul=4
+ */
+ @JSONField(name = "srt.latencyMul")
+ private Integer srtLatencyMul;
+ /**
+ * 包缓存的大小
+ * pktBufSize=8192
+ */
+ @JSONField(name = "srt.pktBufSize")
+ private Integer srtPktBufSize;
+ // ----------------------------------------------- rtsp -----------------------------------------------
+ /**
+ * rtsp专有鉴权方式是采用base64还是md5方式
+ * authBasic=0
+ */
+ @JSONField(name = "rtsp.authBasic")
+ private Integer rtspAuthBasic;
+ /**
+ * rtsp拉流、推流代理是否是直接代理模式
+ * 直接代理后支持任意编码格式,但是会导致GOP缓存无法定位到I帧,可能会导致开播花屏
+ * 并且如果是tcp方式拉流,如果rtp大于mtu会导致无法使用udp方式代理
+ * 假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
+ * 如果你是rtsp推拉流,但是webrtc播放,也建议关闭直接代理模式,
+ * 因为直接代理时,rtp中可能没有sps pps,会导致webrtc无法播放; 另外webrtc也不支持Single NAL Unit Packets类型rtp
+ * 默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
+ * directProxy=1
+ */
+ @JSONField(name = "rtsp.directProxy")
+ private Integer rtspDirectProxy;
+ /**
+ * rtsp必须在此时间内完成握手,否则服务器会断开链接,单位秒
+ * handshakeSecond=15
+ */
+ @JSONField(name = "rtsp.handshakeSecond")
+ private Integer rtspHandshakeSecond;
+ /**
+ * rtsp超时时间,如果该时间内未收到客户端的数据,
+ * 或者tcp发送缓存超过这个时间,则会断开连接,单位秒
+ * keepAliveSecond=15
+ */
+ @JSONField(name = "rtsp.keepAliveSecond")
+ private Integer rtspKeepAliveSecond;
+ /**
+ * rtsp服务器监听地址
+ * port=554
+ */
+ @JSONField(name = "rtsp.port")
+ private Integer rtspPort = 0;
+ /**
+ * rtsps服务器监听地址
+ * sslport=0
+ */
+ @JSONField(name = "rtsp.sslport")
+ private Integer rtspSslPort = 0;
+ // ----------------------------------------------- shell -----------------------------------------------
+ /**
+ * 调试telnet服务器接受最大bufffer大小
+ * maxReqSize=1024
+ */
+ @JSONField(name = "shell.maxReqSize")
+ private Integer shellMaxReqSize;
+ /**
+ * 调试telnet服务器监听端口
+ * port=0
+ */
+ @JSONField(name = "shell.port")
+ private Integer shellPort;
+
+ public void refreshHook(String ip, String port, StreamMediaServerConfig server) {
+ String host = ip + ":" + port;
+ this.hookOnFlowReport = StrUtil.format("http://{}/index/hook/on_flow_report", host);
+ this.hookOnHttpAccess = StrUtil.format("http://{}/index/hook/on_http_access", host);
+ this.hookOnPlay = StrUtil.format("http://{}/index/hook/on_play", host);
+ this.hookOnPublish = StrUtil.format("http://{}/index/hook/on_publish", host);
+ this.hookOnRecordMp4 = StrUtil.format("http://{}/index/hook/on_record_mp4", host);
+ this.hookOnRecordTs = StrUtil.format("http://{}/index/hook/on_record_ts", host);
+ this.hookOnRtspAuth = StrUtil.format("http://{}/index/hook/on_rtsp_auth", host);
+ this.hookOnRtspRealm = StrUtil.format("http://{}/index/hook/on_rtsp_realm", host);
+ this.hookOnShellLogin = StrUtil.format("http://{}/index/hook/on_shell_login", host);
+ this.hookOnStreamChanged = StrUtil.format("http://{}/index/hook/on_stream_changed", host);
+ this.hookOnStreamNoneReader = StrUtil.format("http://{}/index/hook/on_stream_none_reader", host);
+ this.hookOnStreamNotFound = StrUtil.format("http://{}/index/hook/on_stream_not_found", host);
+ this.hookOnServerStarted = StrUtil.format("http://{}/index/hook/on_server_started", host);
+ this.hookOnServerKeepalive = StrUtil.format("http://{}/index/hook/on_server_keepalive", host);
+// this.hookOnSendRtpStopped = StrUtil.format("http://{}/index/hook/on_send_rtp_stopped", host);
+ this.hookOnSendRtpStopped = "";
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java
new file mode 100644
index 0000000..143b1fc
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java
@@ -0,0 +1,30 @@
+package com.dite.znpt.monitor.media.zlm.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 10:50
+ * @Description: 节点基础信息
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ServerInfo implements Serializable {
+ /**
+ * 节点地址
+ */
+ private String apiHost;
+ /**
+ * 节点端口
+ */
+ private Integer apiPort;
+ /**
+ * 节点秘钥
+ */
+ private String secretKey;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java
new file mode 100644
index 0000000..d590d3f
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java
@@ -0,0 +1,108 @@
+package com.dite.znpt.monitor.media.zlm.dto;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.StrUtil;
+import com.dite.znpt.exception.ServiceException;
+import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
+import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig;
+import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 10:50
+ * @Description: 节点信息
+ */
+@Data
+@NoArgsConstructor
+public class ServerItem implements Serializable {
+
+ private static final long serialVersionUID = 2460404295026548536L;
+ /**
+ * 播流最大并发个数
+ */
+ public static final Integer MAX_PLAY_COUNT = 10000;
+ /**
+ * 节点信息ID
+ */
+ private String configId;
+ /**
+ * 流ID前缀
+ */
+ private String streamPrefix;
+ /**
+ * 节点信息ID
+ */
+ private ServerInfo server;
+ /**
+ * 节点格式信息
+ */
+ private List formatList;
+ /**
+ * 节点配置信息
+ */
+ private ServerConfig config;
+ /**
+ * 当前流信息
+ */
+ private List media;
+ /**
+ * 节点状态是否正常
+ */
+ private Boolean status;
+ /**
+ * 流媒体服务器已用的会话句柄
+ */
+ private Set usedSn;
+
+ public ServerItem(StreamMediaServerConfig server, List formatList) {
+ this.streamPrefix = server.getStreamPrefix();
+ this.server = new ServerInfo(server.getApiHost(), server.getApiPort(), server.getSecretKey());
+ this.formatList = formatList;
+ this.status = false;
+ this.usedSn = new HashSet<>();
+ }
+
+ public String genPlaySsrc(String channelCode) {
+ if (this.usedSn.size() >= MAX_PLAY_COUNT) {
+ throw new ServiceException("ssrc已经用完!");
+ }
+ int sn;
+ for (sn = 0; sn < MAX_PLAY_COUNT; sn++) {
+ if (!this.usedSn.contains(sn)) {
+ this.usedSn.add(sn);
+ break;
+ }
+ }
+ //return StrUtil.format("0{}{}", StrUtil.blankToDefault(streamPrefix, channelCode.substring(3, 8)), NumberUtil.decimalFormat("0000", sn));
+ return channelCode;
+ }
+
+ public void releaseSsrc(String ssrc) {
+ try {
+ Integer sn = NumberUtil.parseInt(ssrc.substring(6));
+ usedSn.remove(sn);
+ } catch (Exception ignored) {
+ }
+ }
+
+ public void setMedia(List media) {
+ this.media = media;
+ if (CollUtil.isNotEmpty(media)) {
+ media.forEach(item -> {
+ try {
+ Integer sn = NumberUtil.parseInt(StrUtil.isBlank(streamPrefix) ? item.getStream().substring(6) : item.getStream().replace("0" + streamPrefix, ""));
+ usedSn.add(sn);
+ } catch (Exception ignored) {
+ }
+ });
+ }
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java
new file mode 100644
index 0000000..a4125ae
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java
@@ -0,0 +1,48 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:24
+ * @Description:
+ */
+@Data
+public class BaseEventReq {
+ /**
+ * 服务器id,通过配置文件设置
+ */
+ private String mediaServerId;
+ /**
+ * 流应用名
+ */
+ private String app;
+ /**
+ * TCP链接唯一ID
+ */
+ private String id;
+ /**
+ * 播放器ip
+ */
+ private String ip;
+ /**
+ * 播放url参数
+ */
+ private String params;
+ /**
+ * 播放器端口号
+ */
+ private Integer port;
+ /**
+ * 播放的协议,可能是rtsp、rtmp、http
+ */
+ private String schema;
+ /**
+ * 流ID
+ */
+ private String stream;
+ /**
+ * 流虚拟主机
+ */
+ private String vhost;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java
new file mode 100644
index 0000000..04b573f
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java
@@ -0,0 +1,42 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 10:33
+ * @Description: 请求响应基类
+ */
+@Data
+@Accessors(chain = true)
+public class BaseEventResp {
+ /**
+ * code == 0时代表完全成功
+ */
+ private Integer code;
+ /**
+ * 失败提示
+ */
+ private String msg;
+ /**
+ * 失败具体原因
+ */
+ private String err;
+ /**
+ * 该rtsp流是否需要rtsp专有鉴权,空字符串代码不需要鉴权
+ */
+ private String realm;
+ /**
+ * 是否关闭推流或拉流
+ */
+ private Boolean close;
+
+ public BaseEventResp setSuccess(){
+ return this.setCode(0).setMsg("success").setErr("");
+ }
+
+ public static BaseEventResp success() {
+ return new BaseEventResp().setSuccess();
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java
new file mode 100644
index 0000000..1661a94
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java
@@ -0,0 +1,26 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:04
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FlowReportReq extends BaseEventReq {
+ /**
+ * tcp链接维持时间,单位秒
+ */
+ private Integer duration;
+ /**
+ * true为播放器,false为推流器
+ */
+ private Boolean player;
+ /**
+ * 耗费上下行流量总和,单位字节
+ */
+ private Integer totalBytes;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java
new file mode 100644
index 0000000..dfae6d6
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java
@@ -0,0 +1,29 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import cn.hutool.core.lang.Dict;
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:10
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class HttpAccessReq extends BaseEventReq {
+ /**
+ * http客户端请求header
+ */
+ private Dict header;
+ /**
+ * 访问路径是文件还是目录
+ */
+ @JSONField(name = "is_dir")
+ private Boolean isDir;
+ /**
+ * 请求访问的文件或目录
+ */
+ private String path;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java
new file mode 100644
index 0000000..51424d8
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java
@@ -0,0 +1,34 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:15
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Accessors(chain = true)
+public class HttpAccessResp extends BaseEventResp {
+ /**
+ * 该客户端能访问或被禁止的顶端目录,如果为空字符串,则表述为当前目录
+ */
+ private String path;
+ /**
+ * 本次授权结果的有效期,单位秒
+ */
+ private Integer second;
+ /**
+ * 服务器id,通过配置文件设置
+ */
+ private String mediaServerId;
+
+ public static HttpAccessResp success() {
+ HttpAccessResp resp = new HttpAccessResp();
+ resp.setSuccess();
+ return resp.setSecond(600).setPath("");
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java
new file mode 100644
index 0000000..2531052
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:19
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class PlayReq extends BaseEventReq {
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java
new file mode 100644
index 0000000..de324dc
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:57
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class PublishReq extends BaseEventReq{
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java
new file mode 100644
index 0000000..f16d943
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java
@@ -0,0 +1,81 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:27
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class PublishResp extends BaseEventResp {
+ /**
+ * 是否转换成hls协议
+ */
+ @JSONField(name = "enable_hls")
+ private Boolean enableHls;
+ /**
+ * 是否允许mp4录制
+ */
+ @JSONField(name = "enable_mp4")
+ private Boolean enableMp4;
+ /**
+ * 是否转rtsp协议
+ */
+ @JSONField(name = "enable_rtsp")
+ private Boolean enableRtsp;
+ /**
+ * 是否转rtmp/flv协议
+ */
+ @JSONField(name = "enable_rtmp")
+ private Boolean enableRtmp;
+ /**
+ * 是否转http-ts/ws-ts协议
+ */
+ @JSONField(name = "enable_ts")
+ private Boolean enableTs;
+ /**
+ * 是否转http-fmp4/ws-fmp4协议
+ */
+ @JSONField(name = "enable_fmp4")
+ private Boolean enableFmp4;
+ /**
+ * 转协议时是否开启音频
+ */
+ @JSONField(name = "enable_audio")
+ private Boolean enableAudio;
+ /**
+ * 转协议时,无音频是否添加静音aac音频
+ */
+ @JSONField(name = "add_mute_audio")
+ private Boolean addMuteAudio;
+ /**
+ * mp4录制文件保存根目录,置空使用默认
+ */
+ @JSONField(name = "mp4_save_path")
+ private String mp4SavePath;
+ /**
+ * mp4录制切片大小,单位秒
+ */
+ @JSONField(name = "mp4_max_second")
+ private Integer mp4MaxSecond;
+ /**
+ * hls文件保存保存根目录,置空使用默认
+ */
+ @JSONField(name = "hls_save_path")
+ private String hlsSavePath;
+ /**
+ * 断连续推延时,单位毫秒,置空使用配置文件默认值
+ */
+ @JSONField(name = "continue_push_ms")
+ private Long continuePushMs;
+
+ public static PublishResp success() {
+ PublishResp resp = new PublishResp();
+ resp.setSuccess();
+ return resp;
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java
new file mode 100644
index 0000000..50142ec
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java
@@ -0,0 +1,48 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:32
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RecordMp4Req extends BaseEventReq {
+ /**
+ * 文件名
+ */
+ @JSONField(name = "file_name")
+ private String fileName;
+ /**
+ * 文件绝对路径
+ */
+ @JSONField(name = "file_path")
+ private String filePath;
+ /**
+ * 文件大小,单位字节
+ */
+ @JSONField(name = "file_size")
+ private Integer fileSize;
+ /**
+ * 文件所在目录路径
+ */
+ private String folder;
+ /**
+ * 开始录制时间戳
+ */
+ @JSONField(name = "start_time")
+ private Integer startTime;
+ /**
+ * 录制时长,单位秒
+ */
+ @JSONField(name = "time_len")
+ private Integer timeLen;
+ /**
+ * http/rtsp/rtmp点播相对url路径
+ */
+ private String url;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java
new file mode 100644
index 0000000..bcb51e5
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java
@@ -0,0 +1,30 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:35
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RtspAuthReq extends BaseEventReq {
+ /**
+ * 请求的密码是否必须为明文(base64鉴权需要明文密码)
+ */
+ @JSONField(name = "must_no_encrypt")
+ private Boolean mustNoEncrypt;
+ /**
+ * rtsp播放鉴权加密realm
+ */
+ private String realm;
+ /**
+ * 播放用户名
+ */
+ @JSONField(name = "user_name")
+ private String userName;
+
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java
new file mode 100644
index 0000000..2021549
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java
@@ -0,0 +1,30 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:35
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Accessors(chain = true)
+public class RtspAuthResp extends BaseEventResp {
+ /**
+ * 用户密码
+ */
+ private String passwd;
+ /**
+ * 用户密码是否已加密
+ */
+ private Boolean encrypted;
+
+ public static RtspAuthResp success() {
+ RtspAuthResp resp = new RtspAuthResp();
+ resp.setSuccess();
+ return resp.setEncrypted(false);
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java
new file mode 100644
index 0000000..8d6547f
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:33
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RtspRealmReq extends BaseEventReq {
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java
new file mode 100644
index 0000000..d023328
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java
@@ -0,0 +1,16 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import com.dite.znpt.monitor.media.zlm.dto.resp.StatisticResp;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:43
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ServerKeepaliveReq extends BaseEventReq {
+ private StatisticResp data;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java
new file mode 100644
index 0000000..86016eb
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java
@@ -0,0 +1,24 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:37
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ShellLoginReq extends BaseEventReq {
+ /**
+ * 终端登录用户密码
+ */
+ private String passwd;
+ /**
+ * 终端登录用户名
+ */
+ @JSONField(name = "user_name")
+ private String userName;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java
new file mode 100644
index 0000000..cd90632
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java
@@ -0,0 +1,19 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:38
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class StreamChangedReq extends MediaResp {
+ /**
+ * 流注册或注销
+ */
+ private Boolean regist;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java
new file mode 100644
index 0000000..65e34bf
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:40
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class StreamNoneReaderReq extends BaseEventReq {
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java
new file mode 100644
index 0000000..0ead9cc
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java
@@ -0,0 +1,14 @@
+package com.dite.znpt.monitor.media.zlm.dto.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/30 9:42
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class StreamNotFoundReq extends BaseEventReq {
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java
new file mode 100644
index 0000000..445aef0
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java
@@ -0,0 +1,18 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 10:33
+ * @Description: 请求响应基类
+ */
+@Data
+public class BaseReq implements Serializable {
+ /**
+ * api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数
+ */
+ private String secret;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java
new file mode 100644
index 0000000..2eb3044
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java
@@ -0,0 +1,18 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 10:51
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class CloseStreamReq extends StreamReq {
+ /**
+ * 是否强制关闭(有人在观看是否还关闭)
+ */
+ private Integer force;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java
new file mode 100644
index 0000000..54de5d0
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java
@@ -0,0 +1,45 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 12:26
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FFmpegSourceReq extends BaseReq {
+ /**
+ * FFmpeg拉流地址,支持任意协议或格式(只要FFmpeg支持即可)
+ */
+ @JSONField(name = "src_url")
+ private String srcUrl;
+ /**
+ * FFmpeg rtmp推流地址,一般都是推给自己,例如rtmp://127.0.0.1/live/stream_form_ffmpeg
+ */
+ @JSONField(name = "dst_url")
+ private String dstUrl;
+ /**
+ * FFmpeg推流成功超时时间
+ */
+ @JSONField(name = "timeout_ms")
+ private Integer timeoutMs;
+ /**
+ * 是否开启hls录制
+ */
+ @JSONField(name = "enable_hls")
+ private Integer enableHls;
+ /**
+ * 是否开启mp4录制
+ */
+ @JSONField(name = "enable_mp4")
+ private Integer enableMp4;
+ /**
+ * 配置文件中FFmpeg命令参数模板key(非内容),置空则采用默认模板:ffmpeg.cmd
+ */
+ @JSONField(name = "ffmpeg_cmd_key")
+ private String ffmpegCmdKey;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java
new file mode 100644
index 0000000..dd01aa5
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java
@@ -0,0 +1,25 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 17:21
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class GetAllSessionReq extends BaseReq{
+ /**
+ *筛选本机端口,例如筛选rtsp链接:554
+ */
+ @JSONField(name = "local_port")
+ private Integer localPort;
+ /**
+ *筛选客户端ip
+ */
+ @JSONField(name = "peer_ip")
+ private String peerIp;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java
new file mode 100644
index 0000000..6b05d35
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java
@@ -0,0 +1,24 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:04
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class GetMp4RecordFileReq extends StreamReq {
+ /**
+ * 流的录像日期,格式为2020-02-01,如果不是完整的日期,那么是搜索录像文件夹列表,否则搜索对应日期下的mp4文件列表
+ */
+ private String period;
+ /**
+ * 自定义搜索路径,与startRecord方法中的customized_path一样,默认为配置文件的路径
+ */
+ @JSONField(name = "customized_path")
+ private String customizedPath;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java
new file mode 100644
index 0000000..82c454d
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java
@@ -0,0 +1,20 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 17:24
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@AllArgsConstructor
+public class IdReq extends BaseReq {
+ /**
+ * 客户端唯一id,可以通过getAllSession接口获取
+ */
+ private Long id;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java
new file mode 100644
index 0000000..91ebcd9
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java
@@ -0,0 +1,20 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 17:30
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@AllArgsConstructor
+public class KeyReq extends BaseReq {
+ /**
+ * addStreamProxy接口返回的key
+ */
+ private String key;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java
new file mode 100644
index 0000000..4e3dc21
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java
@@ -0,0 +1,29 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:11
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RecordReq extends StreamReq {
+ /**
+ * 0为hls,1为mp4
+ */
+ private Integer type;
+ /**
+ * 录像保存目录
+ */
+ @JSONField(name = "customized_path")
+ private String customizedPath;
+ /**
+ * mp4录像切片时间大小,单位秒,置0则采用配置项
+ */
+ @JSONField(name = "max_second")
+ private Integer maxSecond;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java
new file mode 100644
index 0000000..a7003e7
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java
@@ -0,0 +1,45 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:17
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+public class RtpServerReq extends BaseReq {
+ /**
+ * 接收端口,0则为随机端口
+ */
+ private Integer port;
+ /**
+ * 启用UDP监听的同时是否监听TCP端口
+ */
+ @JSONField(name = "enable_tcp")
+ private Integer enableTcp;
+ /**
+ * 该端口绑定的流ID,该端口只能创建这一个流(而不是根据ssrc创建多个)
+ */
+ @JSONField(name = "stream_id")
+ private String streamId;
+
+ public RtpServerReq(Integer port, Integer enableTcp, String streamId) {
+ this.port = port;
+ this.enableTcp = enableTcp;
+ this.streamId = streamId;
+ }
+
+ public RtpServerReq(Integer enableTcp, String streamId) {
+ this(0, enableTcp, streamId);
+ }
+
+ public RtpServerReq(String streamId) {
+ this(1, streamId);
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java
new file mode 100644
index 0000000..20807df
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java
@@ -0,0 +1,53 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:23
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SendRtpReq extends StreamReq {
+ /**
+ * 推流的rtp的ssrc,指定不同的ssrc可以同时推流到多个服务器
+ */
+ private String ssrc;
+ /**
+ * 目标ip或域名
+ */
+ @JSONField(name = "dst_url")
+ private String dstUrl;
+ /**
+ * 目标端口
+ */
+ @JSONField(name = "dst_port")
+ private Integer dstPort;
+ /**
+ * 是否为udp模式,否则为tcp模式
+ */
+ @JSONField(name = "is_udp")
+ private Integer isUdp;
+ /**
+ * 使用的本机端口,为0或不传时默认为随机端口
+ */
+ @JSONField(name = "src_port")
+ private Integer srcPort;
+ /**
+ * 使用的本机端口,为0或不传时默认为随机端口
+ */
+ private Integer pt;
+ /**
+ * 发送时,rtp的负载类型。为1时,负载为ps;为0时,为es;不传时默认为1
+ */
+ @JSONField(name = "use_ps")
+ private Integer usePs;
+ /**
+ * 当use_ps 为0时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0
+ */
+ @JSONField(name = "only_audio")
+ private Integer onlyAudio;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java
new file mode 100644
index 0000000..548cfd7
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java
@@ -0,0 +1,29 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:14
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SnapReq extends BaseReq {
+ /**
+ * 需要截图的url,可以是本机的,也可以是远程主机的
+ */
+ private String url;
+ /**
+ * 截图失败超时时间,防止FFmpeg一直等待截图
+ */
+ @JSONField(name = "timeout_sec")
+ private Integer timeoutSec;
+ /**
+ * 截图的过期时间,该时间内产生的截图都会作为缓存返回
+ */
+ @JSONField(name = "expire_sec")
+ private Integer expireSec;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java
new file mode 100644
index 0000000..a53d2dc
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java
@@ -0,0 +1,22 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 17:30
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@AllArgsConstructor
+public class StreamIdReq extends BaseReq {
+ /**
+ * RTP的ssrc,16进制字符串或者是流的id(openRtpServer接口指定)
+ */
+ @JSONField(name = "stream_id")
+ private String streamId;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java
new file mode 100644
index 0000000..8481f0a
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java
@@ -0,0 +1,89 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 12:19
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class StreamProxyReq extends StreamReq {
+ /**
+ * 拉流地址,例如rtmp://live.hkstv.hk.lxdns.com/live/hks2 Y
+ */
+ private String url;
+ /**
+ * 拉流重试次数,默认为-1无限重试
+ */
+ @JSONField(name = "retry_count")
+ private Integer retryCount;
+ /**
+ * rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播
+ */
+ @JSONField(name = "rtp_type")
+ private Integer rtpType;
+ /**
+ * 拉流超时时间,单位秒,float类型
+ */
+ @JSONField(name = "timeout_sec")
+ private Integer timeoutSec;
+ /**
+ * 是否转换成hls协议
+ */
+ @JSONField(name = "enable_hls")
+ private Integer enableHls;
+ /**
+ * 是否允许mp4录制
+ */
+ @JSONField(name = "enable_mp4")
+ private Integer enableMp4;
+ /**
+ * 是否转rtsp协议
+ */
+ @JSONField(name = "enable_rtsp")
+ private Integer enableRtsp;
+ /**
+ * 是否转rtmp/flv协议
+ */
+ @JSONField(name = "enable_rtmp")
+ private Integer enableRtmp;
+ /**
+ * 是否转http-ts/ws-ts协议
+ */
+ @JSONField(name = "enable_ts")
+ private Integer enableTs;
+ /**
+ * 是否转http-fmp4/ws-fmp4协议
+ */
+ @JSONField(name = "enable_fmp4")
+ private Integer enableFmp4;
+ /**
+ * 转协议时是否开启音频
+ */
+ @JSONField(name = "enable_audio")
+ private Integer enableAudio;
+ /**
+ * 转协议时,无音频是否添加静音aac音频
+ */
+ @JSONField(name = "add_mute_audio")
+ private Integer addMuteAudio;
+ /**
+ * mp4录制文件保存根目录,置空使用默认
+ */
+ @JSONField(name = "mp4_save_path")
+ private String mp4SavePath;
+ /**
+ * mp4录制切片大小,单位秒
+ */
+ @JSONField(name = "mp4_max_second")
+ private Integer mp4MaxSecond;
+ /**
+ * hls文件保存保存根目录,置空使用默认
+ */
+ @JSONField(name = "hls_save_path")
+ private String hlsSavePath;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java
new file mode 100644
index 0000000..6729ba1
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java
@@ -0,0 +1,35 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:50
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class StreamPusherProxyReq extends StreamReq {
+ /**
+ * 目标转推url,带参数需要自行url转义
+ */
+ @JSONField(name = "dst_url")
+ private String dstUrl;
+ /**
+ * 转推失败重试次数,默认无限重试
+ */
+ @JSONField(name = "retry_count")
+ private Integer retryCount;
+ /**
+ * rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播
+ */
+ @JSONField(name = "rtp_type")
+ private Integer rtpType;
+ /**
+ * 拉流超时时间,单位秒,float类型
+ */
+ @JSONField(name = "timeout_sec")
+ private Float timeoutSec;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java
new file mode 100644
index 0000000..404369a
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java
@@ -0,0 +1,32 @@
+package com.dite.znpt.monitor.media.zlm.dto.req;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 13:05
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Accessors(chain = true)
+public class StreamReq extends BaseReq {
+ /**
+ * 协议,例如 rtsp或rtmp
+ */
+ private String schema;
+ /**
+ * 筛选虚拟主机,例如__defaultVhost__
+ */
+ private String vhost;
+ /**
+ * 筛选应用名,例如 live
+ */
+ private String app;
+ /**
+ * 筛选流id,例如 test
+ */
+ private String stream;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java
new file mode 100644
index 0000000..d6cfa48
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java
@@ -0,0 +1,64 @@
+package com.dite.znpt.monitor.media.zlm.dto.resp;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 10:33
+ * @Description: 请求响应基类
+ */
+@Data
+public class BaseResp {
+ /**
+ * code == 0时代表完全成功
+ */
+ private Integer code;
+ /**
+ * 失败提示
+ */
+ private String msg;
+ /**
+ * 失败具体原因
+ */
+ private String result;
+ /**
+ * 返回数据
+ */
+ private String data;
+ /**
+ * 配置项变更个数
+ */
+ private Integer changed;
+ /**
+ * false:未录制,true:正在录制
+ */
+ private Boolean status;
+ /**
+ * 接收端口,方便获取随机端口号
+ */
+ private Integer port;
+ /**
+ * 是否找到记录并关闭
+ */
+ private Integer hit;
+
+ public String getMsg() {
+ return StrUtil.format("{}:{}", code, msg);
+ }
+
+ public Boolean isSuccess() {
+ return code == 0;
+ }
+
+ public T getData(Class clazz) {
+ return JSON.parseObject(data, clazz);
+ }
+
+ public List getList(Class clazz) {
+ return JSON.parseArray(data, clazz);
+ }
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java
new file mode 100644
index 0000000..aa0bf8e
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java
@@ -0,0 +1,25 @@
+package com.dite.znpt.monitor.media.zlm.dto.resp;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 11:59
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class CloseStreamResp extends BaseResp {
+ /**
+ * 筛选命中的流个数
+ */
+ @JSONField(name = "count_hit")
+ private Integer countHit;
+ /**
+ * 被关闭的流个数,可能小于count_hit
+ */
+ @JSONField(name = "count_closed")
+ private Integer countClosed;
+}
diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java
new file mode 100644
index 0000000..942b099
--- /dev/null
+++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java
@@ -0,0 +1,61 @@
+package com.dite.znpt.monitor.media.zlm.dto.resp;
+
+import com.dite.znpt.monitor.media.zlm.dto.event.BaseEventReq;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author: huise23
+ * @Date: 2022/8/29 11:18
+ * @Description:
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MediaResp extends BaseEventReq implements Serializable {
+ private static final long serialVersionUID = -8710934021370904914L;
+
+ /**
+ * 本协议观看人数
+ */
+ private Integer readerCount;
+ /**
+ * 观看总人数,包括hls/rtsp/rtmp/http-flv/ws-flv
+ */
+ private Integer totalReaderCount;
+ /**
+ * 客户端和服务器网络信息,可能为null类型
+ */
+ private OriginSock originSock;
+ /**
+ * 产生源类型,包括 unknown = 0, rtmp_push = 1, rtsp_push = 2, rtp_push = 3, pull = 4, ffmpeg_pull = 5, mp4_vod = 6, device_chn = 7
+ */
+ private Integer originType;
+ /**
+ * 产生源类型
+ */
+ private String originTypeStr;
+ /**
+ * 产生源的url
+ */
+ private String originUrl;
+ /**
+ * GMT unix系统时间戳,单位秒
+ */
+ private Long createStamp;
+ /**
+ * 存活时间,单位秒
+ */
+ private Long aliveSecond;
+ /**
+ * 数据产生速度,单位byte/s
+ */
+ private Long bytesSpeed;
+ /**
+ * 音视频轨道
+ */
+ private List