添加对海康平台录像回放的兼容,修复录像信息发送失败, 级联平台支持开启rtcp保活
15个文件已修改
219 ■■■■ 已修改文件
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 79 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/platformEdit.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
@@ -113,7 +113,6 @@
    /**
     * RTCP流保活
     * TODO 预留, 暂不实现
     */
    @Schema(description = "RTCP流保活")
    private boolean rtcp;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -103,7 +103,7 @@
     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */
    void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event errorEvent);
    void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
    /**
     * 请求历史媒体下载
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -456,7 +456,7 @@
    @Override
    public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                                  String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
                                  SipSubscribe.Event errorEvent) {
                                  SipSubscribe.Event okEvent,SipSubscribe.Event errorEvent) {
        try {
            logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
@@ -535,10 +535,11 @@
                    });
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc());
            transmitRequest(device, request, errorEvent, okEvent -> {
                ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
            transmitRequest(device, request, errorEvent, event -> {
                ResponseEvent responseEvent = (ResponseEvent) event.event;
                streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
                streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
                streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), event.dialog);
                okEvent.response(event);
            });
            if (inviteStreamCallback != null) {
                inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
@@ -115,6 +115,11 @@
            param.put("pt", sendRtpItem.getPt());
            param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
            param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
            if (!sendRtpItem.isTcp() && parentPlatform.isRtcp()) {
                // 开启rtcp保活
                param.put("udp_rtcp_timeout", "1");
            }
            if (mediaInfo == null) {
                RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
                        sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
@@ -98,8 +98,8 @@
                    param.put("ssrc",sendRtpItem.getSsrc());
                    logger.info("收到bye:停止向上级推流:" + streamId);
                    MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                    zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
                    redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
                    zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
                    int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
                    if (totalReaderCount <= 0) {
                        logger.info("收到bye: {} 无其它观看者,通知设备停止推流", streamId);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -563,6 +563,7 @@
            responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
        } else if ("push".equals(gbStream.getStreamType())) {
            if (!platform.isStartOfflinePush()) {
                // 平台设置中关闭了拉起离线的推流则直接回复
                responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable");
                return;
            }
@@ -599,7 +600,7 @@
                            app, stream, channelId, mediaTransmissionTCP);
                    if (sendRtpItem == null) {
                        logger.warn("服务器端口资源不足");
                        logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足");
                        try {
                            responseAck(evt, Response.BUSY_HERE);
                        } catch (SipException e) {
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
@@ -50,7 +50,7 @@
        if (mediaServerItem == null) {
            return null;
        }
        if (mediaServerItem.getRecordAssistPort() > 0) {
        if (mediaServerItem.getRecordAssistPort() <= 0) {
            logger.warn("未启用Assist服务");
            return null;
        }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -19,8 +19,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.PostMapping;
@@ -544,6 +542,8 @@
                        for (SendRtpItem sendRtpItem : sendRtpItems) {
                            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
                            commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
                            redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
                                    sendRtpItem.getCallId(), sendRtpItem.getStreamId());
                        }
                    }
                }
@@ -573,13 +573,19 @@
            return ret;
        }else {
            StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
            if (streamProxyItem != null && streamProxyItem.isEnable_remove_none_reader()) {
            if (streamProxyItem != null ) {
                if (streamProxyItem.isEnable_remove_none_reader()) {
                    // 无人观看自动移除
                ret.put("close", true);
                streamProxyService.del(app, streamId);
                String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
                logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除",  app, streamId, url);
                }else if (streamProxyItem.isEnable_disable_none_reader()) {
                    // 无人观看停用
                    ret.put("close", true);
            }else {
                ret.put("close", false);
                }
            }
            return ret;
        }
@@ -649,6 +655,39 @@
        return ret;
    }
    /**
     * 发送rtp(startSendRtp)被动关闭时回调
     */
    @ResponseBody
    @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
    public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody JSONObject jsonObject){
        logger.info("[ ZLM HOOK ]on_send_rtp_stopped API调用,参数:" + jsonObject);
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
        // 查找对应的上级推流,发送停止
        String app = jsonObject.getString("app");
        if (!"rtp".equals(app)) {
            return ret;
        }
        String stream = jsonObject.getString("stream");
        List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(stream);
        if (sendRtpItems.size() > 0) {
            for (SendRtpItem sendRtpItem : sendRtpItems) {
                ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
                commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
                redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
                        sendRtpItem.getCallId(), sendRtpItem.getStreamId());
            }
        }
        return ret;
    }
    private Map<String, String> urlParamToMap(String params) {
        HashMap<String, String> map = new HashMap<>();
        if (ObjectUtils.isEmpty(params)) {
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java
@@ -37,6 +37,9 @@
    private boolean enable_mp4;
    @Schema(description = "是否 无人观看时删除")
    private boolean enable_remove_none_reader;
    @Schema(description = "是否 无人观看时不启用")
    private boolean enable_disable_none_reader;
    @Schema(description = "上级平台国标ID")
    private String platformGbId;
    @Schema(description = "创建时间")
@@ -177,4 +180,11 @@
        this.enable_remove_none_reader = enable_remove_none_reader;
    }
    public boolean isEnable_disable_none_reader() {
        return enable_disable_none_reader;
    }
    public void setEnable_disable_none_reader(boolean enable_disable_none_reader) {
        this.enable_disable_none_reader = enable_disable_none_reader;
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -531,6 +531,7 @@
        param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
        param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
        param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
        param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex));
        if (mediaServerItem.getRecordAssistPort() > 0) {
            param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort()));
        }else {
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
@@ -73,7 +73,6 @@
                }else {
                    streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null);
                }
            }
        }
        return streamInfo;
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -291,7 +291,7 @@
                }
                logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
                if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
                    logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
                    logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
                        // ssrc 不可用
