liusuyi
2026-06-02 f652169cc5d6501511eece5df57b7b396fd7a7ab
ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java
@@ -8,10 +8,17 @@
import com.ard.common.core.domain.RtpServerParam;
import com.ard.common.core.enums.LiveStreamType;
import com.ard.common.core.utils.DateUtils;
import com.ard.common.core.utils.file.FileMultipartFile;
import com.ard.gb28181.api.RemoteGb28181Service;
import com.ard.gb28181.api.domain.Device;
import com.ard.qs.api.RemoteQsDeviceService;
import com.ard.qs.api.domain.QsDevice;
import com.ard.zlm.domain.StreamChannel;
import com.ard.zlm.service.ZlmStreamService;
import com.ard.zlm.domain.StreamChannel;
import com.ard.system.api.RemoteFileService;
import com.ard.system.api.domain.SysFile;
import com.ard.work.api.RemoteCameraService;
import com.ard.work.api.RemoteChannelService;
import com.ard.work.api.domian.ArdChannel;
import com.ard.zlm.api.domain.*;
import com.ard.zlm.api.hook.OriginType;
import com.ard.zlm.common.InviteErrorCode;
@@ -33,6 +40,7 @@
import com.ard.zlm.service.*;
import com.ard.zlm.session.SSRCFactory;
import com.ard.zlm.utils.ZLMRESTfulUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -46,7 +54,12 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
/**
@@ -60,58 +73,62 @@
@Slf4j
@Service
public class MediaServerServiceImpl implements IMediaServerService {
    @Autowired
    @Resource
    private RemoteFileService remoteFileService;
    @Resource
    private MediaServerMapper mediaServerMapper;
    @Autowired
    @Resource
    private UserSetting userSetting;
    @Autowired
    @Resource
    private Map<String, IMediaNodeServerService> nodeServerServiceMap;
    @Autowired
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    @Resource
    private RedisTemplate redisTemplate;
    @Autowired
    @Resource
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    @Resource
    private MediaConfig mediaConfig;
    @Autowired
    @Resource
    private IMediaNodeServerService mediaNodeServerService;
    @Autowired
    @Resource
    private DynamicTask dynamicTask;
    @Autowired
    @Resource
    private HookSubscribe subscribe;
    @Autowired
    private RemoteQsDeviceService remoteQsDeviceService;
    @Resource
    private ZlmStreamService zlmStreamService;
    @Autowired
    @Resource
    private RemoteChannelService remoteChannelService;
    @Resource
    private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    @Resource
    private SSRCFactory ssrcFactory;
    @Autowired
    @Resource
    @Lazy
    private IReceiveRtpServerService receiveRtpServerService;
    @Autowired
    @Resource
    @Lazy
    private IInviteStreamService inviteStreamService;
    @Autowired
    @Resource
    private IZlmCloudRecordService zlmCloudRecordService;
    @Autowired
    @Resource
    private RemoteGb28181Service remoteGb28181Service;
@@ -168,7 +185,7 @@
        }
        redisCatchStorage.updateStreamAuthorityInfo(event.getApp(), event.getStream(), streamAuthorityInfo);
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceStream("pull_" + event.getApp() + "_" + event.getStream(),
        R<StreamChannel> r = zlmStreamService.getQsDeviceStream("pull_" + event.getApp() + "_" + event.getStream(),
                SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            log.error("获取设备信息失败,stream:{}", event.getStream());
@@ -176,7 +193,7 @@
        }
        if (r.getData() == null) {
            r = remoteQsDeviceService.getQsDeviceStream(event.getStream(), SecurityConstants.INNER);
            r = zlmStreamService.getQsDeviceStream(event.getStream(), SecurityConstants.INNER);
            if (r.getCode() != Constants.SUCCESS) {
                log.error("获取设备信息失败,stream:{}", event.getStream());
                return;
@@ -184,10 +201,9 @@
        }
        if (r.getData() == null) {
            QsDevice device = new QsDevice();
            device.setDeviceStatus("ON");
            StreamChannel device = new StreamChannel();
            device.setMediaServerId(mediaInfo.getMediaServer().getId());
            device.setDeviceName("推流设备_" + event.getApp() + "_" + event.getStream());
            device.setChannelName("推流设备_" + event.getApp() + "_" + event.getStream());
            device.setType(LiveStreamType.PUSH.getCode());
            device.setStatus("ENABLE");
            device.setStreamStatus("1");
@@ -197,7 +213,7 @@
            String filePath = snapOnPlay(mediaInfo.getMediaServer(), event.getApp(), event.getStream());
            device.setSnap(filePath);
            R<Boolean> addR = remoteQsDeviceService.addQsDevice(device, SecurityConstants.INNER);
            R<Boolean> addR = zlmStreamService.addQsDevice(device, SecurityConstants.INNER);
            if (addR.getCode() != Constants.SUCCESS) {
                throw new RuntimeException("添加推流设备设备失败" + event.getApp() + "_" + event.getStream());
            }
@@ -206,15 +222,14 @@
                throw new RuntimeException("添加推流设备设备失败" + event.getApp() + "_" + event.getStream());
            }
        } else {
            QsDevice device = new QsDevice();
            device.setDeviceStatus("ON");
            StreamChannel device = new StreamChannel();
            device.setMediaServerId(mediaInfo.getMediaServer().getId());
            device.setStreamKey(r.getData().getDeviceCode());
            device.setStreamStatus("1");
            device.setId(r.getData().getId());
            String filePath = snapOnPlay(mediaInfo.getMediaServer(), event.getApp(), event.getStream());
            device.setSnap(filePath);
            R<Boolean> updateR = remoteQsDeviceService.updateQsDevice(device, SecurityConstants.INNER);
            R<Boolean> updateR = zlmStreamService.updateQsDevice(device, SecurityConstants.INNER);
            if (updateR.getCode() != Constants.SUCCESS) {
                throw new RuntimeException("修改推流设备设备失败" + event.getApp() + "_" + event.getStream());
            }
@@ -252,7 +267,7 @@
            if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {
                inviteStreamService.removeInviteInfo(inviteInfo);
                R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(Long.valueOf(inviteInfo.getDeviceId()),
                R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(Long.valueOf(inviteInfo.getDeviceId()),
                        SecurityConstants.INNER);
                if (r.getCode() != Constants.SUCCESS) {
                    return;
@@ -284,7 +299,7 @@
        }
        if ("video_file".equals(event.getApp())) {
            R<QsDevice> r = remoteQsDeviceService.getQsDeviceStream(event.getStream(), SecurityConstants.INNER);
            R<StreamChannel> r = zlmStreamService.getQsDeviceStream(event.getStream(), SecurityConstants.INNER);
            if (r.getCode() != Constants.SUCCESS) {
                return;
            }
@@ -293,12 +308,12 @@
                return;
            }
            QsDevice qsDevice = new QsDevice();
            qsDevice.setId(r.getData().getId());
            qsDevice.setStreamKey("");
            qsDevice.setMediaServerId("");
            qsDevice.setStreamStatus("0");
            R<Boolean> qsDevicer = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
            StreamChannel streamChannel = new StreamChannel();
            streamChannel.setId(r.getData().getId());
            streamChannel.setStreamKey("");
            streamChannel.setMediaServerId("");
            streamChannel.setStreamStatus("0");
            R<Boolean> qsDevicer = zlmStreamService.updateQsDevice(streamChannel, SecurityConstants.INNER);
            if (qsDevicer.getCode() != Constants.SUCCESS) {
                log.error("更新设备失败");
            }
@@ -326,7 +341,7 @@
            redisCatchStorage.removePushListItem(event.getApp(), event.getStream(), event.getMediaServer().getId());
        }
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceStream("pull_" + event.getApp() + "_" + event.getStream(),
        R<StreamChannel> r = zlmStreamService.getQsDeviceStream("pull_" + event.getApp() + "_" + event.getStream(),
                SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            log.error("获取设备信息失败,stream:{}", event.getStream());
@@ -334,7 +349,7 @@
        }
        if (r.getData() == null) {
            r = remoteQsDeviceService.getQsDeviceStream(event.getStream(), SecurityConstants.INNER);
            r = zlmStreamService.getQsDeviceStream(event.getStream(), SecurityConstants.INNER);
            if (r.getCode() != Constants.SUCCESS) {
                log.error("获取设备信息失败,stream:{}", event.getStream());
                return;
@@ -344,13 +359,12 @@
        if (r.getData() == null) {
            return;
        }
        QsDevice device = new QsDevice();
        device.setDeviceStatus("OFFLINE");
        StreamChannel device = new StreamChannel();
        device.setMediaServerId("");
        device.setStreamKey("");
        device.setStreamStatus("0");
        device.setId(r.getData().getId());
        R<Boolean> updateR = remoteQsDeviceService.updateQsDevice(device, SecurityConstants.INNER);
        R<Boolean> updateR = zlmStreamService.updateQsDevice(device, SecurityConstants.INNER);
        if (updateR.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("修改推流设备设备失败" + event.getApp() + "_" + event.getStream());
        }
@@ -624,18 +638,13 @@
            callback.run(InviteErrorCode.FAIL.getCode(), "无可用的节点", null);
            return;
        }
        R<QsDevice> devicer = remoteQsDeviceService.getQsDeviceInfo(streamPullPlay.getDeviceId(), SecurityConstants
        .INNER);
        if (devicer.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("获取设备信息失败" + streamPullPlay.getDeviceId());
        R<ArdChannel> channel = remoteChannelService.getInfo(streamPullPlay.getChannelId(), SecurityConstants.INNER);
        if (channel.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("获取通道信息失败" + streamPullPlay.getChannelId());
        }
        if (devicer.getData() == null) {
            throw new RuntimeException("设备不存在" + streamPullPlay.getDeviceId());
        }
        if ("OFFLINE".equals(devicer.getData().getDeviceStatus())) {
            throw new RuntimeException("设备不在线" + streamPullPlay.getDeviceId());
        if (channel.getData() == null) {
            throw new RuntimeException("通道不存在" + streamPullPlay.getChannelId());
        }
        StreamInfo stream = getStreamInfoByAppAndStreamWithCheck(streamPullPlay.getApp(), streamPullPlay.getStream(),
@@ -668,10 +677,10 @@
            callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
            subscribe.removeSubscribe(rtpHook);
            QsDevice qsDevice = new QsDevice();
            qsDevice.setId(streamPullPlay.getDeviceId());
            qsDevice.setSnap(filePath);
            R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
            ArdChannel ardChannel = new ArdChannel();
            ardChannel.setId(streamPullPlay.getChannelId());
            ardChannel.setSnapUrl(filePath);
            R<Boolean> r = remoteChannelService.update(ardChannel, SecurityConstants.INNER);
            if (r.getCode() != Constants.SUCCESS) {
                throw new RuntimeException("更新设备失败");
            }
@@ -686,12 +695,12 @@
        }
        String key = mediaNodeServerService.startProxy(mediaServer, streamPullPlay);
        QsDevice qsDevice = new QsDevice();
        qsDevice.setId(streamPullPlay.getDeviceId());
        qsDevice.setStreamKey(key);
        qsDevice.setMediaServerId(mediaServer.getId());
        qsDevice.setStreamStatus("1");
        R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
        ArdChannel ardChannel = new ArdChannel();
        ardChannel.setId(streamPullPlay.getChannelId());
        ardChannel.setStreamKey(key);
        ardChannel.setMediaServerId(mediaServer.getId());
        //ardChannel.setStreamStatus("1");
        R<Boolean> r = remoteChannelService.update(ardChannel, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            log.error("更新设备失败");
            callback.run(InviteErrorCode.FAIL.getCode(), "更新设备失败", null);
@@ -767,17 +776,39 @@
        stopProxy(mediaServer, streamPullPlay.getStreamKey());
        QsDevice qsDevice = new QsDevice();
        qsDevice.setId(streamPullPlay.getDeviceId());
        qsDevice.setStreamKey("");
        qsDevice.setMediaServerId("");
        qsDevice.setStreamStatus("0");
        R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
        ArdChannel ardChannel = new ArdChannel();
        ardChannel.setId(streamPullPlay.getChannelId());
        ardChannel.setStreamKey("");
        ardChannel.setMediaServerId("");
       // ardChannel.setStreamStatus("0");
        R<Boolean> r = remoteChannelService.update(ardChannel, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("更新设备失败");
        }
    }
    /**
     * 点播成功时调用截图
     *
     * @param mediaServer media
     * @param app         app
     * @param stream      流id
     */
    //@Override
