1、流媒体升级1.2.0,修改部分forest接口
2、登录设备判断编码自动配置转码
3、增加sdk聚焦模式切换
4、增加sdk视场角获取并定时上传
已添加3个文件
已修改26个文件
1870 ■■■■■ 文件已修改
ard-work/src/main/java/com/ruoyi/alarm/global/service/impl/QueueTaskExecutor.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/camera/domain/CameraCmd.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/camera/service/impl/CameraSdkServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/dhsdk/lib/enumeration/EM_FOCUS_LIMIT_SELECT_MODE.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/dhsdk/lib/structure/CFG_VIDEO_IN_FOCUS.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/dhsdk/module/ConfigModule.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/dhsdk/service/IDhClientService.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/dhsdk/service/impl/DhClientServiceImpl.java 101 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/hiksdk/sdk/LoginResultCallBack.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/hiksdk/service/IHikClientService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/hiksdk/service/impl/HikClientServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/inspect/service/impl/ArdVideoInspectTaskServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/domain/Conf.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/domain/Items.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/service/IMediaV2Service.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/service/impl/MediaV2ServiceImpl.java 490 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/utils/forest/MediaClientV2.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/resources/templates/preview.html 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/mediamtx/LICENSE 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib/mediamtx/mediamtx.exe 补丁 | 查看 | 原始文档 | blame | 历史
lib/mediamtx/mediamtx.yml 594 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/logback.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/SdkOperateAspect.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/PushTask.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/alarm/global/service/impl/QueueTaskExecutor.java
@@ -47,6 +47,7 @@
    IArdAlarmExternalService ardAlarmExternalService;
    @Resource
    IArdAlarmAccessService ardAlarmAccessService;
    public void processTask(GuideTask guideTask) {
        try {
            CameraCmd cmd = new CameraCmd();
ard-work/src/main/java/com/ruoyi/device/camera/controller/CameraSdkController.java
@@ -286,6 +286,7 @@
    @ApiOperation(value = "获取聚焦模式", notes = "1手动2自动")
    @PostMapping("/getFocusMode")
    @ApiOperationSupport(includeParameters = {"cmd.cameraId", "cmd.chanNo"})
    public @ResponseBody
    AjaxResult getFocusMode(@RequestBody CameraCmd cmd) {
        cmd.setOperator(SecurityUtils.getUserId());
@@ -328,7 +329,7 @@
    @ApiOperation("获取相机架设参数")
    @PostMapping("/getGisInfo")
    @Log(title = "获取相机架设参数", businessType = BusinessType.CONTROL)
    @ApiOperationSupport(includeParameters = {"cmd.cameraId", "cmd.chanNo", "cmd.enable"})
    @ApiOperationSupport(includeParameters = {"cmd.cameraId", "cmd.chanNo"})
    public @ResponseBody
    AjaxResult getGisInfo(@RequestBody CameraCmd cmd) {
        cmd.setOperator(SecurityUtils.getUserId());
ard-work/src/main/java/com/ruoyi/device/camera/domain/CameraCmd.java
@@ -2,13 +2,21 @@
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.bind.DefaultValue;
import java.util.Map;
@Data
@NoArgsConstructor
@ApiModel(description = "sdk命令实体类")
public class CameraCmd {
    public CameraCmd(String cameraId, Integer chanNo) {
        this.cameraId = cameraId;
        this.chanNo = chanNo;
    }
    /*命令标识*/
    String cmdType;
    /*相机ID*/
@@ -26,7 +34,7 @@
    /*聚焦值*/
    Integer dwFocusPos;
    /*PTZ值*/
    Map<String,Double>ptzMap;
    Map<String, Double> ptzMap;
    /*目标经纬度*/
    double[] targetPosition;
ard-work/src/main/java/com/ruoyi/device/camera/service/impl/CameraSdkServiceImpl.java
@@ -315,7 +315,7 @@
                if (factory.equals("1")) {
                    result = hikClientService.getFocusMode(cmd);
                } else if (factory.equals("2")) {
                    result = dhClientService.getFocusMode(cmd);
                }
            }
        } catch (Exception ex) {
@@ -659,7 +659,7 @@
                if (factory.equals("1")) {
                    map = hikClientService.getGisInfo(cmd);
                } else if (factory.equals("2")) {
                    map = dhClientService.getGisInfo(cmd);
                }
            }
        } catch (Exception ex) {
ard-work/src/main/java/com/ruoyi/device/dhsdk/lib/enumeration/EM_FOCUS_LIMIT_SELECT_MODE.java
@@ -1,43 +1,43 @@
package com.ruoyi.device.dhsdk.lib.enumeration;
/**
* @author 291189
* @description  èšç„¦æžé™å¯¹åº”枚举
* @date 2022/11/01 11:16:54
*/
/**
 * @author 291189
 * @description èšç„¦æžé™å¯¹åº”枚举
 * @date 2022/11/01 11:16:54
 */
public enum EM_FOCUS_LIMIT_SELECT_MODE {
/**
 Manual  è‡ªåЍ
*/
EM_FOCUS_LIMIT_MODE_MANUAL(0," Manual è‡ªåЍ"),
/**
 Auto æ‰‹åЍ
*/
EM_FOCUS_LIMIT_MODE_AUTO(1," Auto æ‰‹åЍ"),
/**
    /**
     * Manual  è‡ªåЍ
     */
    EM_FOCUS_LIMIT_MODE_MANUAL(0, " Manual è‡ªåЍ"),
    /**
     * Auto æ‰‹åЍ
     */
    EM_FOCUS_LIMIT_MODE_AUTO(1, " Auto æ‰‹åЍ"),
    /**
     *
     */
    EM_FOCUS_LIMIT_MODE_INVALI(2, "无效");
*/
EM_FOCUS_LIMIT_MODE_INVALI(2,"无效");
    private int value;
private int value;
    private String note;
private String note;
public String getNote() {
    public String getNote() {
        return note;
    }
public int getValue() {
    public int getValue() {
        return value;
    }
EM_FOCUS_LIMIT_SELECT_MODE(int givenValue, String note) {
    EM_FOCUS_LIMIT_SELECT_MODE(int givenValue, String note) {
        this.value = givenValue;
        this.note = note;
    }
public static String getNoteByValue(int givenValue) {
    public static String getNoteByValue(int givenValue) {
        for (EM_FOCUS_LIMIT_SELECT_MODE enumType : EM_FOCUS_LIMIT_SELECT_MODE.values()) {
            if (givenValue == enumType.getValue()) {
                return enumType.getNote();
@@ -46,7 +46,7 @@
        return null;
    }
public static int getValueByNote(String givenNote) {
    public static int getValueByNote(String givenNote) {
        for (EM_FOCUS_LIMIT_SELECT_MODE enumType : EM_FOCUS_LIMIT_SELECT_MODE.values()) {
            if (givenNote.equals(enumType.getNote())) {
                return enumType.getValue();
@@ -55,7 +55,7 @@
        return -1;
    }
public static EM_FOCUS_LIMIT_SELECT_MODE getEnum(int value) {
    public static EM_FOCUS_LIMIT_SELECT_MODE getEnum(int value) {
        for (EM_FOCUS_LIMIT_SELECT_MODE e : EM_FOCUS_LIMIT_SELECT_MODE.values()) {
            if (e.getValue() == value)
                return e;
ard-work/src/main/java/com/ruoyi/device/dhsdk/lib/structure/CFG_VIDEO_IN_FOCUS.java
@@ -3,28 +3,28 @@
import com.ruoyi.device.dhsdk.lib.NetSDKLib;
/**
* @author 291189
* @description  èšç„¦è®¾ç½®åŸºæœ¬ä¿¡æ¯å•å…ƒ
* @date 2022/11/01 11:16:54
*/
/**
 * @author 291189
 * @description èšç„¦è®¾ç½®åŸºæœ¬ä¿¡æ¯å•å…ƒ
 * @date 2022/11/01 11:16:54
 */
public class CFG_VIDEO_IN_FOCUS extends NetSDKLib.SdkStructure {
/**
通道号
*/
public            int                    nChannelIndex;
/**
配置使用个数
*/
public            int                    nVideoInFocusRealNum;
/**
通道聚焦配置单元信息
*/
public            CFG_VIDEO_IN_FOCUS_UNIT[]                    stVideoInFocusUnit=new CFG_VIDEO_IN_FOCUS_UNIT[32];
    /**
     * é€šé“号
     */
    public int nChannelIndex;
    /**
     * é…ç½®ä½¿ç”¨ä¸ªæ•°
     */
    public int nVideoInFocusRealNum;
    /**
     * é€šé“聚焦配置单元信息
     */
    public CFG_VIDEO_IN_FOCUS_UNIT[] stVideoInFocusUnit = new CFG_VIDEO_IN_FOCUS_UNIT[32];
public            CFG_VIDEO_IN_FOCUS(){
        for(int i=0;i<stVideoInFocusUnit.length;i++){
            stVideoInFocusUnit[i]=new CFG_VIDEO_IN_FOCUS_UNIT();
            }
}
    public CFG_VIDEO_IN_FOCUS() {
        for (int i = 0; i < stVideoInFocusUnit.length; i++) {
            stVideoInFocusUnit[i] = new CFG_VIDEO_IN_FOCUS_UNIT();
        }
    }
}
ard-work/src/main/java/com/ruoyi/device/dhsdk/module/ConfigModule.java
@@ -32,6 +32,21 @@
    }
    /**
     * æŸ¥è¯¢è¿œç¨‹è®¾å¤‡çŠ¶æ€
     */
    public static boolean queryRemotDevState(NetSDKLib.LLong hLoginHandle, int nChn, int nType, NetSDKLib.SdkStructure stuInfo) {
        IntByReference intRetLen = new IntByReference();
        stuInfo.write();
        if (!netsdk.CLIENT_QueryRemotDevState(hLoginHandle, nType, nChn, stuInfo.getPointer(), stuInfo.size(), intRetLen, 3000)) {
            System.err.println("Config Failed!" + ToolKits.getErrorCodePrint());
            return false;
        }
        stuInfo.read();
        return true;
    }
    /**
     * èŽ·å–å•ä¸ªé…ç½®
     *
     * @param hLoginHandle ç™»é™†å¥æŸ„
@@ -69,7 +84,7 @@
        int nBufferLen = 2 * 1024 * 1024;
        byte[] strBuffer = new byte[nBufferLen];
        cmdObject.write();
        boolean bRet = netsdk.CLIENT_QueryNewSystemInfo(hLoginHandle, strCmd, nChn, strBuffer, cmdObject.size(), error,3000);
        boolean bRet = netsdk.CLIENT_QueryNewSystemInfo(hLoginHandle, strCmd, nChn, strBuffer, cmdObject.size(), error, 3000);
        if (bRet) {
            cmdObject.read();
        } else {
@@ -78,7 +93,20 @@
        }
        return result;
    }
    // èŽ·å–é…ç½®
    public static boolean GetConfig(NetSDKLib.LLong hLoginHandle, int nChn,int type,Structure cmdObject) {
        boolean result = false;
        // èŽ·å–
        cmdObject.write();
        if (netsdk.CLIENT_GetConfig(hLoginHandle, type, nChn, cmdObject.getPointer(), cmdObject.size(), 4000, null)) {
            cmdObject.read();
            result=true;
        } else {
            System.err.println("GetConfig Failed!" + getErrorCodePrint());
            result=false;
        }
        return result;
    }
    /**
     * è®¾ç½®å•个配置
     *
@@ -112,4 +140,5 @@
        return result;
    }
}
ard-work/src/main/java/com/ruoyi/device/dhsdk/service/IDhClientService.java
@@ -55,8 +55,12 @@
    boolean gotoPreset(CameraCmd cmd);
    //设置预置位
    boolean setPreset(CameraCmd cmd);
    //聚焦模式
    //设置聚焦模式
    boolean controlFocusMode(CameraCmd cmd);
    //获取聚焦模式
    String getFocusMode(CameraCmd cmd);
    //透雾
    boolean controlDefogcfg(CameraCmd cmd);
    //红外
@@ -67,4 +71,6 @@
    boolean setFocusPos(CameraCmd cmd);
    //获取码流压缩参数
    Map<String, Object> getVideoCompressionCfg(CameraCmd cmd);
    //获取GIS信息数据
    Map<String, Object> getGisInfo(CameraCmd cmd);
}
ard-work/src/main/java/com/ruoyi/device/dhsdk/service/impl/DhClientServiceImpl.java
@@ -3,31 +3,25 @@
import com.ruoyi.common.annotation.SdkOperate;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.device.camera.service.IArdCamerasService;
import com.ruoyi.device.channel.domain.ArdChannel;
import com.ruoyi.device.channel.service.IArdChannelService;
import com.ruoyi.device.dhsdk.common.Res;
import com.ruoyi.device.dhsdk.lib.NetSDKLib;
import com.ruoyi.device.dhsdk.lib.NetSDKLib.LLong;
import com.ruoyi.device.dhsdk.lib.enumeration.EM_FOCUS_LIMIT_SELECT_MODE;
import com.ruoyi.device.dhsdk.lib.enumeration.EM_NEW_CONFIG;
import com.ruoyi.device.dhsdk.lib.enumeration.NET_EM_CFG_OPERATE_TYPE;
import com.ruoyi.device.dhsdk.lib.structure.CFG_VIDEO_IN_FOCUS;
import com.ruoyi.device.dhsdk.lib.structure.CFG_VIDEO_IN_FOCUS_UNIT;
import com.ruoyi.device.dhsdk.lib.structure.NET_ENCODE_VIDEO_INFO;
import com.ruoyi.device.dhsdk.lib.structure.DH_OUT_PTZ_VIEW_RANGE_STATUS;
import com.ruoyi.device.dhsdk.module.*;
import com.ruoyi.device.dhsdk.service.IDhClientService;
import com.ruoyi.device.hiksdk.common.GlobalVariable;
import com.ruoyi.device.hiksdk.sdk.HCNetSDK;
import com.ruoyi.media.domain.Vtdu;
import com.ruoyi.media.service.IVtduService;
import com.ruoyi.media.service.impl.VtduServiceImpl;
import com.ruoyi.utils.gis.GisUtil;
import com.ruoyi.utils.minio.MinioUtil;
import com.ruoyi.utils.tools.ArdTool;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -150,11 +144,18 @@
                if (vtdu != null) {
                    vtduService.deleteVtduByName(name);
                }
                //添加到流媒体
                CameraCmd cmd = new CameraCmd(camera.getId(), channel.getChanNo());
                Map<String, Object> videoCompressionCfg = getVideoCompressionCfg(cmd);
                vtdu = new Vtdu();
                if (videoCompressionCfg.get("videoEncType").equals("标准h264")) {
                    vtdu.setIsCode("0");//默认不转码
                } else {
                    vtdu.setIsCode("1");//默认转码
                }
                vtdu.setRtspSource(rtspSource);
                vtdu.setName(camera.getId() + "_" + channel.getChanNo());
                vtdu.setIsCode("0");//默认不转码
                vtdu.setMode("1");//默认CPU软解码
                vtdu.setCameraId(camera.getId());
                vtduService.insertVtdu(vtdu);
@@ -312,7 +313,8 @@
        if (b) {
            DecimalFormat df = new DecimalFormat("0.0");//设置保留位数
            String nPTZPan = df.format((float) dh_ptz_location_info.nPTZPan / 10);
            String nPTZTilt = df.format((float) dh_ptz_location_info.nPTZTilt / 10);
            float t = (float) dh_ptz_location_info.nPTZTilt / 10;
            String nPTZTilt = df.format(t < 0 ? t + 360 : t);
            String nPTZZoom = df.format((float) dh_ptz_location_info.nPTZZoom);
            ptzMap.put("p", nPTZPan);
            ptzMap.put("t", nPTZTilt);
@@ -648,10 +650,12 @@
                cfg_video_in_focus.stVideoInFocusUnit[i].nFocusLimit = 10000;//聚焦极限值, å•位毫米
                if (enable) {//聚焦模式, 0-关闭, 1-辅助聚焦, 2-自动聚焦, 3-半自动聚焦, 4-手动聚焦
                    cfg_video_in_focus.stVideoInFocusUnit[i].nMode = 4;//手动聚焦
                    cfg_video_in_focus.stVideoInFocusUnit[i].emFocusMode = 1;//聚焦极限Manual
                    cfg_video_in_focus.stVideoInFocusUnit[i].emFocusMode = 0;//聚焦极限Manual
                    log.debug("当前为手动聚焦模式");
                } else {
                    cfg_video_in_focus.stVideoInFocusUnit[i].nMode = 2;//自动聚焦
                    cfg_video_in_focus.stVideoInFocusUnit[i].emFocusMode = 0;//聚焦极限Auto
                    cfg_video_in_focus.stVideoInFocusUnit[i].emFocusMode = 1;//聚焦极限Auto
                    log.debug("当前为自动聚焦模式");
                }
            }
            cfg_video_in_focus.nChannelIndex = chanNo - 1;
@@ -666,6 +670,49 @@
        }
    }
    @Override
    public String getFocusMode(CameraCmd cmd) {
        String mode = "";
        String cameraId = cmd.getCameraId();
        Integer chanNo = cmd.getChanNo();
        if (!GlobalVariable.loginMap.containsKey(cameraId)) {
            return "";
        }
        LLong loginId = (LLong) GlobalVariable.loginMap.get(cameraId);
        try {
            NET_VIDEOIN_FOCUSMODE_INFO focusModeInfo = new NET_VIDEOIN_FOCUSMODE_INFO();
            int emCfgOpType = NET_EM_CFG_OPERATE_TYPE.NET_EM_CFG_VIDEOIN_FOCUSMODE;
            boolean bool = ConfigModule.GetConfig(loginId, chanNo - 1, emCfgOpType, focusModeInfo);
            if (!bool) {
                log.error("获取失败,请稍后重试" + getErrorCodePrint());
            }
            System.out.println("配置类型:" + focusModeInfo.emCfgType);     // å…·ä½“信息,参考库里的枚举
            System.out.println("聚焦模式:" + focusModeInfo.emFocusMode);
            switch (focusModeInfo.emFocusMode) {
                case 0:
                    mode = "关闭";
                    break;
                case 1:
                    mode = "辅助聚焦";
                    break;
                case 2:
                    mode = "自动聚焦";
                    break;
                case 3:
                    mode = "半自动聚焦";
                    break;
                case 4:
                    mode = "手动聚焦";
                    break;
            }
        } catch (Exception ex) {
            log.error("获取聚焦模式异常:" + ex.getMessage());
        }
        return mode;
    }
    //透雾
    @Override
    public boolean controlDefogcfg(CameraCmd cmd) {
@@ -677,9 +724,9 @@
        }
        LLong loginId = (LLong) GlobalVariable.loginMap.get(cameraId);
        try {
            EM_NEW_CONFIG config = EM_NEW_CONFIG.CFG_CMD_VIDEOINDEFOG;
            String command = EM_NEW_CONFIG.CFG_CMD_VIDEOINDEFOG.getValue();
            //CFG_VIDEOINDEFOG_LIST cfg_videoindefog_list=new CFG_VIDEOINDEFOG_LIST();
            boolean bool = ConfigModule.SetDevConfig(loginId, chanNo - 1, config.getValue(), null);
            boolean bool = ConfigModule.SetDevConfig(loginId, chanNo - 1, command, null);
            if (!bool) {
                log.error("控制失败,请稍后重试" + getErrorCodePrint());
            }
@@ -836,6 +883,32 @@
        return map;
    }
    //获取GIS信息数据
    @Override
    public Map<String, Object> getGisInfo(CameraCmd cmd) {
        Map<String, Object> map = new HashMap<>();
        try {
            String cameraId = cmd.getCameraId();
            Integer chanNo = cmd.getChanNo();
            if (!GlobalVariable.loginMap.containsKey(cameraId)) {
                return null;
            }
            LLong loginId = (LLong) GlobalVariable.loginMap.get(cameraId);
            DH_OUT_PTZ_VIEW_RANGE_STATUS dh_out_ptz_view_range_status = new DH_OUT_PTZ_VIEW_RANGE_STATUS();
            boolean b = ConfigModule.queryDevState(loginId, NET_DEVSTATE_PTZ_VIEW_RANGE, dh_out_ptz_view_range_status);
            if (b) {
                float nAngelH = (float) dh_out_ptz_view_range_status.nAngelH / 10;
                float nAngelV = (float) dh_out_ptz_view_range_status.nAngelV / 10;
                map = getPtz(cmd);//获取ptz
                map.put("fHorFieldAngle", nAngelH);// æ°´å¹³è§†åœºè§’
                map.put("fVerFieldAngle", nAngelV);// åž‚直视场角
            }
        } catch (Exception ex) {
            log.error("获取云台可视域异常" + ex.getMessage());
        }
        return map;
    }
    // è®¾å¤‡æ–­çº¿å›žè°ƒ: å½“设备出现断线时,SDK会调用该函数
    private static class DisConnect implements NetSDKLib.fDisConnect {
        public void invoke(LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
ard-work/src/main/java/com/ruoyi/device/hiksdk/sdk/LoginResultCallBack.java
@@ -12,11 +12,9 @@
import com.ruoyi.device.hiksdk.service.IHikClientService;
import com.ruoyi.media.domain.Vtdu;
import com.ruoyi.media.service.IVtduService;
import com.ruoyi.utils.forest.MediaClient;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
ard-work/src/main/java/com/ruoyi/device/hiksdk/service/IHikClientService.java
@@ -84,6 +84,7 @@
    //获取ptz范围
    Map<String, Object> getPtzScope(CameraCmd cmd);
    //设置ptz
    boolean setPtz(CameraCmd cmd);
    //设置零方位角
ard-work/src/main/java/com/ruoyi/device/hiksdk/service/impl/HikClientServiceImpl.java
@@ -25,7 +25,9 @@
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.Base64;
import javax.annotation.Resource;
import java.io.*;
@@ -109,6 +111,7 @@
     * @创建时间 2023/1/17 16:12
     * @修改人和其它信息
     */
    @Async
    public void syncLogin(ArdCameras camera) {
        // åˆå§‹åŒ–
        if (!hCNetSDK.NET_DVR_Init()) {
@@ -188,7 +191,13 @@
                    vtdu = new Vtdu();
                    vtdu.setRtspSource(rtspSource);
                    vtdu.setName(camera.getId() + "_" + channel.getChanNo());
                    vtdu.setIsCode("0");//默认不转码
                    CameraCmd cmd = new CameraCmd(camera.getId(), channel.getChanNo());
                    Map<String, Object> videoCompressionCfg = getVideoCompressionCfg(cmd);
                    if (videoCompressionCfg.get("videoEncType").equals("标准h264")) {
                        vtdu.setIsCode("0");//默认不转码
                    } else {
                        vtdu.setIsCode("1");//默认转码
                    }
                    vtdu.setMode("1");//默认CPU软解码
                    vtdu.setCameraId(camera.getId());
                    vtduService.insertVtdu(vtdu);
ard-work/src/main/java/com/ruoyi/inspect/service/impl/ArdVideoInspectTaskServiceImpl.java
@@ -8,6 +8,7 @@
import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.device.camera.mapper.ArdCamerasMapper;
import com.ruoyi.device.camera.service.ICameraSdkService;
import com.ruoyi.device.hiksdk.service.IHikClientService;
import com.ruoyi.inspect.domain.ArdVideoInspectRecord;
import com.ruoyi.inspect.mapper.ArdVideoInspectRecordMapper;
@@ -44,7 +45,7 @@
    @Resource
    private ArdCamerasMapper ardCamerasMapper;
    @Resource
    private IHikClientService hikClientService;
    private ICameraSdkService cameraSdkService;
    @Resource
    private ArdCamerasMapper camerasMapper;
@@ -455,10 +456,10 @@
                    cmd.setTargetPosition(targetPositon);
                    cmd.setOperator("sys_patrol_inspect");
                    cmd.setExpired(step.getRecordingTime() * 60);
                    boolean setTargetPosition = hikClientService.guideTargetPosition(cmd);
                    boolean setTargetPosition = cameraSdkService.guideTargetPosition(cmd);
                    if (setTargetPosition) {
                        /*控制相机巡检成功,开始录像*/
                        hikClientService.recordStart(cmd);
                        cameraSdkService.recordStart(cmd);
                    } else {
                        /*控制失败,当前步骤启动时间置null*/
                        ardVideoInspectTask.setCurrentStepStartTime("");
@@ -513,7 +514,7 @@
                    cmd.setTargetPosition(targetPositon);
                    cmd.setOperator("sys_patrol_inspect");
                    cmd.setExpired(step.getRecordingTime() * 60);
                    boolean setTargetPosition = hikClientService.guideTargetPosition(cmd);
                    boolean setTargetPosition = cameraSdkService.guideTargetPosition(cmd);
                    if (!setTargetPosition) {
                        /*控制失败,当前步骤启动时间置null*/
                        ardVideoInspectTask.setCurrentStepStartTime("");
@@ -552,7 +553,7 @@
                cmd.setOperator("sys_patrol_inspect");
                cmd.setRecordBucketName("record");
                cmd.setRecordObjectName("inspect_" + IdUtils.fastSimpleUUID());
                String url = hikClientService.recordStopToMinio(cmd);
                String url = cameraSdkService.recordStopToMinio(cmd);
                /*插入巡检记录*/
                ArdVideoInspectRecord ardVideoInspectRecord = new ArdVideoInspectRecord();
                ardVideoInspectRecord.setStepId(step.getId());
ard-work/src/main/java/com/ruoyi/media/domain/Conf.java
@@ -15,122 +15,11 @@
@Data
public class Conf {
    private String source;
    @JsonProperty("sourceFingerprint")
    private String sourcefingerprint;
    @JsonProperty("sourceOnDemand")
    private boolean sourceondemand;
    @JsonProperty("sourceOnDemandStartTimeout")
    private String sourceondemandstarttimeout;
    @JsonProperty("sourceOnDemandCloseAfter")
    private String sourceondemandcloseafter;
    @JsonProperty("publishUser")
    private String publishuser;
    @JsonProperty("publishPass")
    private String publishpass;
    @JsonProperty("publishIPs")
    private List<String> publiships;
    @JsonProperty("readUser")
    private String readuser;
    @JsonProperty("readPass")
    private String readpass;
    @JsonProperty("readIPs")
    private List<String> readips;
    @JsonProperty("disablePublisherOverride")
    private boolean disablepublisheroverride;
    private String fallback;
    @JsonProperty("sourceProtocol")
    private String sourceprotocol;
    @JsonProperty("sourceAnyPortEnable")
    private boolean sourceanyportenable;
    @JsonProperty("rtspRangeType")
    private String rtsprangetype;
    @JsonProperty("rtspRangeStart")
    private String rtsprangestart;
    @JsonProperty("sourceRedirect")
    private String sourceredirect;
    @JsonProperty("rpiCameraCamID")
    private int rpicameracamid;
    @JsonProperty("rpiCameraWidth")
    private int rpicamerawidth;
    @JsonProperty("rpiCameraHeight")
    private int rpicameraheight;
    @JsonProperty("rpiCameraHFlip")
    private boolean rpicamerahflip;
    @JsonProperty("rpiCameraVFlip")
    private boolean rpicameravflip;
    @JsonProperty("rpiCameraBrightness")
    private int rpicamerabrightness;
    @JsonProperty("rpiCameraContrast")
    private int rpicameracontrast;
    @JsonProperty("rpiCameraSaturation")
    private int rpicamerasaturation;
    @JsonProperty("rpiCameraSharpness")
    private int rpicamerasharpness;
    @JsonProperty("rpiCameraExposure")
    private String rpicameraexposure;
    @JsonProperty("rpiCameraAWB")
    private String rpicameraawb;
    @JsonProperty("rpiCameraDenoise")
    private String rpicameradenoise;
    @JsonProperty("rpiCameraShutter")
    private int rpicamerashutter;
    @JsonProperty("rpiCameraMetering")
    private String rpicamerametering;
    @JsonProperty("rpiCameraGain")
    private int rpicameragain;
    @JsonProperty("rpiCameraEV")
    private int rpicameraev;
    @JsonProperty("rpiCameraROI")
    private String rpicameraroi;
    @JsonProperty("rpiCameraTuningFile")
    private String rpicameratuningfile;
    @JsonProperty("rpiCameraMode")
    private String rpicameramode;
    @JsonProperty("rpiCameraFPS")
    private int rpicamerafps;
    @JsonProperty("rpiCameraIDRPeriod")
    private int rpicameraidrperiod;
    @JsonProperty("rpiCameraBitrate")
    private int rpicamerabitrate;
    @JsonProperty("rpiCameraProfile")
    private String rpicameraprofile;
    @JsonProperty("rpiCameraLevel")
    private String rpicameralevel;
    @JsonProperty("rpiCameraAfMode")
    private String rpicameraafmode;
    @JsonProperty("rpiCameraAfRange")
    private String rpicameraafrange;
    @JsonProperty("rpiCameraAfSpeed")
    private String rpicameraafspeed;
    @JsonProperty("rpiCameraLensPosition")
    private int rpicameralensposition;
    @JsonProperty("rpiCameraAfWindow")
    private String rpicameraafwindow;
    @JsonProperty("rpiCameraTextOverlayEnable")
    private boolean rpicameratextoverlayenable;
    @JsonProperty("rpiCameraTextOverlay")
    private String rpicameratextoverlay;
    @JsonProperty("runOnInit")
    private String runoninit;
    @JsonProperty("runOnInitRestart")
    private boolean runoninitrestart;
    @JsonProperty("runOnDemand")
    private String runondemand;
    @JsonProperty("runOnDemandRestart")
    private boolean runondemandrestart;
    @JsonProperty("runOnDemandStartTimeout")
    private String runondemandstarttimeout;
    @JsonProperty("runOnDemandCloseAfter")
    private String runondemandcloseafter;
    @JsonProperty("runOnReady")
    private String runonready;
    @JsonProperty("runOnReadyRestart")
    private boolean runonreadyrestart;
    @JsonProperty("runOnRead")
    private String runonread;
    @JsonProperty("runOnReadRestart")
    private boolean runonreadrestart;
    @JsonProperty("maxReaders")
    private boolean sourceOnDemand;
    private String sourceProtocol;
    private Integer maxReaders;
    private String runOnDemand;
    private boolean runOnDemandRestart;
    private String runOnDemandStartTimeout;
    private String runOnDemandCloseAfter;
}
ard-work/src/main/java/com/ruoyi/media/domain/Items.java
@@ -19,18 +19,9 @@
public class Items {
    private String name;
    private String confName;
    private Conf conf;
    private Source source;
    private List<Readers> readers;
    private boolean sourceReady;
    private List<String> tracks;
    private String mode;
    private String id;
    private Date created;
    private String remoteAddr;
    private String state;
    private long bytesReceived;
    private long bytesSent;
    private Long bytesReceived;
}
ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
@@ -1,9 +1,6 @@
package com.ruoyi.media.service;
import com.dtflys.forest.annotation.Var;
import com.ruoyi.media.domain.*;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.util.List;
import java.util.Map;
ard-work/src/main/java/com/ruoyi/media/service/IMediaV2Service.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.ruoyi.media.service;
import com.ruoyi.media.domain.*;
import java.util.List;
import java.util.Map;
public interface IMediaV2Service {
    /**
     * å¢žåŠ è·¯å¾„
     * name åç§°
     * rtspPath rtsp地址
     * mode æ¨¡å¼ï¼šgpu硬解码/cpu软解码
     * isCode æ˜¯å¦è½¬ç 
     * åˆ˜è‹ä¹‰
     * 2023/8/12 13:56:52
     */
    Map<String, String> addPath(String name, String sourceUrl, String mode, String isCode);
    /**
     * ä¿®æ”¹è·¯å¾„
     * name åç§°
     * rtspPath rtsp地址
     * mode æ¨¡å¼ï¼šå®žæ—¶/按需
     * isCode æ˜¯å¦è½¬ç 
     * åˆ˜è‹ä¹‰
     * 2023/8/12 13:56:52
     */
    Map<String, String> editPath(String name, String sourceUrl, String mode, String isCode);
    StreamInfo getPathInfo(String name);
    void removePath(String[] names);
    void removePath(String name);
    List<StreamInfo> paths();
    List<String> getNameList();
    boolean checkNameExist(String name);
    RtspSession getRtspSessionById(String sessionId);
    WebrtcSession getWebrtcSessionById(String sessionId);
    RtmpSession getRtmpSessionById(String sessionId);
    List<StreamInfo> getPushStreamList();
    List<StreamInfo> getPullStreamList();
    Boolean kickRtspSession(String sessionId);
    Boolean kickRtmpSession(String sessionId);
    Boolean kickWebrtcSession(String sessionId);
    /**
     * é…ç½®æµåª’体参数
     * åˆ˜è‹ä¹‰
     * 2023/10/13 15:17:57
     */
    public String setConfig(Config config);
}
ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
@@ -1,31 +1,18 @@
package com.ruoyi.media.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.dtflys.forest.Forest;
import com.dtflys.forest.exceptions.ForestNetworkException;
import com.dtflys.forest.exceptions.ForestRuntimeException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.media.domain.*;
import com.ruoyi.media.mapper.VtduMapper;
import com.ruoyi.media.service.IMediaService;
import com.ruoyi.utils.forest.MediaClient;
import com.ruoyi.utils.tools.ArdTool;
import com.ruoyi.utils.process.CmdUtils;
import com.sun.jna.Platform;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -65,34 +52,27 @@
        String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
        String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
        Conf mediaInfo = new Conf();
        Conf conf = new Conf();
        String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
        if (isCode.equals("1")) {
            mediaInfo.setSource("publisher");
            conf.setSource("publisher");
            //默认软解码
            String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
            if (mode.equals("0")) {//硬解码
                cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid  -rtsp_transport tcp  -i " + sourceUrl + " -c:v h264_nvenc  -r 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -bf 0 -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
            }
            mediaInfo.setRunondemand(cmd);
            mediaInfo.setRunondemandrestart(true);
            mediaInfo.setRunondemandcloseafter("5s");
            conf.setRunOnDemand(cmd);
            conf.setRunOnDemandRestart(true);
            conf.setRunOnDemandCloseAfter("5s");
        } else {
            mediaInfo.setSource(sourceUrl);
            mediaInfo.setSourceondemand(true);
            conf.setSource(sourceUrl);
            conf.setSourceOnDemand(true);
        }
        mediaInfo.setMaxReaders(100);
        mediaInfo.setSourceprotocol("tcp");
        conf.setMaxReaders(100);
        conf.setSourceProtocol("tcp");
        List<String> nameList = new ArrayList<>();
        String paths = mediaClient.paths();
        JsonsRoot jsonsRoot = JSONObject.parseObject(paths, JsonsRoot.class);
        List<Items> items = jsonsRoot.getItems();
        for (Items item : items) {
            nameList.add(item.getName());
        }
        if (!nameList.contains(name)) {
            mediaClient.addPath(name, mediaInfo);
        if (!checkNameExist(name)) {
            mediaClient.addPath(name, conf);
        }
        Map<String, String> map = new HashMap<>();
        map.put("rtspUrl", rtspUrl);
@@ -109,26 +89,27 @@
            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
            Conf mediaInfo = new Conf();
            Conf conf = new Conf();
            String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
            if (isCode.equals("1")) {
                mediaInfo.setSource("publisher");
                conf.setSource("publisher");
                //默认软解码
                String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
                if (mode.equals("0")) {//硬解码
                    cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid  -rtsp_transport tcp  -i " + sourceUrl + " -c:v h264_nvenc  -r 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -bf 0 -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
                }
                mediaInfo.setRunondemand(cmd);
                mediaInfo.setRunondemandrestart(true);
                mediaInfo.setRunondemandcloseafter("5s");
                conf.setRunOnDemand(cmd);
                conf.setRunOnDemandRestart(true);
                conf.setRunOnDemandCloseAfter("5s");
            } else {
                mediaInfo.setSource(sourceUrl);
                conf.setSource(sourceUrl);
                conf.setSourceOnDemand(true);
            }
            mediaInfo.setMaxReaders(100);
            mediaInfo.setSourceprotocol("tcp");
            conf.setMaxReaders(100);
            conf.setSourceProtocol("tcp");
            if (checkNameExist(name)) {
                mediaClient.editPath(name, mediaInfo);
                mediaClient.editPath(name, conf);
            }
            map.put("rtspUrl", rtspUrl);
@@ -142,18 +123,14 @@
    @Override
    public StreamInfo getPathInfo(String name) {
        Items item = mediaClient.getPathInfo(name);
        Conf conf = mediaClient.getPathInfo(name);
        StreamInfo info = new StreamInfo();
        //ID
        info.setName(name);
        String runOn;
        if (StringUtils.isNotEmpty(item.getConf().getRunondemand())) {
            runOn = item.getConf().getRunondemand();
        String runOn = "";
        if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
            runOn = conf.getRunOnDemand();
            info.setMode("0");
        } else {
            //runOn = item.getConf().getRunonready();
            runOn = item.getConf().getRunoninit();
            info.setMode("1");
        }
        //RTSP源地址
        Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
@@ -161,7 +138,7 @@
            info.setRtspSource(matcher.group());
            info.setIsCode("1");
        } else {
            info.setRtspSource(item.getConf().getSource());
            info.setRtspSource(conf.getSource());
            info.setIsCode("0");
        }
        return info;
@@ -170,14 +147,15 @@
    @Override
    public void removePath(String[] names) {
        for (String name : names) {
            if(checkNameExist(name)) {
            mediaClient.removePath(name);}
            if (checkNameExist(name)) {
                mediaClient.removePath(name);
            }
        }
    }
    @Override
    public void removePath(String name) {
        if(checkNameExist(name)) {
        if (checkNameExist(name)) {
            mediaClient.removePath(name);
        }
    }
@@ -193,9 +171,11 @@
            //ID
            String name = item.getName();
            info.setName(name);
            Conf conf = mediaClient.getPathInfo(name);
            String runOn = "";
            if (StringUtils.isNotEmpty(item.getConf().getRunondemand())) {
                runOn = item.getConf().getRunondemand();
            if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
                runOn = conf.getRunOnDemand();
            }
            //RTSP源地址
            Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
@@ -203,7 +183,7 @@
                info.setRtspSource(matcher.group());
                info.setIsCode("1");
            } else {
                info.setRtspSource(item.getConf().getSource());
                info.setRtspSource(conf.getSource());
                info.setIsCode("0");
            }
            //传输协议
@@ -216,7 +196,6 @@
        }
        return pathInfoList;
    }
    @Override
    public RtspSession getRtspSessionById(String sessionId) {
@@ -255,6 +234,9 @@
            //ID
            String name = item.getName();
            info.setName(name);
            Conf conf = mediaClient.getPathInfo(name);
            //RTMP播放地址
            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
            info.setRtmpUrl(rtmpUrl);
@@ -291,17 +273,17 @@
            }
            //RTSP源地址
            String runOn = "";
            if (StringUtils.isNotEmpty(item.getConf().getRunondemand())) {
                runOn = item.getConf().getRunondemand();
            if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
                runOn = conf.getRunOnDemand();
            }
            Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
            if (matcher.find()) {
                info.setRtspSource(matcher.group());
            } else {
                info.setRtspSource(item.getConf().getSource());
                info.setRtspSource(conf.getSource());
            }
            //传输协议
            info.setProtocol(item.getConf().getSourceprotocol());
            info.setProtocol(conf.getSourceProtocol());
            //拉流数量
            List<Readers> readers = item.getReaders();
            info.setNum(readers.size());
@@ -329,8 +311,9 @@
                //ID
                String name = item.getName();
                info.setName(name);
                Conf conf = mediaClient.getPathInfo(name);
                //传输协议
                info.setProtocol(item.getConf().getSourceprotocol());
                info.setProtocol(conf.getSourceProtocol());
                String type = reader.getType();
                switch (type) {
@@ -461,17 +444,11 @@
    @Override
    public List<String> getNameList() {
        List<String> nameList = new ArrayList<>();
        try {
            String paths = mediaClient.paths();
            JsonsRoot jsonsRoot = JSONObject.parseObject(paths, JsonsRoot.class);
            List<Items> items = jsonsRoot.getItems();
            for (Items item : items) {
                nameList.add(item.getName());
            }
        }
        catch (Exception ex)
        {
            log.error(ex.getMessage());
        String paths = mediaClient.paths();
        JsonsRoot jsonsRoot = JSONObject.parseObject(paths, JsonsRoot.class);
        List<Items> items = jsonsRoot.getItems();
        for (Items item : items) {
            nameList.add(item.getName());
        }
        return nameList;
    }
ard-work/src/main/java/com/ruoyi/media/service/impl/MediaV2ServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,490 @@
package com.ruoyi.media.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.dtflys.forest.exceptions.ForestNetworkException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.media.domain.*;
import com.ruoyi.media.service.IMediaService;
import com.ruoyi.media.service.IMediaV2Service;
import com.ruoyi.utils.forest.MediaClient;
import com.ruoyi.utils.tools.ArdTool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * @Description: æµåª’体业务
 * @ClassName: MediaService
 * @Author: åˆ˜è‹ä¹‰
 * @Date: 2023å¹´07月13日9:28
 * @Version: 1.0
 **/
@Service
@Slf4j(topic = "cmd")
@Order(2)
public class MediaV2ServiceImpl implements IMediaV2Service {
    @Resource
    MediaClient mediaClient;
    @Value("${mediamtx.host}")
    String mediamtxHost;
    /**
     * æ·»åŠ æµåª’ä½“
     * name ç›¸æœºID
     * sourceUrl rtsp地址
     * isCode 0-不转码 1-转码
     * mode 0-gpu硬解码 1-cpu软解码
     * <p>
     * åˆ˜è‹ä¹‰
     * 2023/10/12 9:03:41
     */
    @Override
    public Map<String, String> addPath(String name, String sourceUrl, String mode, String isCode) {
        String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
        String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
        String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
        Conf conf = new Conf();
        String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
        if (isCode.equals("1")) {
            conf.setSource("publisher");
            //默认软解码
            String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
            if (mode.equals("0")) {//硬解码
                cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid  -rtsp_transport tcp  -i " + sourceUrl + " -c:v h264_nvenc  -r 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -bf 0 -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
            }
            conf.setRunOnDemand(cmd);
            conf.setRunOnDemandRestart(true);
            conf.setRunOnDemandCloseAfter("5s");
        } else {
            conf.setSource(sourceUrl);
            conf.setRunOnDemandRestart(true);
        }
        conf.setMaxReaders(100);
        conf.setSourceProtocol("tcp");
        List<String> nameList = new ArrayList<>();
        String paths = mediaClient.paths();
        JsonsRoot jsonsRoot = JSONObject.parseObject(paths, JsonsRoot.class);
        List<Items> items = jsonsRoot.getItems();
        for (Items item : items) {
            nameList.add(item.getName());
        }
        if (!nameList.contains(name)) {
            mediaClient.addPath(name, conf);
        }
        Map<String, String> map = new HashMap<>();
        map.put("rtspUrl", rtspUrl);
        map.put("rtmpUrl", rtmpUrl);
        map.put("webrtcUrl", webrtcUrl);
        return map;
    }
    @Override
    public Map<String, String> editPath(String name, String sourceUrl, String mode, String isCode) {
        Map<String, String> map = new HashMap<>();
        try {
            String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
            Conf mediaInfo = new Conf();
            String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
            if (isCode.equals("1")) {
                mediaInfo.setSource("publisher");
                //默认软解码
                String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
                if (mode.equals("0")) {//硬解码
                    cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid  -rtsp_transport tcp  -i " + sourceUrl + " -c:v h264_nvenc  -r 25 -g 60 -sc_threshold 0 -threads 6  -b:v 2048k -bf 0 -acodec opus  -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
                }
                mediaInfo.setRunOnDemand(cmd);
                mediaInfo.setRunOnDemandRestart(true);
                mediaInfo.setRunOnDemandCloseAfter("5s");
            } else {
                mediaInfo.setSource(sourceUrl);
            }
            mediaInfo.setMaxReaders(100);
            mediaInfo.setSourceProtocol("tcp");
            if (checkNameExist(name)) {
                mediaClient.editPath(name, mediaInfo);
            }
            map.put("rtspUrl", rtspUrl);
            map.put("rtmpUrl", rtmpUrl);
            map.put("webrtcUrl", webrtcUrl);
        } catch (ForestNetworkException ex) {
            log.error(ex.getMessage());
        }
        return map;
    }
    @Override
    public StreamInfo getPathInfo(String name) {
        Conf conf = mediaClient.getPathInfo(name);
        StreamInfo info = new StreamInfo();
        //ID
        info.setName(name);
        String runOn="";
        if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
            runOn = conf.getRunOnDemand();
        }
        //RTSP源地址
        Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
        if (matcher.find()) {
            info.setRtspSource(matcher.group());
            info.setIsCode("1");
        } else {
            info.setRtspSource(conf.getSource());
            info.setIsCode("0");
        }
        return info;
    }
    @Override
    public void removePath(String[] names) {
        for (String name : names) {
            if(checkNameExist(name)) {
            mediaClient.removePath(name);}
        }
    }
    @Override
    public void removePath(String name) {
        if(checkNameExist(name)) {
            mediaClient.removePath(name);
        }
    }
    @Override
    public List<StreamInfo> paths() {
        String list = mediaClient.paths();
        JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class);
        List<Items> items = jsonsRoot.getItems();
        List<StreamInfo> pathInfoList = new ArrayList<>();
        for (Items item : items) {
            StreamInfo info = new StreamInfo();
            //ID
            String name = item.getName();
            info.setName(name);
            Conf conf = mediaClient.getPathInfo(name);
            String runOn = "";
            if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
                runOn = conf.getRunOnDemand();
            }
            //RTSP源地址
            Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
            if (matcher.find()) {
                info.setRtspSource(matcher.group());
                info.setIsCode("1");
            } else {
                info.setRtspSource(conf.getSource());
                info.setIsCode("0");
            }
            //传输协议
            matcher = Pattern.compile("-rtsp_transport\\s+(\\w+)").matcher(runOn);
            if (matcher.find()) {
                info.setProtocol(matcher.group(1));
            }
            pathInfoList.add(info);
        }
        return pathInfoList;
    }
    @Override
    public RtspSession getRtspSessionById(String sessionId) {
        String list = mediaClient.getRtspsessionById(sessionId);
        RtspSession rtspSession = JSONObject.parseObject(list, RtspSession.class);
        return rtspSession;
    }
    @Override
    public WebrtcSession getWebrtcSessionById(String sessionId) {
        String list = mediaClient.getWebrtcsessionById(sessionId);
        WebrtcSession webrtcSession = JSONObject.parseObject(list, WebrtcSession.class);
        return webrtcSession;
    }
    @Override
    public RtmpSession getRtmpSessionById(String sessionId) {
        String list = mediaClient.getRtmpsessionById(sessionId);
        RtmpSession rtmpSession = JSONObject.parseObject(list, RtmpSession.class);
        return rtmpSession;
    }
    /**
     * èŽ·å–æŽ¨æµåˆ—è¡¨
     * åˆ˜è‹ä¹‰
     * 2023/8/29 9:37:05
     */
    @Override
    public List<StreamInfo> getPushStreamList() {
        List<StreamInfo> PushStreamInfoList = new ArrayList<>();
        String list = mediaClient.paths();
        JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class);
        List<Items> items = jsonsRoot.getItems();
        for (Items item : items) {
            StreamInfo info = new StreamInfo();
            //ID
            String name = item.getName();
            info.setName(name);
            Conf conf = mediaClient.getPathInfo(name);
            //RTMP播放地址
            String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
            info.setRtmpUrl(rtmpUrl);
            //RTSP播放地址
            String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
            info.setRtspUrl(rtspUrl);
            //WEBRTC播放地址
            String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
            info.setWebrtcUrl(webrtcUrl);
            Source source = item.getSource();
            if (source == null || source.getId().equals("")) {
                //会话ID
                info.setId("0");
                //上行流量
                long bytesReceived = item.getBytesReceived();
                String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
                info.setUpTraffic(formatReceivedSize);
            } else {
                RtspSession rtspSession = getRtspSessionById(source.getId());
                //会话ID
                info.setId(rtspSession.getId());
                //开始推流时间
                info.setBeginTime(rtspSession.getCreated());
                //上行流量
                long bytesReceived = rtspSession.getBytesReceived();
                String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
                info.setUpTraffic(formatReceivedSize);
                //下行流量
                long bytesSent = rtspSession.getBytesSent();
                String formatSentSize = ArdTool.formatFileSize(bytesSent);
                info.setDownTraffic(formatSentSize);
                //推流服务器
                info.setRemoteAddr(rtspSession.getRemoteAddr());
            }
            //RTSP源地址
            String runOn = "";
            if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
                runOn = conf.getRunOnDemand();
            }
            Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
            if (matcher.find()) {
                info.setRtspSource(matcher.group());
            } else {
                info.setRtspSource(conf.getSource());
            }
            //传输协议
            info.setProtocol(conf.getSourceProtocol());
            //拉流数量
            List<Readers> readers = item.getReaders();
            info.setNum(readers.size());
            PushStreamInfoList.add(info);
        }
        return PushStreamInfoList;
    }
    /**
     * èŽ·å–æ‹‰æµåˆ—è¡¨
     * åˆ˜è‹ä¹‰
     * 2023/8/29 9:37:05
     */
    @Override
    public List<StreamInfo> getPullStreamList() {
        List<StreamInfo> PullStreamInfoList = new ArrayList<>();
        String list = mediaClient.paths();
        JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class);
        List<Items> items = jsonsRoot.getItems();
        for (Items item : items) {
            List<Readers> readers = item.getReaders();
            for (Readers reader : readers) {
                StreamInfo info = new StreamInfo();
                //ID
                String name = item.getName();
                info.setName(name);
                Conf conf = mediaClient.getPathInfo(name);
                //传输协议
                info.setProtocol(conf.getSourceProtocol());
                String type = reader.getType();
                switch (type) {
                    case "rtmpConn":
                        info.setSessionType("rtmp");
                        //webrtc播放地址
                        String url = "rtmp://" + mediamtxHost + ":1935/" + name;
                        info.setRtspUrl(url);
                        RtmpSession rtmpSession = getRtmpSessionById(reader.getId());
                        //会话ID
                        info.setId(rtmpSession.getId());
                        //开始拉流时间
                        info.setBeginTime(rtmpSession.getCreated());
                        //上行流量
                        long bytesReceived = rtmpSession.getBytesReceived();
                        String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
                        info.setUpTraffic(formatReceivedSize);
                        //下行流量
                        long bytesSent = rtmpSession.getBytesSent();
                        String formatSentSize = ArdTool.formatFileSize(bytesSent);
                        info.setDownTraffic(formatSentSize);
                        //拉流服务器
                        info.setRemoteAddr(rtmpSession.getRemoteAddr());
                        PullStreamInfoList.add(info);
                        break;
                    case "webRTCSession":
                        info.setSessionType("webrtc");
                        //webrtc播放地址
                        url = "http://" + mediamtxHost + ":8889/" + name;
                        info.setRtspUrl(url);
                        WebrtcSession webrtcSession = getWebrtcSessionById(reader.getId());
                        //会话ID
                        info.setId(webrtcSession.getId());
                        //开始拉流时间
                        info.setBeginTime(webrtcSession.getCreated());
                        //上行流量
                        bytesReceived = webrtcSession.getBytesReceived();
                        formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
                        info.setUpTraffic(formatReceivedSize);
                        //下行流量
                        bytesSent = webrtcSession.getBytesSent();
                        formatSentSize = ArdTool.formatFileSize(bytesSent);
                        info.setDownTraffic(formatSentSize);
                        //拉流服务器
                        info.setRemoteAddr(webrtcSession.getRemoteAddr());
                        PullStreamInfoList.add(info);
                        break;
                    case "rtspSession":
                        info.setSessionType("rtsp");
                        //RTSP播放地址
                        String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
                        info.setRtspUrl(rtspUrl);
                        RtspSession rtspSession = getRtspSessionById(reader.getId());
                        //会话ID
                        info.setId(rtspSession.getId());
                        //开始拉流时间
                        info.setBeginTime(rtspSession.getCreated());
                        //上行流量
                        bytesReceived = rtspSession.getBytesReceived();
                        formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
                        info.setUpTraffic(formatReceivedSize);
                        //下行流量
                        bytesSent = rtspSession.getBytesSent();
                        formatSentSize = ArdTool.formatFileSize(bytesSent);
                        info.setDownTraffic(formatSentSize);
                        //拉流服务器
                        info.setRemoteAddr(rtspSession.getRemoteAddr());
                        PullStreamInfoList.add(info);
                        break;
                }
            }
        }
        Comparator<StreamInfo> comparator = Comparator.comparing(streamInfo -> streamInfo.getBeginTime()); // ä½¿ç”¨Collections.sort方法进行排序 Collections.sort(personList, comparator);
        Collections.sort(PullStreamInfoList, comparator.reversed());
        return PullStreamInfoList;
    }
    /**
     * è¸¢å‡ºrtsp会话
     * åˆ˜è‹ä¹‰
     * 2023/8/29 9:37:05
     */
    @Override
    public Boolean kickRtspSession(String sessionId) {
        try {
            mediaClient.kickRtspSessions(sessionId);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
    /**
     * è¸¢å‡ºrtmp会话
     * åˆ˜è‹ä¹‰
     * 2023/8/29 9:37:05
     */
    @Override
    public Boolean kickRtmpSession(String sessionId) {
        try {
            mediaClient.kickRtmpSessions(sessionId);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
    /**
     * è¸¢å‡ºwebrtc会话
     * åˆ˜è‹ä¹‰
     * 2023/8/29 9:37:05
     */
    @Override
    public Boolean kickWebrtcSession(String sessionId) {
        try {
            mediaClient.kickWebrtcSessions(sessionId);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
    /**
     * èŽ·å–æµåª’ä½“name列表
     * åˆ˜è‹ä¹‰
     * 2023/10/13 14:19:07
     */
    @Override
    public List<String> getNameList() {
        List<String> nameList = new ArrayList<>();
        try {
            String paths = mediaClient.paths();
            JsonsRoot jsonsRoot = JSONObject.parseObject(paths, JsonsRoot.class);
            List<Items> items = jsonsRoot.getItems();
            for (Items item : items) {
                nameList.add(item.getName());
            }
        }
        catch (Exception ex)
        {
            log.error(ex.getMessage());
        }
        return nameList;
    }
    /**
     * æ£€æŸ¥åç§°æ˜¯å¦å­˜åœ¨
     * åˆ˜è‹ä¹‰
     * 2023/10/19 15:18:45
     */
    @Override
    public boolean checkNameExist(String name) {
        boolean result = false;
        List<String> nameList = getNameList();
        if (nameList.contains(name)) {
            result = true;
        }
        return result;
    }
    /**
     * é…ç½®æµåª’体参数
     */
    @Override
    public String setConfig(Config config) {
        return mediaClient.setConfig(config);
    }
}
ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java
@@ -1,21 +1,11 @@
package com.ruoyi.media.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.device.camera.service.ICameraSdkService;
import com.ruoyi.media.service.IMediaService;
import com.ruoyi.utils.forest.MediaClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.ruoyi.media.mapper.VtduMapper;
import com.ruoyi.media.domain.Vtdu;
@@ -30,7 +20,7 @@
 * @date 2023-08-29
 */
@Service
@Slf4j
@Slf4j(topic = "sdk")
public class VtduServiceImpl implements IVtduService {
    @Resource
    private VtduMapper vtduMapper;
@@ -68,7 +58,7 @@
     */
    @Override
    public int insertVtdu(Vtdu vtdu) {
        log.info("流媒体【" + vtdu.getName() + "】通道添加");
        log.debug("流媒体【" + vtdu.getName() + "】通道添加");
        Map<String, String> map = mediaService.addPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
        vtdu.setRtspUrl(map.get("rtspUrl"));
        vtdu.setRtmpUrl(map.get("rtmpUrl"));
@@ -91,7 +81,7 @@
    @Override
    public int updateVtdu(Vtdu vtdu) {
        log.info("流媒体【" + vtdu.getName() + "】通道更新");
        log.debug("流媒体【" + vtdu.getName() + "】通道更新");
        Map<String, String> map = mediaService.editPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
        vtdu.setName(vtdu.getName());
        vtdu.setRtspSource(vtdu.getRtspSource());
@@ -112,6 +102,9 @@
     */
    @Override
    public int deleteVtduByNames(String[] names) {
        for (String name : names) {
            log.debug("流媒体【" + name + "】通道删除");
        }
        mediaService.removePath(names);
        return vtduMapper.deleteVtduByNames(names);
    }
@@ -124,6 +117,7 @@
     */
    @Override
    public int deleteVtduByName(String name) {
        log.debug("流媒体【" + name + "】通道删除");
        mediaService.removePath(name);
        return vtduMapper.deleteVtduByName(name);
    }
ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
@@ -1,7 +1,6 @@
package com.ruoyi.utils.forest;
import com.dtflys.forest.annotation.*;
import com.dtflys.forest.callback.OnError;
import com.ruoyi.media.domain.Conf;
import com.ruoyi.media.domain.Config;
import com.ruoyi.media.domain.Items;
@@ -13,7 +12,7 @@
 * @Date: 2023å¹´07月06日9:51
 * @Version: 1.0
 **/
@BaseRequest(baseURL = "http://#{mediamtx.host}:9997/v2")
@BaseRequest(baseURL = "http://#{mediamtx.host}:9997/v3")
public interface MediaClient {
    /**
     * å¢žåŠ è·¯å¾„
@@ -24,20 +23,20 @@
    /**
     * ä¿®æ”¹è·¯å¾„
     */
    @Post(url = "/config/paths/edit/{name}", async = true)
    @Patch(url = "/config/paths/patch/{name}")
    public String editPath(@Var("name") String name, @JSONBody Conf body);
    /**
     * ç§»é™¤è·¯å¾„
     */
    @Post("/config/paths/remove/{name}")
    @Delete("/config/paths/delete/{name}")
    public String removePath(@Var("name") String name);
    /**
     * èŽ·å–è·¯å¾„è¯¦æƒ…
     */
    @Get("/paths/get/{name}")
    public Items getPathInfo(@Var("name") String name);
    @Get("/config/paths/get/{name}")
    public Conf getPathInfo(@Var("name") String name);
    /**
     * æŸ¥è¯¢æ‰€æœ‰è·¯å¾„
@@ -50,12 +49,6 @@
     */
    @Get("/rtspsessions/list")
    public String rtspsessions();
    /**
     * æŸ¥è¯¢æ‰€æœ‰rtsp连接
     */
    @Get("/rtspconns/list")
    public String rtspconns();
    /**
     * æŒ‰sessionId查询rtsp会话
ard-work/src/main/java/com/ruoyi/utils/forest/MediaClientV2.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package com.ruoyi.utils.forest;
import com.dtflys.forest.annotation.*;
import com.ruoyi.media.domain.Conf;
import com.ruoyi.media.domain.Config;
import com.ruoyi.media.domain.Items;
/**
 * @Description: mediamtx流媒体客户端
 * @ClassName: client
 * @Author: åˆ˜è‹ä¹‰
 * @Date: 2023å¹´07月06日9:51
 * @Version: 1.0
 **/
@BaseRequest(baseURL = "http://#{mediamtx.host}:9997/v2")
public interface MediaClientV2 {
    /**
     * å¢žåŠ è·¯å¾„
     */
    @Post(url = "/config/paths/add/{name}")
    public String addPath(@Var("name") String name, @JSONBody Conf body);
    /**
     * ä¿®æ”¹è·¯å¾„
     */
    @Post(url = "/config/paths/edit/{name}", async = true)
    public String editPath(@Var("name") String name, @JSONBody Conf body);
    /**
     * ç§»é™¤è·¯å¾„
     */
    @Post("/config/paths/remove/{name}")
    public String removePath(@Var("name") String name);
    /**
     * èŽ·å–è·¯å¾„è¯¦æƒ…
     */
    @Get("/paths/get/{name}")
    public Items getPathInfo(@Var("name") String name);
    /**
     * æŸ¥è¯¢æ‰€æœ‰è·¯å¾„
     */
    @Get("/paths/list")
    public String paths();
    /**
     * æŸ¥è¯¢æ‰€æœ‰rtsp会话
     */
    @Get("/rtspsessions/list")
    public String rtspsessions();
    /**
     * æŸ¥è¯¢æ‰€æœ‰rtsp连接
     */
    @Get("/rtspconns/list")
    public String rtspconns();
    /**
     * æŒ‰sessionId查询rtsp会话
     */
    @Get("/rtspsessions/get/{sessionId}")
    public String getRtspsessionById(@Var("sessionId") String sessionId);
    /**
     * æŒ‰sessionId查询webrtc会话
     */
    @Get("/webrtcsessions/get/{sessionId}")
    public String getWebrtcsessionById(@Var("sessionId") String sessionId);
    /**
     * æŒ‰sessionId查询rtmp会话
     */
    @Get("/rtmpconns/get/{sessionId}")
    public String getRtmpsessionById(@Var("sessionId") String sessionId);
    /**
     * æŒ‰sessionId删除rtsp会话
     */
    @Post("/rtspsessions/kick/{sessionId}")
    public String kickRtspSessions(@Var("sessionId") String sessionId);
    /**
     * æŒ‰sessionId删除rtmp连接
     */
    @Post("/rtmpconns/kick/{sessionId}")
    public String kickRtmpSessions(@Var("sessionId") String sessionId);
    /**
     * æŒ‰sessionId删除webrtc会话
     */
    @Post("/webrtcsessions/kick/{sessionId}")
    public String kickWebrtcSessions(@Var("sessionId") String sessionId);
    /**
     * é…ç½®æµåª’体参数
     */
    @Post("/config/set")
    public String setConfig(@JSONBody Config config);
}
ard-work/src/main/resources/templates/preview.html
@@ -190,6 +190,9 @@
            console.log("requesting ICE servers");
            fetch(this.wurl, {
                method: 'OPTIONS',
                headers: {
                    'Referer': this.wurl,
                },
            })
                .then((res) => this.onIceServers(res))
                .catch((err) => {
@@ -229,6 +232,7 @@
                method: 'POST',
                headers: {
                    'Content-Type': 'application/sdp',
                    'Referer': this.wurl,
                },
                body: offer.sdp,
            })
@@ -296,7 +300,9 @@
                headers: {
                    'Content-Type': 'application/trickle-ice-sdpfrag',
                    'If-Match': this.eTag,
                    'Referer': this.wurl,
                },
                body: generateSdpFragment(this.offerData, candidates),
            })
                .then((res) => {
lib/mediamtx/LICENSE
@@ -19,3 +19,8 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
internal/core/hls.min.js is Copyright (c) Dailymotion and is protected by
its own license (Apache License, Version 2.0) available at
https://github.com/video-dev/hls.js/blob/master/LICENSE
lib/mediamtx/mediamtx.exe
Binary files differ
lib/mediamtx/mediamtx.yml
@@ -1,8 +1,12 @@
###############################################
# Global settings
# Settings in this section are applied anywhere.
###############################################
# General settings
# Global settings -> General
# Sets the verbosity of the program; available values are "error", "warn", "info", "debug".
# Verbosity of the program; available values are "error", "warn", "info", "debug".
logLevel: info
# Destinations of log messages; available values are "stdout", "file" and "syslog".
logDestinations: [stdout]
@@ -40,29 +44,33 @@
# Enable the HTTP API.
api: yes
# Address of the API listener.
apiAddress: 192.168.1.227:9997
apiAddress: 112.98.126.2:9997
# Enable Prometheus-compatible metrics.
metrics: no
# Address of the metrics listener.
metricsAddress: 127.0.0.1:9998
metricsAddress: 112.98.126.2:9998
# Enable pprof-compatible endpoint to monitor performances.
pprof: no
# Address of the pprof listener.
pprofAddress: 127.0.0.1:9999
pprofAddress: 112.98.126.2:9999
# Command to run when a client connects to the server.
# Prepend ./ to run an executable in the current folder (example: "./ffmpeg")
# This is terminated with SIGINT when a client disconnects from the server.
# The following environment variables are available:
# * RTSP_PORT: RTSP server port
# * MTX_CONN_TYPE: connection type
# * MTX_CONN_ID: connection ID
runOnConnect:
# Restart the command if it exits.
runOnConnectRestart: no
# Command to run when a client disconnects from the server.
# Environment variables are the same of runOnConnect.
runOnDisconnect:
###############################################
# RTSP settings
# Global settings -> RTSP
# Allow publishing and reading streams with the RTSP protocol.
rtsp: yes
@@ -77,19 +85,19 @@
# Available values are "no", "strict", "optional".
encryption: "no"
# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional".
rtspAddress: :7554
rtspAddress: :8554
# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional".
rtspsAddress: :7322
rtspsAddress: :8322
# Address of the UDP/RTP listener. This is needed only when "udp" is in protocols.
rtpAddress: :7000
rtpAddress: :8000
# Address of the UDP/RTCP listener. This is needed only when "udp" is in protocols.
rtcpAddress: :7001
rtcpAddress: :8001
# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in protocols.
multicastIPRange: 224.1.0.0/16
# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in protocols.
multicastRTPPort: 7002
multicastRTPPort: 8002
# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in protocols.
multicastRTCPPort: 7003
multicastRTCPPort: 8003
# Path to the server key. This is needed only when encryption is "strict" or "optional".
# This can be generated with:
# openssl genrsa -out server.key 2048
@@ -102,7 +110,7 @@
authMethods: [basic]
###############################################
# RTMP settings
# Global settings -> RTMP
# Allow publishing and reading streams with the RTMP protocol.
rtmp: yes
@@ -122,7 +130,7 @@
rtmpServerCert: server.crt
###############################################
# HLS settings
# Global settings -> HLS
# Allow reading streams with the HLS protocol.
hls: no
@@ -178,7 +186,7 @@
hlsDirectory: ''
###############################################
# WebRTC settings
# Global settings -> WebRTC
# Allow publishing and reading streams with the WebRTC protocol.
webrtc: yes
@@ -207,27 +215,30 @@
  # needed when server and clients are on different LANs.
  # TURN/TURNS servers are needed when a direct connection between server and
  # clients is not possible. All traffic is routed through them.
- url: stun:stun.l.google.com:19302
- url: stun:112.98.126.2:3478
  # if user is "AUTH_SECRET", then authentication is secret based.
  # the secret must be inserted into the password field.
  username: ''
  password: ''
  username: 'admin'
  password: '123456'
# List of interfaces that will be used to gather IPs to send
# to the counterpart to establish a connection.
webrtcICEInterfaces: []
# List of public IP addresses that are to be used as a host.
# This is used typically for servers that are behind 1:1 D-NAT.
webrtcICEHostNAT1To1IPs: [192.168.1.227]
webrtcICEHostNAT1To1IPs: [112.98.126.2]
# Address of a ICE UDP listener in format host:port.
# If filled, ICE traffic will pass through a single UDP port,
# allowing the deployment of the server inside a container or behind a NAT.
webrtcICEUDPMuxAddress: 192.168.1.227:8189
webrtcICEUDPMuxAddress:
# Address of a ICE TCP listener in format host:port.
# If filled, ICE traffic will pass through a single TCP port,
# allowing the deployment of the server inside a container or behind a NAT.
# Using this setting forces usage of the TCP protocol, which is not
# optimal for WebRTC.
webrtcICETCPMuxAddress: 192.168.1.227:8189
webrtcICETCPMuxAddress: 112.98.126.2:1234
###############################################
# SRT settings
# Global settings -> SRT
# Allow publishing and reading streams with the SRT protocol.
srt: yes
@@ -235,246 +246,311 @@
srtAddress: :8890
###############################################
# Default path settings
# Settings in "pathDefaults" are applied anywhere,
# unless they are overridden in "paths".
pathDefaults:
  ###############################################
  # Default path settings -> General
  # Source of the stream. This can be:
  # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client
  # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera
  # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS
  # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera
  # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS
  # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera
  # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS
  # * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port
  # * srt://existing-url -> the stream is pulled from another SRT server / camera
  # * whep://existing-url -> the stream is pulled from another WebRTC server / camera
  # * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
  # * redirect -> the stream is provided by another path or server
  # * rpiCamera -> the stream is provided by a Raspberry Pi Camera
  source: publisher
  # If the source is a URL, and the source certificate is self-signed
  # or invalid, you can provide the fingerprint of the certificate in order to
  # validate it anyway. It can be obtained by running:
  # openssl s_client -connect source_ip:source_port </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
  # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
  sourceFingerprint:
  # If the source is a URL, it will be pulled only when at least
  # one reader is connected, saving bandwidth.
  sourceOnDemand: no
  # If sourceOnDemand is "yes", readers will be put on hold until the source is
  # ready or until this amount of time has passed.
  sourceOnDemandStartTimeout: 10s
  # If sourceOnDemand is "yes", the source will be closed when there are no
  # readers connected and this amount of time has passed.
  sourceOnDemandCloseAfter: 10s
  # Maximum number of readers. Zero means no limit.
  maxReaders: 0
  # SRT encryption passphrase require to read from this path
  srtReadPassphrase:
  ###############################################
  # Default path settings -> Recording
  # Record streams to disk.
  record: no
  # Path of recording segments.
  # Extension is added automatically.
  # Available variables are %path (path name), %Y %m %d %H %M %S %f (time in strftime format)
  recordPath: ./recordings/%path/%Y-%m-%d_%H-%M-%S-%f
  # Format of recorded segments.
  # Available formats are "fmp4" (fragmented MP4) and "mpegts" (MPEG-TS).
  recordFormat: fmp4
  # fMP4 segments are concatenation of small MP4 files (parts), each with this duration.
  # MPEG-TS segments are concatenation of 188-bytes packets, flushed to disk with this period.
  # When a system failure occurs, the last part gets lost.
  # Therefore, the part duration is equal to the RPO (recovery point objective).
  recordPartDuration: 100ms
  # Minimum duration of each segment.
  recordSegmentDuration: 1h
  # Delete segments after this timespan.
  # Set to 0s to disable automatic deletion.
  recordDeleteAfter: 24h
  ###############################################
  # Default path settings -> Authentication
  # Username required to publish.
  # SHA256-hashed values can be inserted with the "sha256:" prefix.
  publishUser:
  # Password required to publish.
  # SHA256-hashed values can be inserted with the "sha256:" prefix.
  publishPass:
  # IPs or networks (x.x.x.x/24) allowed to publish.
  publishIPs: []
  # Username required to read.
  # SHA256-hashed values can be inserted with the "sha256:" prefix.
  readUser:
  # password required to read.
  # SHA256-hashed values can be inserted with the "sha256:" prefix.
  readPass:
  # IPs or networks (x.x.x.x/24) allowed to read.
  readIPs: []
  ###############################################
  # Default path settings -> Publisher source (when source is "publisher")
  # allow another client to disconnect the current publisher and publish in its place.
  overridePublisher: yes
  # if no one is publishing, redirect readers to this path.
  # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
  fallback:
  # SRT encryption passphrase required to publish to this path
  srtPublishPassphrase:
  ###############################################
  # Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL)
  # protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
  sourceProtocol: automatic
  # support sources that don't provide server ports or use random server ports. This is a security issue
  # and must be used only when interacting with sources that require it.
  sourceAnyPortEnable: no
  # range header to send to the source, in order to start streaming from the specified offset.
  # available values:
  # * clock: Absolute time
  # * npt: Normal Play Time
  # * smpte: SMPTE timestamps relative to the start of the recording
  rtspRangeType:
  # available values:
  # * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z
  # * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
  # * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
  rtspRangeStart:
  ###############################################
  # Default path settings -> Redirect source (when source is "redirect")
  # RTSP URL which clients will be redirected to.
  sourceRedirect:
  ###############################################
  # Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera")
  # ID of the camera
  rpiCameraCamID: 0
  # width of frames
  rpiCameraWidth: 1920
  # height of frames
  rpiCameraHeight: 1080
  # flip horizontally
  rpiCameraHFlip: false
  # flip vertically
  rpiCameraVFlip: false
  # brightness [-1, 1]
  rpiCameraBrightness: 0
  # contrast [0, 16]
  rpiCameraContrast: 1
  # saturation [0, 16]
  rpiCameraSaturation: 1
  # sharpness [0, 16]
  rpiCameraSharpness: 1
  # exposure mode.
  # values: normal, short, long, custom
  rpiCameraExposure: normal
  # auto-white-balance mode.
  # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom
  rpiCameraAWB: auto
  # denoise operating mode.
  # values: off, cdn_off, cdn_fast, cdn_hq
  rpiCameraDenoise: "off"
  # fixed shutter speed, in microseconds.
  rpiCameraShutter: 0
  # metering mode of the AEC/AGC algorithm.
  # values: centre, spot, matrix, custom
  rpiCameraMetering: centre
  # fixed gain
  rpiCameraGain: 0
  # EV compensation of the image [-10, 10]
  rpiCameraEV: 0
  # Region of interest, in format x,y,width,height
  rpiCameraROI:
  # whether to enable HDR on Raspberry Camera 3.
  rpiCameraHDR: false
  # tuning file
  rpiCameraTuningFile:
  # sensor mode, in format [width]:[height]:[bit-depth]:[packing]
  # bit-depth and packing are optional.
  rpiCameraMode:
  # frames per second
  rpiCameraFPS: 30
  # period between IDR frames
  rpiCameraIDRPeriod: 60
  # bitrate
  rpiCameraBitrate: 1000000
  # H264 profile
  rpiCameraProfile: main
  # H264 level
  rpiCameraLevel: '4.1'
  # Autofocus mode
  # values: auto, manual, continuous
  rpiCameraAfMode: auto
  # Autofocus range
  # values: normal, macro, full
  rpiCameraAfRange: normal
  # Autofocus speed
  # values: normal, fast
  rpiCameraAfSpeed: normal
  # Lens position (for manual autofocus only), will be set to focus to a specific distance
  # calculated by the following formula: d = 1 / value
  # Examples: 0 moves the lens to infinity.
  #           0.5 moves the lens to focus on objects 2m away.
  #           2 moves the lens to focus on objects 50cm away.
  rpiCameraLensPosition: 0.0
  # Specifies the autofocus window, in the form x,y,width,height where the coordinates
  # are given as a proportion of the entire image.
  rpiCameraAfWindow:
  # enables printing text on each frame.
  rpiCameraTextOverlayEnable: false
  # text that is printed on each frame.
  # format is the one of the strftime() function.
  rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX'
  ###############################################
  # Default path settings -> Hooks
  # Command to run when this path is initialized.
  # This can be used to publish a stream when the server is launched.
  # This is terminated with SIGINT when the program closes.
  # The following environment variables are available:
  # * MTX_PATH: path name
  # * RTSP_PORT: RTSP server port
  # * G1, G2, ...: regular expression groups, if path name is
  #   a regular expression.
  runOnInit:
  # Restart the command if it exits.
  runOnInitRestart: no
  # Command to run when this path is requested by a reader.
  # This can be used to publish a stream on demand.
  # This is terminated with SIGINT when the path is not requested anymore.
  # The following environment variables are available:
  # * MTX_PATH: path name
  # * RTSP_PORT: RTSP server port
  # * G1, G2, ...: regular expression groups, if path name is
  #   a regular expression.
  runOnDemand:
  # Restart the command if it exits.
  runOnDemandRestart: no
  # Readers will be put on hold until the runOnDemand command starts publishing
  # or until this amount of time has passed.
  runOnDemandStartTimeout: 10s
  # The command will be closed when there are no
  # readers connected and this amount of time has passed.
  runOnDemandCloseAfter: 10s
  # Command to run when the stream is ready to be read, whenever it is
  # published by a client or pulled from a server / camera.
  # This is terminated with SIGINT when the stream is not ready anymore.
  # The following environment variables are available:
  # * MTX_PATH: path name
  # * RTSP_PORT: RTSP server port
  # * G1, G2, ...: regular expression groups, if path name is
  #   a regular expression.
  # * MTX_SOURCE_TYPE: source type
  # * MTX_SOURCE_ID: source ID
  runOnReady:
  # Restart the command if it exits.
  runOnReadyRestart: no
  # Command to run when the stream is not available anymore.
  # Environment variables are the same of runOnReady.
  runOnNotReady:
  # Command to run when a client starts reading.
  # This is terminated with SIGINT when a client stops reading.
  # The following environment variables are available:
  # * MTX_PATH: path name
  # * RTSP_PORT: RTSP server port
  # * G1, G2, ...: regular expression groups, if path name is
  #   a regular expression.
  # * MTX_READER_TYPE: reader type
  # * MTX_READER_ID: reader ID
  runOnRead:
  # Restart the command if it exits.
  runOnReadRestart: no
  # Command to run when a client stops reading.
  # Environment variables are the same of runOnRead.
  runOnUnread:
  # Command to run when a recording segment is created.
  # The following environment variables are available:
  # * MTX_PATH: path name
  # * RTSP_PORT: RTSP server port
  # * G1, G2, ...: regular expression groups, if path name is
  #   a regular expression.
  # * MTX_SEGMENT_PATH: segment file path
  runOnRecordSegmentCreate:
  # Command to run when a recording segment is complete.
  # The following environment variables are available:
  # * MTX_PATH: path name
  # * RTSP_PORT: RTSP server port
  # * G1, G2, ...: regular expression groups, if path name is
  #   a regular expression.
  # * MTX_SEGMENT_PATH: segment file path
  runOnRecordSegmentComplete:
###############################################
# Path settings
# These settings are path-dependent, and the map key is the name of the path.
# Settings in "paths" are applied to specific paths, and the map key
# is the name of the path.
# Any setting in "pathDefaults" can be overridden here.
# It's possible to use regular expressions by using a tilde as prefix,
# for example "~^(test1|test2)$" will match both "test1" and "test2",
# for example "~^prefix" will match all paths that start with "prefix".
# Settings under the path "all" are applied to all paths that do not match
# another entry.
paths:
  all:
    ###############################################
    # General path settings
  # example:
  # my_camera:
  #   source: rtsp://my_camera
    # Source of the stream. This can be:
    # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client
    # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera
    # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS
    # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera
    # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS
    # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera
    # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS
    # * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port
    # * srt://existing-url -> the stream is pulled from another SRT server / camera
    # * whep://existing-url -> the stream is pulled from another WebRTC server / camera
    # * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
    # * redirect -> the stream is provided by another path or server
    # * rpiCamera -> the stream is provided by a Raspberry Pi Camera
    source: publisher
    # If the source is a URL, and the source certificate is self-signed
    # or invalid, you can provide the fingerprint of the certificate in order to
    # validate it anyway. It can be obtained by running:
    # openssl s_client -connect source_ip:source_port </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
    # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
    sourceFingerprint:
    # If the source is a URL, it will be pulled only when at least
    # one reader is connected, saving bandwidth.
    sourceOnDemand: no
    # If sourceOnDemand is "yes", readers will be put on hold until the source is
    # ready or until this amount of time has passed.
    sourceOnDemandStartTimeout: 10s
    # If sourceOnDemand is "yes", the source will be closed when there are no
    # readers connected and this amount of time has passed.
    sourceOnDemandCloseAfter: 10s
    # Maximum number of readers. Zero means no limit.
    maxReaders: 0
    ###############################################
    # Authentication path settings
    # Username required to publish.
    # SHA256-hashed values can be inserted with the "sha256:" prefix.
    publishUser:
    # Password required to publish.
    # SHA256-hashed values can be inserted with the "sha256:" prefix.
    publishPass:
    # IPs or networks (x.x.x.x/24) allowed to publish.
    publishIPs: []
    # Username required to read.
    # SHA256-hashed values can be inserted with the "sha256:" prefix.
    readUser:
    # password required to read.
    # SHA256-hashed values can be inserted with the "sha256:" prefix.
    readPass:
    # IPs or networks (x.x.x.x/24) allowed to read.
    readIPs: []
    ###############################################
    # Publisher path settings (when source is "publisher")
    # allow another client to disconnect the current publisher and publish in its place.
    overridePublisher: yes
    # if no one is publishing, redirect readers to this path.
    # It can be can be a relative path  (i.e. /otherstream) or an absolute RTSP URL.
    fallback:
    ###############################################
    # RTSP path settings (when source is a RTSP or a RTSPS URL)
    # protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
    sourceProtocol: automatic
    # support sources that don't provide server ports or use random server ports. This is a security issue
    # and must be used only when interacting with sources that require it.
    sourceAnyPortEnable: no
    # range header to send to the source, in order to start streaming from the specified offset.
    # available values:
    # * clock: Absolute time
    # * npt: Normal Play Time
    # * smpte: SMPTE timestamps relative to the start of the recording
    rtspRangeType:
    # available values:
    # * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z
    # * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
    # * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
    rtspRangeStart:
    ###############################################
    # Redirect path settings (when source is "redirect")
    # RTSP URL which clients will be redirected to.
    sourceRedirect:
    ###############################################
    # Raspberry Pi Camera path settings (when source is "rpiCamera")
    # ID of the camera
    rpiCameraCamID: 0
    # width of frames
    rpiCameraWidth: 1920
    # height of frames
    rpiCameraHeight: 1080
    # flip horizontally
    rpiCameraHFlip: false
    # flip vertically
    rpiCameraVFlip: false
    # brightness [-1, 1]
    rpiCameraBrightness: 0
    # contrast [0, 16]
    rpiCameraContrast: 1
    # saturation [0, 16]
    rpiCameraSaturation: 1
    # sharpness [0, 16]
    rpiCameraSharpness: 1
    # exposure mode.
    # values: normal, short, long, custom
    rpiCameraExposure: normal
    # auto-white-balance mode.
    # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom
    rpiCameraAWB: auto
    # denoise operating mode.
    # values: off, cdn_off, cdn_fast, cdn_hq
    rpiCameraDenoise: "off"
    # fixed shutter speed, in microseconds.
    rpiCameraShutter: 0
    # metering mode of the AEC/AGC algorithm.
    # values: centre, spot, matrix, custom
    rpiCameraMetering: centre
    # fixed gain
    rpiCameraGain: 0
    # EV compensation of the image [-10, 10]
    rpiCameraEV: 0
    # Region of interest, in format x,y,width,height
    rpiCameraROI:
    # whether to enable HDR on Raspberry Camera 3.
    rpiCameraHDR: false
    # tuning file
    rpiCameraTuningFile:
    # sensor mode, in format [width]:[height]:[bit-depth]:[packing]
    # bit-depth and packing are optional.
    rpiCameraMode:
    # frames per second
    rpiCameraFPS: 30
    # period between IDR frames
    rpiCameraIDRPeriod: 60
    # bitrate
    rpiCameraBitrate: 1000000
    # H264 profile
    rpiCameraProfile: main
    # H264 level
    rpiCameraLevel: '4.1'
    # Autofocus mode
    # values: auto, manual, continuous
    rpiCameraAfMode: auto
    # Autofocus range
    # values: normal, macro, full
    rpiCameraAfRange: normal
    # Autofocus speed
    # values: normal, fast
    rpiCameraAfSpeed: normal
    # Lens position (for manual autofocus only), will be set to focus to a specific distance
    # calculated by the following formula: d = 1 / value
    # Examples: 0 moves the lens to infinity.
    #           0.5 moves the lens to focus on objects 2m away.
    #           2 moves the lens to focus on objects 50cm away.
    rpiCameraLensPosition: 0.0
    # Specifies the autofocus window, in the form x,y,width,height where the coordinates
    # are given as a proportion of the entire image.
    rpiCameraAfWindow:
    # enables printing text on each frame.
    rpiCameraTextOverlayEnable: false
    # text that is printed on each frame.
    # format is the one of the strftime() function.
    rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX'
    ###############################################
    # External commands path settings
    # Command to run when this path is initialized.
    # This can be used to publish a stream and keep it always opened.
    # Prepend ./ to run an executable in the current folder (example: "./ffmpeg")
    # This is terminated with SIGINT when the program closes.
    # The following environment variables are available:
    # * MTX_PATH: path name
    # * RTSP_PORT: RTSP server port
    # * G1, G2, ...: regular expression groups, if path name is
    #   a regular expression.
    runOnInit:
    # Restart the command if it exits.
    runOnInitRestart: no
    # Command to run when this path is requested.
    # This can be used to publish a stream on demand.
    # Prepend ./ to run an executable in the current folder (example: "./ffmpeg")
    # This is terminated with SIGINT when the path is not requested anymore.
    # The following environment variables are available:
    # * MTX_PATH: path name
    # * RTSP_PORT: RTSP server port
    # * G1, G2, ...: regular expression groups, if path name is
    #   a regular expression.
    runOnDemand:
    # Restart the command if it exits.
    runOnDemandRestart: no
    # Readers will be put on hold until the runOnDemand command starts publishing
    # or until this amount of time has passed.
    runOnDemandStartTimeout: 10s
    # The command will be closed when there are no
    # readers connected and this amount of time has passed.
    runOnDemandCloseAfter: 10s
    # Command to run when the stream is ready to be read, whether it is
    # published by a client or pulled from a server / camera.
    # Prepend ./ to run an executable in the current folder (example: "./ffmpeg")
    # This is terminated with SIGINT when the stream is not ready anymore.
    # The following environment variables are available:
    # * MTX_PATH: path name
    # * RTSP_PORT: RTSP server port
    # * G1, G2, ...: regular expression groups, if path name is
    #   a regular expression.
    runOnReady:
    # Restart the command if it exits.
    runOnReadyRestart: no
    # Command to run when a clients starts reading.
    # Prepend ./ to run an executable in the current folder (example: "./ffmpeg")
    # This is terminated with SIGINT when a client stops reading.
    # The following environment variables are available:
    # * MTX_PATH: path name
    # * RTSP_PORT: RTSP server port
    # * G1, G2, ...: regular expression groups, if path name is
    #   a regular expression.
    runOnRead:
    # Restart the command if it exits.
    runOnReadRestart: no
  # Settings under path "all_others" are applied to all paths that
  # do not match another entry.
  all_others:
ruoyi-admin/src/main/resources/logback.xml
@@ -107,6 +107,19 @@
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!--dhSdk日志输出-->
    <appender name="sdk" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/sdk.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--按天回滚daily-->
            <fileNamePattern>${log.path}/sdk.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--日志最大的历史60天-->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!--minio日志输出-->
    <appender name="minio" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/minio.log</file>
@@ -262,6 +275,10 @@
    <logger name="dhSdk" level="INFO">
        <appender-ref ref="dhSdk"/>
    </logger>
    <!--sdk日志-->
    <logger name="sdk" level="INFO">
        <appender-ref ref="sdk"/>
    </logger>
    <!--mqtt日志-->
    <logger name="mqtt" level="INFO">
        <appender-ref ref="mqtt"/>
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/SdkOperateAspect.java
@@ -29,7 +29,7 @@
 */
@Aspect
@Component
@Slf4j(topic = "hikSdk")
@Slf4j(topic = "sdk")
public class SdkOperateAspect {
    @Resource
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/PushTask.java
@@ -9,11 +9,14 @@
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.device.camera.service.ICameraSdkService;
import com.ruoyi.device.hiksdk.common.GlobalVariable;
import com.ruoyi.device.hiksdk.service.IHikClientService;
import com.ruoyi.utils.websocket.util.WebSocketUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import static com.ruoyi.utils.websocket.util.WebSocketUtils.ONLINE_USER_SESSIONS;
@@ -28,6 +31,12 @@
@Slf4j
public class PushTask {
    @Resource
    RedisCache redisCache;
    @Resource
    ICameraSdkService cameraSdkService;
    @Resource
    IGlobalAlarmService globalAlarmService;
    /**
     * @描述 å®šæ—¶æŽ¨é€æ‰€æœ‰æŠ¥è­¦çš„点位数量
     * @参数 []
@@ -38,7 +47,6 @@
     */
    public void globalAlarmCountPush() {
        try {
            IGlobalAlarmService globalAlarmService = SpringUtils.getBean(IGlobalAlarmService.class);
            Map<String, Object> stringIntegerMap = globalAlarmService.selectAlarmLogsCount();
            if (ONLINE_USER_SESSIONS.size() > 0) {
                WebSocketUtils.sendMessageAll(stringIntegerMap);
@@ -59,8 +67,6 @@
     */
    public void ptzPush() {
        try {
            RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
            IHikClientService hikClientService = SpringUtils.getBean(IHikClientService.class);
            List<Map<String, Object>> list = new ArrayList<>();
            List<Object> Objects = redisCache.getListKey(CacheConstants.CAMERA_LIST_KEY);
            if (Objects.size() > 0) {
@@ -70,7 +76,6 @@
                    {
                        continue;
                    }
                    //推送大光电
                    if(!"1".equals(camera.getGdtype()))
                    {
                        continue;
@@ -80,12 +85,12 @@
                    cmd.setChanNo(1);
                    cmd.setOperator(camera.getOperatorId());
                    //推送在线的相机
                    boolean onLine = hikClientService.isOnLine(cmd);
                    boolean onLine = cameraSdkService.isOnLine(cmd);
                    if(!onLine)
                    {
                        continue;
                    }
                    Map<String, Object> ptz = hikClientService.getGisInfo(cmd);
                    Map<String, Object> ptz = cameraSdkService.getGisInfo(cmd);
                    if (StringUtils.isNull(ptz)) {
                        continue;
                    }