@@ -441,9 +441,19 @@
            resultHolder.exist(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid);
        }, userSetting.getPlayTimeout());
        cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
                (InviteStreamInfo inviteStreamInfo) -> {
                    logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
        SipSubscribe.Event errorEvent = event -> {
            dynamicTask.stop(playBackTimeOutTaskKey);
            requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)));
            playBackResult.setCode(ErrorCode.ERROR100.getCode());
            playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
            playBackResult.setData(requestMessage);
            playBackResult.setEvent(event);
            playBackCallback.call(playBackResult);
            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
        };
        InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
            logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
                    dynamicTask.stop(playBackTimeOutTaskKey);
                    StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
                    if (streamInfo == null) {
@@ -462,16 +472,61 @@
                    playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
                    playBackResult.setResponse(inviteStreamInfo.getResponse());
                    playBackCallback.call(playBackResult);
                }, event -> {
                    dynamicTask.stop(playBackTimeOutTaskKey);
                    requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)));
                    playBackResult.setCode(ErrorCode.ERROR100.getCode());
                    playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
                    playBackResult.setData(requestMessage);
                    playBackResult.setEvent(event);
                    playBackCallback.call(playBackResult);
        };
        cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
                hookEvent, eventResult -> {
                    if (eventResult.type == SipSubscribe.EventResultType.response) {
                        ResponseEvent responseEvent = (ResponseEvent)eventResult.event;
                        String contentString = new String(responseEvent.getResponse().getRawContent());
                        // 获取ssrc
                        int ssrcIndex = contentString.indexOf("y=");
                        // 检查是否有y字段
                        if (ssrcIndex >= 0) {
                            //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
                            String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
                            // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
                            if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
                                return;
                            }
                            logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
                            if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
                                logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
                                if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
                                    // ssrc 不可用
                                    // 释放ssrc
                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
                                    eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
                                    eventResult.statusCode = 400;
                                    errorEvent.response(eventResult);
                                    return;
                                }
                                // 单端口模式streamId也有变化,需要重新设置监听
                                if (!mediaServerItem.isRtpEnable()) {
                                    // 添加订阅
                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
                                    subscribe.removeSubscribe(hookSubscribe);
                                    hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
                                    subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
                                        dynamicTask.stop(playBackTimeOutTaskKey);
                                        // hook响应
                                        onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
                                        hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
                });
                                }
                                // 关闭rtp server
                                mediaServerService.closeRTPServer(device.getDeviceId(), channelId, ssrcInfo.getStream());
                                // 重新开启ssrc server
                                mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
                            }
                        }
                    }
                }, errorEvent);
        return result;
    }
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -236,4 +236,6 @@
    void sendStreamPushRequestedMsgForStatus();
    List<SendRtpItem> querySendRTPServerByChnnelId(String channelId);
    List<SendRtpItem> querySendRTPServerByStream(String stream);
}
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -388,6 +388,24 @@
    }
    @Override
    public List<SendRtpItem> querySendRTPServerByStream(String stream) {
        if (stream == null) {
            return null;
        }
        String platformGbId = "*";
        String callId = "*";
        String channelId = "*";
        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetting.getServerId() + "_" + platformGbId
                + "_" + channelId + "_" + stream + "_" + callId;
        List<Object> scan = RedisUtil.scan(key);
        List<SendRtpItem> result = new ArrayList<>();
        for (Object o : scan) {
            result.add((SendRtpItem) RedisUtil.get((String) o));
        }
        return result;
    }
    @Override
    public List<SendRtpItem> querySendRTPServer(String platformGbId) {
        if (platformGbId == null) {
            platformGbId = "*";
web_src/src/components/dialog/platformEdit.vue
@@ -37,13 +37,13 @@
              <el-form-item label="本地端口" prop="devicePort">
                <el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
              </el-form-item>
              <el-form-item label="SIP认证用户名" prop="username">
                <el-input v-model="platform.username"></el-input>
              </el-form-item>
            </el-form>
          </el-col>
          <el-col :span="12">
            <el-form ref="platform2" :rules="rules" :model="platform" label-width="160px">
              <el-form-item label="SIP认证用户名" prop="username">
                <el-input v-model="platform.username"></el-input>
              </el-form-item>
              <el-form-item label="行政区划" prop="administrativeDivision">
                <el-input v-model="platform.administrativeDivision" clearable></el-input>
              </el-form-item>
@@ -79,7 +79,7 @@
                </el-select>
              </el-form-item>
              <el-form-item label="目录结构" prop="treeType" >
                <el-select v-model="platform.treeType" style="width: 100%" >
                <el-select v-model="platform.treeType" style="width: 100%" @change="treeTypeChange">
                  <el-option key="WGS84" label="行政区划" value="CivilCode"></el-option>
                  <el-option key="GCJ02" label="业务分组" value="BusinessGroup"></el-option>
                </el-select>
@@ -98,6 +98,7 @@
                <el-checkbox label="启用" v-model="platform.enable" @change="checkExpires"></el-checkbox>
                <el-checkbox label="云台控制" v-model="platform.ptz"></el-checkbox>
                <el-checkbox label="拉起离线推流" v-model="platform.startOfflinePush"></el-checkbox>
                <el-checkbox label="RTCP保活" v-model="platform.rtcp" @change="rtcpCheckBoxChange"></el-checkbox>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" @click="onSubmit">{{
@@ -251,21 +252,7 @@
    },
    onSubmit: function () {
      if (this.onSubmit_text === "保存") {
        this.$confirm("修改目录结构会导致关联目录与通道数据被清空", '提示', {
          dangerouslyUseHTMLString: true,
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          center: true,
          type: 'warning'
        }).then(() => {
          this.saveForm()
        }).catch(() => {
        });
      }else {
        this.saveForm()
      }
    },
    saveForm: function (){
      this.$axios({
@@ -343,6 +330,22 @@
      if (this.platform.enable && this.platform.expires == "0") {
        this.platform.expires = "300";
      }
    },
    rtcpCheckBoxChange: function (result){
      if (result) {
        this.$message({
          showClose: true,
          message: "开启RTCP保活需要上级平台支持,可以避免无效推流",
          type: "warning",
        });
      }
    },
    treeTypeChange: function (){
      this.$message({
        showClose: true,
        message: "修改目录结构会导致关联目录与通道数据被清空,保存后生效",
        type: "warning",
      });
    }
  },
};