//    public String snapOnPlay1(ZlmMediaServer mediaServer, String app, String stream) {
//        String fileName = app + "-" + stream + ".jpg";
//        // 请求截图
//        log.info("[请求截图]: " + fileName);
//
//        IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
//        if (mediaNodeServerService == null) {
//            log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
//            throw new RuntimeException("[getSnap] 失败, mediaServer的类型: " + mediaServer.getType() + ",未找到对应的实现类");
//        }
//        String filePath = fileDomain + filePrefix + "/snap/" + fileName;
//        mediaNodeServerService.getSnap(mediaServer, app, stream, 30, 300, this.filePath + "/snap", fileName);
//        return filePath;
//    }
    /**
     * 点播成功时调用截图
     *
@@ -796,11 +827,53 @@
            log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
            throw new RuntimeException("[getSnap] 失败, mediaServer的类型: " + mediaServer.getType() + ",未找到对应的实现类");
        }
        String filePath = fileDomain + filePrefix + "/snap/" + fileName;
        mediaNodeServerService.getSnap(mediaServer, app, stream, 15, 1, this.filePath + "/snap", fileName);
        return filePath;
    }
        // 生成临时文件路径(用于 FFmpeg 截图)
        String tempDir = System.getProperty("java.io.tmpdir") + "/snap_temp/";
        File tempDirFile = new File(tempDir);
        if (!tempDirFile.exists()) {
            tempDirFile.mkdirs();
        }
        String tempFilePath = tempDir + fileName;
        try {
            // 1. 获取截图到临时文件
            mediaNodeServerService.getSnap(mediaServer, app, stream, 30, 300, tempDir, fileName);
            // 2. 检查临时文件是否存在
            File tempFile = new File(tempFilePath);
            if (!tempFile.exists()) {
                log.error("[截图失败] 临时文件不存在: {}", tempFilePath);
                return null;
            }
            // 3. 使用 FileMultipartFile 转换为 MultipartFile
            MultipartFile multipartFile = new FileMultipartFile(
                    tempFile,
                    fileName,
                    "image/jpeg"
            );
            // 3. 调用文件上传接口,上传到 MinIO(bucketName 可以固定为 "snap" 或动态传入)
            R<SysFile> result = remoteFileService.upload(multipartFile, "snap");
            // 4. 清理临时文件
            Files.deleteIfExists(Paths.get(tempFilePath));
            // 5. 返回 URL
            if (result.getCode() == Constants.SUCCESS && result.getData() != null) {
                String url = result.getData().getUrl();
                log.info("[截图上传成功] URL: {}", url);
                return url;
            } else {
                log.error("[截图上传失败] {}", result.getMsg());
                return null;
            }
        } catch (Exception e) {
            log.error("[截图失败] ", e);
            return null;
        }
    }
    /**
     * 获取截图
     *
@@ -915,7 +988,7 @@
            return;
        }
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(rtpServerParam.getId(), SecurityConstants.INNER);
        R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(rtpServerParam.getId(), SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            callback.run(InviteErrorCode.FAIL.getCode(), "获取设备信息失败" + rtpServerParam.getId(), null);
            return;
@@ -925,7 +998,7 @@
            return;
        }
        if ("OFFLINE".equals(r.getData().getDeviceStatus())) {
        if (!Boolean.TRUE.equals(Boolean.TRUE.equals(r.getData().getDeviceOnLine()))) {
            callback.run(InviteErrorCode.FAIL.getCode(), "设备不在线" + rtpServerParam.getId(), null);
            return;
        }
@@ -945,11 +1018,10 @@
     * @param rtpServerParam 创建rtp端口请求参数
     * @param device         设备信息
     * @param ssrc           ssrc
     * @param record         是否录制
     * @param callback       回调
     * @return
     */
    private SSRCInfo play(ZlmMediaServer mediaServer, RTPServerParam rtpServerParam, QsDevice device, String ssrc,
    private SSRCInfo play(ZlmMediaServer mediaServer, RTPServerParam rtpServerParam, StreamChannel device, String ssrc,
                          ErrorCallback<StreamInfo> callback) {
        // 获取点播的状态信息
        InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY,
@@ -1048,13 +1120,13 @@
                    String filePath = snapOnPlay(streamInfo.getMediaServer(), streamInfo.getApp(),
                            streamInfo.getStream());
                    QsDevice qsDevice = new QsDevice();
                    qsDevice.setId(rtpServerParam.getId());
                    qsDevice.setStreamKey(rtpServerParam.getStreamId());
                    qsDevice.setMediaServerId(mediaServer.getId());
                    qsDevice.setStreamStatus("1");
                    qsDevice.setSnap(filePath);
                    R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
                    StreamChannel streamChannel = new StreamChannel();
                    streamChannel.setId(rtpServerParam.getId());
                    streamChannel.setStreamKey(rtpServerParam.getStreamId());
                    streamChannel.setMediaServerId(mediaServer.getId());
                    streamChannel.setStreamStatus("1");
                    streamChannel.setSnap(filePath);
                    R<Boolean> r = zlmStreamService.updateQsDevice(streamChannel, SecurityConstants.INNER);
                    if (r.getCode() != Constants.SUCCESS) {
                        throw new RuntimeException("更新设备失败");
                    }
@@ -1142,14 +1214,14 @@
     */
    @Override
    public void stopRtpPlay(RTPServerParam rtpServerParam) {
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(rtpServerParam.getId(), SecurityConstants.INNER);
        R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(rtpServerParam.getId(), SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("获取设备信息失败");
        }
        if (r.getData() == null) {
            throw new RuntimeException("设备不存在");
        }
        QsDevice device = r.getData();
        StreamChannel device = r.getData();
        String mediaServerId = device.getMediaServerId();
        ZlmMediaServer mediaServer = getOne(mediaServerId);
@@ -1166,12 +1238,12 @@
            }
        }
        QsDevice qsDevice = new QsDevice();
        qsDevice.setId(rtpServerParam.getId());
        qsDevice.setStreamKey("");
        qsDevice.setMediaServerId("");
        qsDevice.setStreamStatus("0");
        R<Boolean> devicer = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
        StreamChannel streamChannel = new StreamChannel();
        streamChannel.setId(rtpServerParam.getId());
        streamChannel.setStreamKey("");
        streamChannel.setMediaServerId("");
        streamChannel.setStreamStatus("0");
        R<Boolean> devicer = zlmStreamService.updateQsDevice(streamChannel, SecurityConstants.INNER);
        if (devicer.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("更新设备失败");
        }
@@ -1212,7 +1284,7 @@
            return;
        }
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(id, SecurityConstants.INNER);
        R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(id, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            callback.run(InviteErrorCode.FAIL.getCode(), "获取设备信息失败" + id, null);
            return;
@@ -1222,11 +1294,11 @@
            return;
        }
        if ("OFFLINE".equals(r.getData().getDeviceStatus())) {
        if (!Boolean.TRUE.equals(Boolean.TRUE.equals(r.getData().getDeviceOnLine()))) {
            throw new RuntimeException("设备不在线" + id);
        }
        QsDevice device = r.getData();
        StreamChannel device = r.getData();
        String videoPath = convertUrlToPath(device.getLiveAddress(), this.fileDomain, this.filePrefix, this.filePath);
        loadMP4File(mediaServer, "video_file", device.getDeviceCode(), id, videoPath, ((code, msg, streamInfo) -> {
@@ -1241,7 +1313,7 @@
     */
    @Override
    public void closeStreams(Long id) {
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(id, SecurityConstants.INNER);
        R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(id, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("获取设备信息失败");
        }
@@ -1249,7 +1321,7 @@
            throw new RuntimeException("设备不存在");
        }
        QsDevice device = r.getData();
        StreamChannel device = r.getData();
        ZlmMediaServer mediaServer = getOne(device.getMediaServerId());
        if (mediaServer == null) {
@@ -1420,12 +1492,12 @@
    /**
     * 开始播放
     *
     * @param device   设备信息
     * @param ardChannel   通道信息
     * @param record   是否录制
     * @param callback 回调
     */
    @Override
    public void play(QsDevice device, Boolean record, ErrorCallback<StreamInfo> callback) {
    public void play(ArdChannel ardChannel, Boolean record, ErrorCallback<StreamInfo> callback) {
        ZlmMediaServer mediaServer = getMediaServerForMinimumLoad(null);
        if (mediaServer == null) {
@@ -1434,57 +1506,57 @@
        }
        // 播放海康sdk/播放海康isup/播放大华sdk
        if (LiveStreamType.HIK_SDK.getCode().equals(device.getType()) || LiveStreamType.HIK_ISUP.getCode().equals(device.getType()) || LiveStreamType.DAHUA_SDK.getCode().equals(device.getType())) {
            RTPServerParam rtpServerParam = new RTPServerParam();
            if (LiveStreamType.HIK_SDK.getCode().equals(device.getType())) {
                rtpServerParam.setApp("haikang");
            } else if (LiveStreamType.HIK_ISUP.getCode().equals(device.getType())) {
                rtpServerParam.setApp("haikang_isup");
            } else if (LiveStreamType.DAHUA_SDK.getCode().equals(device.getType())) {
                rtpServerParam.setApp("dahua");
            }
            rtpServerParam.setStreamId(device.getDeviceCode());
            rtpServerParam.setTcpMode(0);
            rtpServerParam.setType(device.getType());
            rtpServerParam.setId(device.getId());
            System.out.println(rtpServerParam);
            play(mediaServer, rtpServerParam, device, null, callback);
        if (LiveStreamType.HIK_SDK.getCode().equals(ardChannel.getType()) || LiveStreamType.HIK_ISUP.getCode().equals(ardChannel.getType()) || LiveStreamType.DAHUA_SDK.getCode().equals(ardChannel.getType())) {
//            RTPServerParam rtpServerParam = new RTPServerParam();
//            if (LiveStreamType.HIK_SDK.getCode().equals(device.getType())) {
//                rtpServerParam.setApp("haikang");
//            } else if (LiveStreamType.HIK_ISUP.getCode().equals(device.getType())) {
//                rtpServerParam.setApp("haikang_isup");
//            } else if (LiveStreamType.DAHUA_SDK.getCode().equals(device.getType())) {
//                rtpServerParam.setApp("dahua");
//            }
//
//            rtpServerParam.setStreamId(device.getDeviceCode());
//            rtpServerParam.setTcpMode(0);
//            rtpServerParam.setType(device.getType());
//            rtpServerParam.setId(device.getId());
//            System.out.println(rtpServerParam);
//            play(mediaServer, rtpServerParam, device, null, callback);
        }
        // rtsp/rtmp/flv/hls/onvif
        if (LiveStreamType.RTSP.getCode().equals(device.getType()) || LiveStreamType.RTMP.getCode().equals(device.getType()) || LiveStreamType.FLV.getCode().equals(device.getType()) || LiveStreamType.HLS.getCode().equals(device.getType()) || LiveStreamType.ONVIF.getCode().equals(device.getType())) {
        if (LiveStreamType.RTSP.getCode().equals(ardChannel.getType()) || LiveStreamType.RTMP.getCode().equals(ardChannel.getType()) || LiveStreamType.FLV.getCode().equals(ardChannel.getType()) || LiveStreamType.HLS.getCode().equals(ardChannel.getType()) || LiveStreamType.ONVIF.getCode().equals(ardChannel.getType())) {
            StreamPullPlay streamPullPlay = new StreamPullPlay();
            streamPullPlay.setDeviceId(device.getId());
            streamPullPlay.setStream(device.getDeviceCode());
            streamPullPlay.setUrl(device.getLiveAddress());
            streamPullPlay.setEnable_mp4("1".equals(device.getEnableMp4()));
            streamPullPlay.setEnable_audio("1".equals(device.getEnableAudio()));
            streamPullPlay.setChannelId(ardChannel.getId());
            streamPullPlay.setStream(ardChannel.getId());
            streamPullPlay.setUrl(ardChannel.getLiveAddress());
            streamPullPlay.setEnable_mp4(false);
            streamPullPlay.setEnable_audio(true);
            streamPullPlay.setRtp_type("1");
            streamPullPlay.setTimeOut(10);
            if (LiveStreamType.RTSP.getCode().equals(device.getType())) {
            if (LiveStreamType.RTSP.getCode().equals(ardChannel.getType())) {
                streamPullPlay.setApp("rtsp");
            } else if (LiveStreamType.RTMP.getCode().equals(device.getType())) {
                streamPullPlay.setApp("rtmp");
            } else if (LiveStreamType.FLV.getCode().equals(device.getType())) {
                streamPullPlay.setApp("flv");
                if ("ws".equals(device.getFlvType())) {
                    streamPullPlay.setUrl(convertWsToHttp(device.getLiveAddress()));
                }
            } else if (LiveStreamType.HLS.getCode().equals(device.getType())) {
                streamPullPlay.setApp("hls");
            } else if (LiveStreamType.ONVIF.getCode().equals(device.getType())) {
                streamPullPlay.setApp("onvif");
            }
            } else if (LiveStreamType.RTMP.getCode().equals(ardChannel.getType())) {
                streamPullPlay.setApp("rtmp");}
//            } else if (LiveStreamType.FLV.getCode().equals(device.getType())) {
//                streamPullPlay.setApp("flv");
//                if ("ws".equals(device.getFlvType())) {
//                    streamPullPlay.setUrl(convertWsToHttp(device.getLiveAddress()));
//                }
//            } else if (LiveStreamType.HLS.getCode().equals(device.getType())) {
//                streamPullPlay.setApp("hls");
//            } else if (LiveStreamType.ONVIF.getCode().equals(device.getType())) {
//                streamPullPlay.setApp("onvif");
//            }
            streamPullPlay(streamPullPlay, callback);
        }
        // 视频文件
        if (LiveStreamType.VIDEO_FILE.getCode().equals(device.getType())) {
            loadRecord(device.getId(), callback);
        }
//        if (LiveStreamType.VIDEO_FILE.getCode().equals(device.getType())) {
//            loadRecord(device.getId(), callback);
//        }
    }
    /**
@@ -1535,7 +1607,7 @@
     */
    @Override
    public Map<String, Object> getStreamPushAddress(Long id, String callId) {
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(id, SecurityConstants.INNER);
        R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(id, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("获取设备信息失败");
        }
@@ -1565,7 +1637,7 @@
     */
    @Override
    public void streamPullPush(Long id, ErrorCallback<StreamInfo> callback) {
        R<QsDevice> r = remoteQsDeviceService.getQsDeviceInfo(id, SecurityConstants.INNER);
        R<StreamChannel> r = zlmStreamService.getQsDeviceInfo(id, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            log.info("获取设备信息失败 id:{}", id);
            callback.run(InviteErrorCode.FAIL.getCode(), "获取设备信息失败", null);
@@ -1576,7 +1648,7 @@
            callback.run(InviteErrorCode.FAIL.getCode(), "设备不存在", null);
            return;
        }
        QsDevice device = r.getData();
        StreamChannel device = r.getData();
        if (!LiveStreamType.PUSH.getCode().equals(device.getType())) {
            log.info("直播流接入类型不对,应当是PUSH id:{}", id);
@@ -1597,10 +1669,10 @@
                callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(),
                        getStreamInfoByAppAndStream(mediaServer, "push", device.getDeviceCode(), mediaInfo));
                if ("0".equals(device.getStreamStatus())) {
                    QsDevice qsDevice = new QsDevice();
                    qsDevice.setId(id);
                    qsDevice.setStreamStatus("1");
                    R<Boolean> updateR = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
                    StreamChannel streamChannel = new StreamChannel();
                    streamChannel.setId(id);
                    streamChannel.setStreamStatus("1");
                    R<Boolean> updateR = zlmStreamService.updateQsDevice(streamChannel, SecurityConstants.INNER);
                    if (updateR.getCode() != Constants.SUCCESS) {
                        log.info("修改推流设备设备失败 id:{}", id);
                        throw new RuntimeException("修改推流设备设备失败");
@@ -1619,12 +1691,12 @@
    /**
     * gb28181 播放
     *
     * @param qsDevice
     * @param streamChannel
     * @param gbDevice
     * @param callback
     */
    @Override
    public void startGb28181Play(QsDevice qsDevice, Device gbDevice, ErrorCallback<StreamInfo> callback) {
    public void startGb28181Play(StreamChannel streamChannel, Device gbDevice, ErrorCallback<StreamInfo> callback) {
        ZlmMediaServer mediaServer = getMediaServerForMinimumLoad(null);
        if (mediaServer == null) {
@@ -1632,18 +1704,18 @@
            return;
        }
        int tcpMode = qsDevice.getStreamMode().equals("TCP-ACTIVE") ? 2 : (qsDevice.getStreamMode().equals("TCP" +
        int tcpMode = streamChannel.getStreamMode().equals("TCP-ACTIVE") ? 2 : (streamChannel.getStreamMode().equals("TCP" +
                "-PASSIVE") ? 1 : 0);
        RTPServerParam rtpServerParam = new RTPServerParam();
        rtpServerParam.setApp("gb28181");
        rtpServerParam.setMediaServer(mediaServer);
        rtpServerParam.setType(LiveStreamType.GB28181.getCode());
        rtpServerParam.setStreamId(qsDevice.getDeviceCode());
        rtpServerParam.setStreamId(streamChannel.getDeviceCode());
        rtpServerParam.setTcpMode(tcpMode);
        rtpServerParam.setId(qsDevice.getId());
        rtpServerParam.setId(streamChannel.getId());
        startGb28181PlayFun(mediaServer, qsDevice, gbDevice, rtpServerParam, null, callback);
        startGb28181PlayFun(mediaServer, streamChannel, gbDevice, rtpServerParam, null, callback);
    }
    /**
@@ -1669,21 +1741,21 @@
     * gb28181 停止点播
     *
     * @param type
     * @param qsDevice
     * @param streamChannel
     * @param device
     * @param stream
     */
    @Override
    public void stopGb28181Play(InviteSessionType type, QsDevice qsDevice, Device device, String stream) {
        InviteInfo inviteInfo = inviteStreamService.getInviteInfo(type, qsDevice.getId(), stream);
    public void stopGb28181Play(InviteSessionType type, StreamChannel streamChannel, Device device, String stream) {
        InviteInfo inviteInfo = inviteStreamService.getInviteInfo(type, streamChannel.getId(), stream);
        if (inviteInfo == null) {
            if (type == InviteSessionType.PLAY) {
                QsDevice qsDeviceUpdate = new QsDevice();
                qsDeviceUpdate.setId(qsDevice.getId());
                StreamChannel qsDeviceUpdate = new StreamChannel();
                qsDeviceUpdate.setId(streamChannel.getId());
                qsDeviceUpdate.setStreamKey("");
                qsDeviceUpdate.setMediaServerId("");
                qsDeviceUpdate.setStreamStatus("0");
                R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDeviceUpdate, SecurityConstants.INNER);
                R<Boolean> r = zlmStreamService.updateQsDevice(qsDeviceUpdate, SecurityConstants.INNER);
                if (r.getCode() != Constants.SUCCESS) {
                    throw new RuntimeException("更新设备失败");
                }
@@ -1693,18 +1765,18 @@
        inviteStreamService.removeInviteInfo(inviteInfo);
        if (InviteSessionStatus.ok == inviteInfo.getStatus()) {
            try {
                log.info("[停止点播/回放/下载] {}/{}", qsDevice.getGbDeviceId(), qsDevice.getGbChannelId());
                log.info("[停止点播/回放/下载] {}/{}", streamChannel.getGbDeviceId(), streamChannel.getGbChannelId());
                RtpServerParam rtpServer = new RtpServerParam();
                rtpServer.setApp("gb28181");
                rtpServer.setStream(qsDevice.getDeviceCode());
                rtpServer.setGbDeviceId(qsDevice.getGbDeviceId());
                rtpServer.setGbChannelId(qsDevice.getGbChannelId());
                rtpServer.setStream(streamChannel.getDeviceCode());
                rtpServer.setGbDeviceId(streamChannel.getGbDeviceId());
                rtpServer.setGbChannelId(streamChannel.getGbChannelId());
                R<Void> r = remoteGb28181Service.streamByeCmd(rtpServer, SecurityConstants.INNER);
                if (r.getCode() != Constants.SUCCESS) {
                    log.error("[命令发送失败] 停止点播/回放/下载, deviceId:{}", qsDevice.getGbDeviceId());
                    throw new RuntimeException("[命令发送失败] 停止点播/回放/下载, deviceId:" + qsDevice.getGbDeviceId());
                    log.error("[命令发送失败] 停止点播/回放/下载, deviceId:{}", streamChannel.getGbDeviceId());
                    throw new RuntimeException("[命令发送失败] 停止点播/回放/下载, deviceId:" + streamChannel.getGbDeviceId());
                }
            } catch (Exception e) {
                log.error("[命令发送失败] 停止点播/回放/下载, 发送BYE: {}", e.getMessage());
@@ -1713,12 +1785,12 @@
        }
        if (inviteInfo.getType() == InviteSessionType.PLAY) {
            QsDevice qsDeviceUpdate = new QsDevice();
            qsDeviceUpdate.setId(qsDevice.getId());
            StreamChannel qsDeviceUpdate = new StreamChannel();
            qsDeviceUpdate.setId(streamChannel.getId());
            qsDeviceUpdate.setStreamKey("");
            qsDeviceUpdate.setMediaServerId("");
            qsDeviceUpdate.setStreamStatus("0");
            R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDeviceUpdate, SecurityConstants.INNER);
            R<Boolean> r = zlmStreamService.updateQsDevice(qsDeviceUpdate, SecurityConstants.INNER);
            if (r.getCode() != Constants.SUCCESS) {
                throw new RuntimeException("更新设备失败");
            }
@@ -1737,6 +1809,160 @@
        }
    }
    /**
     * gb28181 播放(基于GbDevice,不依赖QS)
     */
    @Override
    public void startGb28181PlayByGbChannel(StreamChannel streamChannel, Device gbDevice, ErrorCallback<StreamInfo> callback) {
        ZlmMediaServer mediaServer = getMediaServerForMinimumLoad(null);
        if (mediaServer == null) {
            callback.run(InviteErrorCode.FAIL.getCode(), "无可用的节点", null);
            return;
        }
        String streamMode = streamChannel.getStreamMode() != null
                ? streamChannel.getStreamMode() : gbDevice.getStreamMode();
        int tcpMode = streamMode.equals("TCP-ACTIVE") ? 2
                : (streamMode.equals("TCP-PASSIVE") ? 1 : 0);
        RTPServerParam rtpServerParam = new RTPServerParam();
        rtpServerParam.setApp("gb28181");
        rtpServerParam.setMediaServer(mediaServer);
        rtpServerParam.setType(LiveStreamType.GB28181.getCode());
        rtpServerParam.setStreamId(streamChannel.getDeviceCode());
        rtpServerParam.setTcpMode(tcpMode);
        rtpServerParam.setId(streamChannel.getId());
        startGb28181PlayFunByGbChannel(mediaServer, streamChannel, gbDevice, rtpServerParam, null, callback);
    }
    /**
     * gb28181 停止点播(基于GbDevice,不依赖QS)
     */
    @Override
    public void stopGb28181PlayByGbChannel(InviteSessionType type, StreamChannel streamChannel, Device device, String stream) {
        InviteInfo inviteInfo = inviteStreamService.getInviteInfo(type, streamChannel.getId(), stream);
        if (inviteInfo == null) {
            if (type == InviteSessionType.PLAY) {
                StreamChannel update = new StreamChannel();
                update.setId(streamChannel.getId());
                update.setStreamKey("");
                update.setMediaServerId("");
                update.setStreamStatus("0");
                R<Boolean> r = zlmStreamService.updateQsDevice(update, SecurityConstants.INNER);
                if (r.getCode() != Constants.SUCCESS) {
                    throw new RuntimeException("更新GbDevice失败");
                }
            }
            return;
        }
        inviteStreamService.removeInviteInfo(inviteInfo);
        if (InviteSessionStatus.ok == inviteInfo.getStatus()) {
            try {
                log.info("[停止点播/回放/下载] {}/{}", streamChannel.getGbDeviceId(), streamChannel.getGbChannelId());
                RtpServerParam rtpServer = new RtpServerParam();
                rtpServer.setApp("gb28181");
                rtpServer.setStream(streamChannel.getDeviceCode());
                rtpServer.setGbDeviceId(streamChannel.getGbDeviceId());
                rtpServer.setGbChannelId(streamChannel.getGbChannelId());
                R<Void> r = remoteGb28181Service.streamByeCmd(rtpServer, SecurityConstants.INNER);
                if (r.getCode() != Constants.SUCCESS) {
                    log.error("[命令发送失败] 停止点播/回放/下载, deviceId:{}", streamChannel.getGbDeviceId());
                    throw new RuntimeException("[命令发送失败] 停止点播/回放/下载, deviceId:" + streamChannel.getGbDeviceId());
                }
            } catch (Exception e) {
                log.error("[命令发送失败] 停止点播/回放/下载, 发送BYE: {}", e.getMessage());
                throw new RuntimeException("命令发送失败: " + e.getMessage());
            }
        }
        if (inviteInfo.getType() == InviteSessionType.PLAY) {
            StreamChannel update = new StreamChannel();
            update.setId(streamChannel.getId());
            update.setStreamKey("");
            update.setMediaServerId("");
            update.setStreamStatus("0");
            R<Boolean> r = zlmStreamService.updateQsDevice(update, SecurityConstants.INNER);
            if (r.getCode() != Constants.SUCCESS) {
                throw new RuntimeException("更新GbDevice失败");
            }
        }
        ZlmMediaServer mediaServer = null;
        if (inviteInfo.getStreamInfo() != null) {
            mediaServer = inviteInfo.getStreamInfo().getMediaServer();
        } else {
            mediaServer = getOne(inviteInfo.getMediaServerId());
        }
        if (mediaServer != null && inviteInfo.getSsrcInfo() != null) {
            closeRTPServer(mediaServer, inviteInfo.getSsrcInfo().getStream());
            ssrcFactory.releaseSsrc(inviteInfo.getMediaServerId(), inviteInfo.getSsrcInfo().getSsrc());
        }
    }
    /**
     * 通过通道ID拉流代理(简化传参,自动根据通道类型确定app/url等参数)
     *
     * @param channelId 通道ID
     * @param callback  回调
     */
    @Override
    public void streamPullPlayByChannelId(String channelId, ErrorCallback<StreamInfo> callback) {
        R<ArdChannel> channelR = remoteChannelService.getInfo(channelId, SecurityConstants.INNER);
        if (channelR.getCode() != Constants.SUCCESS) {
            callback.run(InviteErrorCode.FAIL.getCode(), "获取通道信息失败 channelId:" + channelId, null);
            return;
        }
        if (channelR.getData() == null) {
            callback.run(InviteErrorCode.FAIL.getCode(), "通道不存在 channelId:" + channelId, null);
            return;
        }
        ArdChannel ardChannel = channelR.getData();
        StreamPullPlay streamPullPlay = buildStreamPullPlay(ardChannel);
        streamPullPlay(streamPullPlay, callback);
    }
    /**
     * 通过通道ID停止拉流代理
     *
     * @param channelId 通道ID
     */
    @Override
    public void stopStreamPullPlayByChannelId(String channelId) {
        R<ArdChannel> channelR = remoteChannelService.getInfo(channelId, SecurityConstants.INNER);
        if (channelR.getCode() != Constants.SUCCESS) {
            throw new RuntimeException("获取通道信息失败 channelId:" + channelId);
        }
        if (channelR.getData() == null) {
            throw new RuntimeException("通道不存在 channelId:" + channelId);
        }
        ArdChannel ardChannel = channelR.getData();
        StreamPullPlay streamPullPlay = new StreamPullPlay();
        streamPullPlay.setChannelId(channelId);
        streamPullPlay.setMediaServerId(ardChannel.getMediaServerId());
        streamPullPlay.setStreamKey(ardChannel.getStreamKey());
        stopStreamPullPlay(streamPullPlay);
    }
    private StreamPullPlay buildStreamPullPlay(ArdChannel ardChannel) {
        StreamPullPlay streamPullPlay = new StreamPullPlay();
        streamPullPlay.setChannelId(ardChannel.getId());
        streamPullPlay.setStream(ardChannel.getId());
        streamPullPlay.setUrl(ardChannel.getLiveAddress());
        streamPullPlay.setApp("ard");
        streamPullPlay.setEnable_mp4(false);
        streamPullPlay.setEnable_audio(true);
        streamPullPlay.setRtp_type("1");
        streamPullPlay.setTimeOut(10);
        return streamPullPlay;
    }
    /**
     * 开启国标28181播放
@@ -1747,7 +1973,7 @@
     * @param ssrc
     * @param callback
     */
    private SSRCInfo startGb28181PlayFun(ZlmMediaServer mediaServer, QsDevice device, Device gbDevice,
    private SSRCInfo startGb28181PlayFun(ZlmMediaServer mediaServer, StreamChannel device, Device gbDevice,
                                         RTPServerParam rtpServerParam, String ssrc,
                                         ErrorCallback<StreamInfo> callback) {
        // 获取点播的状态信息
@@ -1847,13 +2073,13 @@
                    String filePath = snapOnPlay(streamInfo.getMediaServer(), streamInfo.getApp(),
                            streamInfo.getStream());
                    QsDevice qsDevice = new QsDevice();
                    qsDevice.setId(rtpServerParam.getId());
                    qsDevice.setStreamKey(rtpServerParam.getStreamId());
                    qsDevice.setMediaServerId(mediaServer.getId());
                    qsDevice.setStreamStatus("1");
                    qsDevice.setSnap(filePath);
                    R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
                    StreamChannel streamChannel = new StreamChannel();
                    streamChannel.setId(rtpServerParam.getId());
                    streamChannel.setStreamKey(rtpServerParam.getStreamId());
                    streamChannel.setMediaServerId(mediaServer.getId());
                    streamChannel.setStreamStatus("1");
                    streamChannel.setSnap(filePath);
                    R<Boolean> r = zlmStreamService.updateQsDevice(streamChannel, SecurityConstants.INNER);
                    if (r.getCode() != Constants.SUCCESS) {
                        throw new RuntimeException("更新设备失败");
                    }
@@ -1955,6 +2181,197 @@
    }
    /**
     * 开启国标28181播放(基于GbDevice,不依赖QS)
     */
    private SSRCInfo startGb28181PlayFunByGbChannel(ZlmMediaServer mediaServer, StreamChannel streamChannel,
                                                    Device gbDevice, RTPServerParam rtpServerParam,
                                                    String ssrc, ErrorCallback<StreamInfo> callback) {
        // 获取点播的状态信息
        InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY,
                streamChannel.getId());
        if (inviteInfoInCatch != null) {
            if (inviteInfoInCatch.getStreamInfo() == null) {
                ssrcFactory.releaseSsrc(mediaServer.getId(), null);
                inviteStreamService.once(InviteSessionType.PLAY, streamChannel.getId(), null, callback);
                log.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channel: {}", streamChannel.getId(), streamChannel.getId());
                return inviteInfoInCatch.getSsrcInfo();
            } else {
                StreamInfo streamInfo = inviteInfoInCatch.getStreamInfo();
                String streamId = streamInfo.getStream();
                if (streamId == null) {
                    callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null);
                    inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null,
                            InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null);
                    return inviteInfoInCatch.getSsrcInfo();
                }
                ZlmMediaServer mediaInfo = streamInfo.getMediaServer();
                Boolean ready = isStreamReady(mediaInfo, rtpServerParam.getApp(), streamId);
                if (ready != null && ready) {
                    if (callback != null) {
                        callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
                    }
                    inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null,
                            InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
                    log.info("[点播已存在] 直接返回, 设备编号: {}", streamChannel.getId());
                    return inviteInfoInCatch.getSsrcInfo();
                } else {
                    inviteStreamService.once(InviteSessionType.PLAY, streamChannel.getId(), null, callback);
                    RTPServerParam stopRtp = new RTPServerParam();
                    stopRtp.setId(streamChannel.getId());
                    stopRtp.setType(rtpServerParam.getType());
                    stopRtp.setStreamId(rtpServerParam.getStreamId());
                    stopRtpPlay(stopRtp);
                    inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, streamChannel.getId());
                }
            }
        }
        rtpServerParam.setMediaServer(mediaServer);
        if (rtpServerParam.getPresetSsrc() != null) {
            ssrc = rtpServerParam.getPresetSsrc();
        } else {
            if (rtpServerParam.isPlayback()) {
                ssrc = ssrcFactory.getPlayBackSsrc(mediaServer.getId());
            } else {
                ssrc = ssrcFactory.getPlaySsrc(mediaServer.getId());
            }
        }
        rtpServerParam.setSsrc(ssrc);
        SSRCInfo ssrcInfo = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, result) -> {
            if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) {
                log.info("[创建RTP服务器] 成功, code: {}, msg: {}, result: {}", code, msg, result);
                StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, rtpServerParam.getApp(),
                        rtpServerParam.getStreamId(), result.getHookData().getMediaInfo());
                if (streamInfo == null) {
                    if (callback != null) {
                        callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
                                InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
                    }
                    inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null,
                            InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
                            InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
                    if (result != null && result.getSsrcInfo() != null) {
                        closeRTPServer(mediaServer, result.getSsrcInfo().getStream());
                        ssrcFactory.releaseSsrc(mediaServer.getId(), result.getSsrcInfo().getSsrc());
                    }
                    return;
                }
                if (callback != null) {
                    callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
                    inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null,
                            InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(
                            InviteSessionType.PLAY, streamChannel.getId());
                    if (inviteInfo != null) {
                        inviteInfo.setStatus(InviteSessionStatus.ok);
                        inviteInfo.setStreamInfo(streamInfo);
                        inviteStreamService.updateInviteInfo(inviteInfo);
                    }
                    String filePath = snapOnPlay(streamInfo.getMediaServer(), streamInfo.getApp(),
                            streamInfo.getStream());
                    // 更新GbDevice流状态
                    StreamChannel update = new StreamChannel();
                    update.setId(rtpServerParam.getId());
                    update.setStreamKey(rtpServerParam.getStreamId());
                    update.setMediaServerId(mediaServer.getId());
                    update.setStreamStatus("1");
                    update.setSnap(filePath);
                    R<Boolean> r = zlmStreamService.updateQsDevice(update, SecurityConstants.INNER);
                    if (r.getCode() != Constants.SUCCESS) {
                        throw new RuntimeException("更新GbDevice失败");
                    }
                }
            } else {
                log.error("[创建RTP服务器] 失败, code: {}, msg: {}, result: {}", code, msg, result);
                if (callback != null) {
                    callback.run(code, msg, null);
                }
                inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null, code, msg, null);
                inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, streamChannel.getId());
                if (result != null && result.getSsrcInfo() != null) {
                    closeRTPServer(mediaServer, result.getSsrcInfo().getStream());
                    ssrcFactory.releaseSsrc(mediaServer.getId(), result.getSsrcInfo().getSsrc());
                }
            }
        });
        if (ssrcInfo == null || ssrcInfo.getPort() <= 0) {
            log.info("[点播端口/SSRC]获取失败,设备编号:{}, 通道编号:{}, ssrcInfo: {}", streamChannel.getId(), streamChannel.getId(), ssrcInfo);
            if (rtpServerParam.getPresetSsrc() == null) {
                ssrcFactory.releaseSsrc(mediaServer.getId(), ssrc);
            }
            callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "获取端口或者ssrc失败", null);
            inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null,
                    InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
                    InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
            return null;
        }
        int port = ssrcInfo.getPort();
        String ip = mediaServer.getIp();
        RtpServerParam rtpServer = new RtpServerParam();
        rtpServer.setPort(port);
        rtpServer.setIp(ip);
        rtpServer.setId(rtpServerParam.getId());
        rtpServer.setSsrc(rtpServerParam.getSsrc());
        rtpServer.setGbDeviceId(gbDevice.getDeviceId());
        rtpServer.setGbChannelId(streamChannel.getGbChannelId());
        rtpServer.setStreamMode(gbDevice.getStreamMode());
        rtpServer.setMediaServerId(mediaServer.getId());
        rtpServer.setApp(rtpServerParam.getApp());
        rtpServer.setStream(rtpServerParam.getStreamId());
        log.info("[国标28181点播开始(基于GbDevice)] ===============================");
        log.info("[国标28181] GbDeviceId: {}, 设备国标ID: {}, 通道国标ID: {}", streamChannel.getId(),
                gbDevice.getDeviceId(), streamChannel.getGbChannelId());
        log.info("[国标28181] 流模式: {}, ZLM tcpMode: {}, ssrcCheck: {}", gbDevice.getStreamMode(),
                rtpServerParam.getTcpMode(), rtpServerParam.isSsrcCheck());
        log.info("[国标28181] ZLM媒体服务器IP: {}, 收流端口: {}, 流ID: {}, SSRC: {}", ip, port, ssrcInfo.getStream(),
                ssrcInfo.getSsrc());
        log.info("[国标28181] =======================================");
        InviteInfo inviteInfo = InviteInfo.getInviteInfo(streamChannel.getId().toString(), streamChannel.getId(),
                ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(), mediaServer.getSdpIp(), ssrcInfo.getPort(),
                gbDevice.getStreamMode(), InviteSessionType.PLAY, InviteSessionStatus.ready,
                userSetting.getRecordSip());
        if ("1".equals(streamChannel.getEnableMp4())) {
            inviteInfo.setRecord(true);
        }
        inviteStreamService.updateInviteInfo(inviteInfo);
        R<Void> r = remoteGb28181Service.playStreamCmd(rtpServer, SecurityConstants.INNER);
        if (r.getCode() != Constants.SUCCESS) {
            log.info("[点播失败]{}:{} deviceId: {}, channelId:{}", r.getCode(), r.getMsg(),
                    streamChannel.getGbDeviceId(), streamChannel.getGbChannelId());
            inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.PLAY, streamChannel.getId(),
                    rtpServerParam.getStreamId());
            if (inviteInfo != null) {
                inviteStreamService.removeInviteInfo(inviteInfo);
                if (inviteInfo.getSsrcInfo() != null) {
                    ssrcFactory.releaseSsrc(mediaServer.getId(), inviteInfo.getSsrcInfo().getSsrc());
                }
            }
            closeRTPServer(mediaServer, ssrcInfo.getStream());
            ssrcFactory.releaseSsrc(mediaServer.getId(), ssrcInfo.getSsrc());
            if (callback != null) {
                callback.run(r.getCode(), r.getMsg(), null);
            }
            inviteStreamService.call(InviteSessionType.PLAY, streamChannel.getId(), null,
                    r.getCode(), r.getMsg(), null);
            inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, streamChannel.getId());
            return ssrcInfo;
        }
        return ssrcInfo;
    }
    /**
     * 将 WebSocket 协议地址转换为 HTTP 协议地址
     * ws:// -> http://
     * wss:// -> https://
@@ -1999,12 +2416,12 @@
            if (callback != null) {
                callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
                QsDevice qsDevice = new QsDevice();
                qsDevice.setId(id);
                qsDevice.setStreamKey(stream);
                qsDevice.setMediaServerId(mediaServer.getId());
                qsDevice.setStreamStatus("1");
                R<Boolean> r = remoteQsDeviceService.updateQsDevice(qsDevice, SecurityConstants.INNER);
                StreamChannel streamChannel = new StreamChannel();
                streamChannel.setId(id);
                streamChannel.setStreamKey(stream);
                streamChannel.setMediaServerId(mediaServer.getId());
                streamChannel.setStreamStatus("1");
                R<Boolean> r = zlmStreamService.updateQsDevice(streamChannel, SecurityConstants.INNER);
                if (r.getCode() != Constants.SUCCESS) {
                    log.error("更新设备失败");
                    callback.run(InviteErrorCode.FAIL.getCode(), "更新设备失败", null);