czt
19 小时以前 bc3e9b68c66fdeeb7c49155ff46ed68d3650cc18
安防视频调整
已删除1个文件
已修改10个文件
已添加2个文件
1419 ■■■■ 文件已修改
fzzy-igdss-core/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-core/src/main/java/com/fzzy/igds/camera/impl/ApiPtzOnvifService.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-core/src/main/java/com/fzzy/igds/camera/impl/ApiWebrtcPlayTypeService.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-core/src/main/java/com/fzzy/igds/constant/PlayType.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-core/src/main/java/com/fzzy/igds/service/DicService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-core/src/main/java/com/fzzy/igds/service/SecCameraService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/java/com/fzzy/Application.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/java/com/fzzy/sys/controller/security/SecurityController.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/java/com/fzzy/sys/manager/security/SecManager.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/resources/static/security/video-control.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/resources/static/security/video-list-dept.js 939 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/resources/static/security/video-list.css 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-web/src/main/resources/templates/security/video-list-dept.html 160 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fzzy-igdss-core/pom.xml
@@ -54,6 +54,13 @@
            <version>1.7</version>
        </dependency>
        <!--onvif协议实现-->
        <dependency>
            <groupId>com.ld.onvif</groupId>
            <artifactId>ld-onvif</artifactId>
            <version>1.0.0-RELEASE</version>
        </dependency>
    </dependencies>
fzzy-igdss-core/src/main/java/com/fzzy/igds/camera/impl/ApiPtzOnvifService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package com.fzzy.igds.camera.impl;
import com.fzzy.igds.camera.AbstractApiCameraService;
import com.fzzy.igds.camera.data.ApiCameraData;
import com.fzzy.igds.camera.data.ApiCameraResp;
import com.fzzy.igds.constant.CameraPtzType;
import com.ld.onvif.OnvifService;
import com.ld.onvif.data.OnvifResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * ONVIF播放接口实现,只负责实现PTZ接口
 */
@Component
public class ApiPtzOnvifService extends AbstractApiCameraService {
    @Resource
    private OnvifService onvifService;
    @Override
    public String getType() {
        return CameraPtzType.PTZ_TYPE_FZZY_ONVIF.getCode();
    }
    @Override
    public ApiCameraResp ptzMedia(ApiCameraData apiData) {
        if (StringUtils.isEmpty(apiData.getIp())
                || null == apiData.getWebPort()
                || StringUtils.isEmpty(apiData.getLoginId())
                || StringUtils.isEmpty(apiData.getPwd())) {
            return new ApiCameraResp(ApiCameraResp.CODE_ERROR, "参数不全,不支持云台控制");
        }
        try {
            OnvifResult result = onvifService.ptz(apiData.getIp(),
                    apiData.getWebPort(),
                    apiData.getLoginId(),
                    apiData.getPwd(),
                    apiData.getCommand(), 0.5);
            String code = result.get("code") + "";
            if ("0".equals(code) || "200".equals(code)) {
                code = ApiCameraResp.CODE_SUCCESS;
            }else {
                code = ApiCameraResp.CODE_ERROR;
            }
            return new ApiCameraResp(code, (String) result.get("msg"));
        } catch (Exception e) {
            return new ApiCameraResp(ApiCameraResp.CODE_ERROR, "后端执行异常" + e.getMessage());
        }
    }
    @Override
    public ApiCameraResp ptzPreset(ApiCameraData apiData) {
        try {
            if (StringUtils.isEmpty(apiData.getIp()) || null == apiData.getWebPort()
                    || StringUtils.isEmpty(apiData.getLoginId())
                    || StringUtils.isEmpty(apiData.getPwd())) {
                return new ApiCameraResp(ApiCameraResp.CODE_ERROR, "没有获取到当前摄像机信息,不支持云台控制");
            }
            OnvifResult result = onvifService.preset(apiData.getIp(), apiData.getWebPort(),
                    apiData.getLoginId(), apiData.getPwd(), apiData.getPreset());
            String code = result.get("code") + "";
            if ("0".equals(code) || "200".equals(code)) {
                code = ApiCameraResp.CODE_SUCCESS;
            }else {
                code = ApiCameraResp.CODE_ERROR;
            }
            return new ApiCameraResp(code, (String) result.get("msg"));
        } catch (Exception e) {
            return new ApiCameraResp(ApiCameraResp.CODE_ERROR, "后端执行异常" + e.getMessage());
        }
    }
}
fzzy-igdss-core/src/main/java/com/fzzy/igds/camera/impl/ApiWebrtcPlayTypeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package com.fzzy.igds.camera.impl;
import com.fzzy.igds.camera.AbstractApiCameraService;
import com.fzzy.igds.camera.data.ApiCameraData;
import com.fzzy.igds.camera.data.ApiCameraResp;
import com.fzzy.igds.constant.CameraPlayType;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * @Desc WEB-RTC
 * @Author CZT
 * @Date 2024/12/12 09:38
 */
@Slf4j
@Component
public class ApiWebrtcPlayTypeService extends AbstractApiCameraService {
    @Override
    public String getType() {
        return CameraPlayType.PLAY_TYPE_WEB_RTC.getCode();
    }
    @Override
    public ApiCameraResp getPlayAddr(ApiCameraData apiCameraDto) {
        ApiCameraResp apiCameraResp = null;
        //外网播放地址
        if (apiCameraDto.isExtNetwork()) {
            if (StringUtils.isEmpty(apiCameraDto.getUrlOut())) {
                return new ApiCameraResp(ApiCameraResp.CODE_ERROR, "摄像头没有配置外网播放地址");
            }
            apiCameraResp = new ApiCameraResp(apiCameraDto.getUrlOut());
        } else {
            if (StringUtils.isEmpty(apiCameraDto.getUrlIn())) {
                return new ApiCameraResp(ApiCameraResp.CODE_ERROR, "摄像头没有配置内网播放地址");
            }
            apiCameraResp = new ApiCameraResp(apiCameraDto.getUrlIn());
        }
        return apiCameraResp;
    }
}
fzzy-igdss-core/src/main/java/com/fzzy/igds/constant/PlayType.java
ÎļþÒÑɾ³ý
fzzy-igdss-core/src/main/java/com/fzzy/igds/service/DicService.java
@@ -176,7 +176,7 @@
     */
    public List<SysDictData> triggerPlayType() {
        List<SysDictData> list = new ArrayList<SysDictData>();
        for (PlayType w : PlayType.values()) {
        for (CameraPlayType w : CameraPlayType.values()) {
            list.add(new SysDictData(w.getName(), w.getCode()));
        }
        return list;
fzzy-igdss-core/src/main/java/com/fzzy/igds/service/SecCameraService.java
@@ -86,14 +86,16 @@
        if (data.getChanNum() == 0) {
            data.setChanNum(1);
        }
        data.setUpdateBy(ContextUtil.getLoginUserName());
        data.setUpdateTime(new Date());
        if(StringUtils.isEmpty(data.getId())){
            data.setId(ContextUtil.generateId());
            data.setCreateBy(ContextUtil.getLoginUserName());
            data.setCreateTime(new Date());
            cameraMapper.insert(data);
        }else {
            cameraMapper.updateById(data);
        }
        data.setUpdateBy(ContextUtil.getLoginUserName());
        data.setUpdateTime(new Date());
        cameraMapper.insert(data);
    }
    /**
fzzy-igdss-web/src/main/java/com/fzzy/Application.java
@@ -24,7 +24,7 @@
 */
@EnableRedisHttpSession
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ComponentScan(basePackages = {"com.fzzy", "com.ruoyi"})
@ComponentScan(basePackages = {"com.fzzy", "com.ruoyi", "com.ld"})
@MapperScan("com.fzzy.igds.mapper")
@EntityScan(basePackages = {"com.fzzy.*.*.domain"})
fzzy-igdss-web/src/main/java/com/fzzy/sys/controller/security/SecurityController.java
@@ -1,5 +1,7 @@
package com.fzzy.sys.controller.security;
import com.fzzy.igds.camera.data.ApiCameraData;
import com.fzzy.igds.camera.data.ApiCameraResp;
import com.fzzy.igds.constant.CameraPlayType;
import com.fzzy.igds.constant.Constant;
import com.fzzy.igds.data.PageResponse;
@@ -7,6 +9,7 @@
import com.fzzy.igds.domain.Dept;
import com.fzzy.igds.service.CoreDeptService;
import com.fzzy.igds.utils.ContextUtil;
import com.fzzy.igds.utils.SystemUtil;
import com.fzzy.sys.manager.security.SecManager;
import com.ruoyi.common.core.domain.entity.SysUser;
import lombok.extern.slf4j.Slf4j;
@@ -18,6 +21,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
@@ -159,6 +163,47 @@
        return viewUrl;
    }
    /**
     * èŽ·å–è§†é¢‘æ’­æ”¾åœ°å€
     *
     * @param param
     * @return
     */
    @RequestMapping("/get-media")
    @ResponseBody
    public ApiCameraResp getMedia(HttpServletRequest request, @RequestBody Camera param) {
        //获取请求方IP
        String ip = SystemUtil.getIP(request);
        return secManager.getPlayAddr(param, ip);
    }
    /**
     * è§†é¢‘云台控制,所有类型的云台控制入口
     *
     * @param param å‰ç«¯è½¬æ¢çš„参数
     * @return æ‰§è¡Œç»“æžœ
     */
    @RequestMapping("/ptz-media")
    @ResponseBody
    public ApiCameraResp ptzMedia(@RequestBody ApiCameraData param) {
        //log.debug("-----云台调用-----{}",param);
        return secManager.ptzMedia(param);
    }
    /**
     * è§†é¢‘云台预置位设置
     *
     * @param param
     * @return
     */
    @RequestMapping("/ptz-preset")
    @ResponseBody
    public ApiCameraResp ptzPreset(@RequestBody ApiCameraData param) {
        return secManager.ptzPreset(param);
    }
    /***
     * è§†é¢‘鸟瞰图中更改摄像头位置
     *
fzzy-igdss-web/src/main/java/com/fzzy/sys/manager/security/SecManager.java
@@ -1,5 +1,8 @@
package com.fzzy.sys.manager.security;
import com.fzzy.igds.camera.ApiCameraManager;
import com.fzzy.igds.camera.data.ApiCameraData;
import com.fzzy.igds.camera.data.ApiCameraResp;
import com.fzzy.igds.constant.RespCodeEnum;
import com.fzzy.igds.data.PageResponse;
import com.fzzy.igds.domain.Camera;
@@ -23,6 +26,8 @@
    @Resource
    private SecCameraService secCameraService;
    @Resource
    private ApiCameraManager apiCameraManager;
    /**
     *
@@ -61,6 +66,70 @@
    }
    /**
     * èŽ·å–æ’­æ”¾ä¿¡æ¯
     * @param param
     * @return
     */
    public ApiCameraResp getPlayAddr(Camera param, String requireIp) {
        if (StringUtils.isEmpty(param.getId())) {
            log.error("没有获取到监控信息!");
            return new ApiCameraResp("ERROR", "没有获取到监控信息!");
        }
        // æ ¹æ®id获取设备信息
        Camera camera = secCameraService.getCameraById(ContextUtil.getCompanyId(), param.getId());
        if(null == camera){
            log.error("缓存中没有获取到摄像头信息"+ param.toString());
            return new ApiCameraResp("ERROR", "缓存中没有获取到摄像头信息!");
        }
        //通过统一入口获取播放地址
        ApiCameraData apiCameraData = new ApiCameraData();
        apiCameraData.setCompanyId(camera.getCompanyId());
        apiCameraData.setDeptId(camera.getDeptId());
        apiCameraData.setCameraId(camera.getId());
        apiCameraData.setIp(camera.getIp());
        apiCameraData.setCtrlPort(camera.getControlPort());
        apiCameraData.setWebPort(camera.getWebPort());
        apiCameraData.setPlayType(camera.getPlayType());
        apiCameraData.setSnapType(camera.getSnapType());
        apiCameraData.setSn(camera.getSn());
        apiCameraData.setChannel(camera.getChanNum());
        apiCameraData.setLoginId(camera.getLoginId());
        apiCameraData.setPwd(camera.getPwd());
        apiCameraData.setUrlIn(camera.getUrlIn());
        apiCameraData.setUrlOut(camera.getUrlOut());
        apiCameraData.setMediaAddr(camera.getMediaAddr());
        //根据播放方式获取对应的播放地址
        ApiCameraResp resp = apiCameraManager.getApiCameraService(camera.getPlayType()).getPlayAddr(apiCameraData);
        return resp;
    }
    /**
     * äº‘台控制
     * @param param
     * @return
     */
    public ApiCameraResp ptzMedia(ApiCameraData param) {
        if(StringUtils.isEmpty(param.getCompanyId())){
            param.setCompanyId(ContextUtil.getCompanyId());
        }
        return apiCameraManager.getApiCameraService(param.getPtzType()).ptzMedia(param);
    }
    /**
     * é¢„置位设置
     * @param param
     * @return
     */
    public ApiCameraResp ptzPreset(ApiCameraData param) {
        return apiCameraManager.getApiCameraService(param.getPlayType()).ptzPreset(param);
    }
    /**
     *
     * @param params
     * @return
fzzy-igdss-web/src/main/resources/static/security/video-control.js
@@ -32,7 +32,7 @@
function ptzControl(command) {
   // layer.msg('开始调用云台……', {icon: 1, time: 1200,offset:'rb'});
    const param = getParam(command, null);
    const url = "../../basic/security/ptz-media";
    const url = "/security/ptz-media";
    sendControlCommand(url, param);
}
@@ -41,7 +41,7 @@
    //layer.msg('开始执行……', {icon: 1, time: 1200,offset:'rb'});
    const preset = $("#preset").val();
    const param = getParam(command, preset);
    const url = "../../basic/security/ptz-media";
    const url = "/security/ptz-media";
    sendControlCommand(url, param);
}
@@ -49,7 +49,7 @@
function moveStop() {
    //layer.msg('开始调用云台……', {icon: 1, time: 1200,offset:'rb'});
    const param = getParam(0, null);
    const url = "../../basic/security/ptz-media";
    const url = "/security/ptz-media";
    sendControlCommand(url, param);
}
fzzy-igdss-web/src/main/resources/static/security/video-list-dept.js
@@ -1,99 +1,108 @@
var layer;
var splitWin = 1;  //分屏数,默认1分屏
var windowsNum = 1; //播放窗口下标,手动选择模式下使用
var windowsNum = null; //播放窗口下标,手动选择模式下使用
var timer;
var table;
var cameraData;
var playUrl= null;
var curCamera = null;
var playCamera = null;
var playList = {};
var playUrl1 = null;
var videoId1 = null;
let webrtc1;
let mediaStream1;
var playUrl2 = null;
var videoId2 = null;
let webrtc2;
let mediaStream2;
var playUrl3 = null;
var videoId3 = null;
let webrtc3;
let mediaStream3;
var playUrl4 = null;
var videoId4 = null;
let webrtc4;
let mediaStream4;
var playUrl5 = null;
var videoId5 = null;
let webrtc5;
let mediaStream5;
var playUrl6 = null;
var videoId6 = null;
let webrtc6;
let mediaStream6;
var playUrl7 = null;
var videoId7 = null;
let webrtc7;
let mediaStream7;
var playUrl8 = null;
var videoId8 = null;
let webrtc8;
let mediaStream8;
var playUrl9 = null;
var videoId9 = null;
let webrtc9;
let mediaStream9;
$(function () {
    layui.use(['layer', 'table'], function () {
        layer = layui.layer;
        table = layui.table;
    });
    showPtz = function(){
        $("#ptz-block").css("opacity",1);
    };
    disPtz = function(){
        $("#ptz-block").css("opacity",0);
    };
    //初始化渲染播放列表
    renderList();
});
/**
 * æ’­æ”¾çª—口选中
 * @param win1 åˆ†å±æ•°
 * @param win2 é€‰ä¸­çª—口数
 * æ¸²æŸ“监控列表
 */
function selectWin(win1,win2) {
    removeSelectCss();
    splitWin = win1;
    windowsNum = win2;
    addSelectCss();
}
/**
 * å޻除选䏭CSS
 */
function removeSelectCss() {
    $("#f" + splitWin + "_d" + windowsNum).removeClass("selectWin");
}
/**
 * å¢žåР选䏭CSS
 */
function addSelectCss() {
    $("#f" + splitWin + "_d" + windowsNum).addClass("selectWin");
}
/**
 * åˆ†å±åˆ‡æ¢
 * @param tagNum  åˆ†å±æ•°
 */
function fenping(tagNum) {
    //切换分屏图标及页面
    if (tagNum == 1) {
        $("#f_1").attr("src", "/img/web/group/fp_1_active.png");
        $("#f_4").attr("src", "/img/web/group/fp_4.png");
        $("#f_9").attr("src", "/img/web/group/fp_9.png");
        $("#video_1").css('display', 'block');
        $("#video_4").css('display', 'none');
        $("#video_9").css('display', 'none');
function renderList() {
    if (!listCamera || listCamera.length == 0) {
        return;
    }
    if (tagNum == 4) {
        $("#f_1").attr("src", "/img/web/group/fp_1.png");
        $("#f_4").attr("src", "/img/web/group/fp_4_active.png");
        $("#f_9").attr("src", "/img/web/group/fp_9.png");
        $("#video_1").css('display', 'none');
        $("#video_4").css('display', 'block');
        $("#video_9").css('display', 'none');
    }
    if (tagNum == 9) {
        $("#f_1").attr("src", "/img/web/group/fp_1.png");
        $("#f_4").attr("src", "/img/web/group/fp_4.png");
        $("#f_9").attr("src", "/img/web/group/fp_9_active.png");
        $("#video_1").css('display', 'none');
        $("#video_4").css('display', 'none');
        $("#video_9").css('display', 'block');
    }
    var eleTable = document.getElementById("cameraList");
    var _tr;
    var _td;
    $.each(listCamera, function (index, item) {
        _tr = document.createElement("tr");
        _tr.ondblclick = function () {
            getMedia(index);
        };
        _td = document.createElement("td");
        _td.innerText = item.name;
        _tr.appendChild(_td);
        _td = document.createElement("td");
        _td.innerText = item.type == "01" ? "枪机" : "球机";
        _tr.appendChild(_td);
        _td = document.createElement("td");
        _td.innerText = item.status == "OFF" ? "离线" : "在线";
        _tr.appendChild(_td);
        eleTable.appendChild(_tr);
    })
}
/**
 * ç‚¹å‡»æ’­æ”¾
 * @param cameraId
 * ç‚¹å‡»èŽ·å–æ’­æ”¾ä¿¡æ¯
 * @param index
 */
play = function (data) {
    layer.msg("正在获取播放信息……");
    cameraData = null;
    playUrl = null;
function getMedia(index) {
    if(windowsNum == null){
        windowsNum = 1;
    }
    playCamera = null;
    var camera = listCamera[index];
    playList[windowsNum] = camera;
    var data = {
        id: data.id,
        playType: data.playType
        id: camera.id,
        playType: camera.playType
    };
    $.ajax({
        type: 'POST',
@@ -105,144 +114,226 @@
            if (result.code != "SUCCESS") {
                layer.msg(result.msg);
            } else {
                cameraData = result;
                playUrl = result.playUrl;
                play2();
                playCamera = result;
                startPlay();
            }
        },
        error: function (result) {
            play2();
            layer.msg(result.msg);
            startPlay();
        }
    });
};
play2 = function () {
    if (!cameraData) {
        layer.alert("未获取到当前摄像头播放信息!!");
        return;
/**
 * æ’­æ”¾çª—口选中
 * @param win1 åˆ†å±æ•°
 * @param win2 é€‰ä¸­çª—口数
 */
function selectWin(win1, win2) {
    if (windowsNum) {
        removeSelectCss();
    }
    splitWin = win1;
    windowsNum = win2;
    addSelectCss();
    if(PlayType.VLC == cameraData.playType){ //说明使用本地VLC播放
        vlcToPlay();
    }
    if (PlayType.PLAY_TYPE_WEB_RTC_DH == cameraData.playType
    || PlayType.PLAY_TYPE_WEB_RTC_HIK == cameraData.playType) {//使用web-rtc播放
        webRtcToPlay();
    }
};
function renderList() {
    if (!listCamera || listCamera.length == 0) {
        return;
    }
    var eleTable = document.getElementById("cameraList");
    var _tr;
    var _td;
    listCamera.forEach(function (data) {
        _tr = document.createElement("tr");
        _tr.ondblclick = function () {
            play(data);
        };
        _td = document.createElement("td");
        _td.innerText = data.name;
        _tr.appendChild(_td);
        _td = document.createElement("td");
        _td.innerText = data.type == "01" ? "枪机" : "球机";
        _tr.appendChild(_td);
        _td = document.createElement("td");
        _td.innerText = data.status == "OFF" ? "离线" : "在线";
        _tr.appendChild(_td);
        eleTable.appendChild(_tr);
    });
    curCamera = playList[windowsNum];
}
/**
 * æ’­æ”¾è¿˜æ˜¯æš‚停
 * å޻除选䏭CSS
 */
function playStop() {
    var videL = $('#easyPlayer');
    if (videL.paused) {
        videL.play();
    } else {
        videL.pause();
function removeSelectCss() {
    const target = document.getElementById("f" + splitWin + "_d" + windowsNum);
    target.classList.remove('active');
}
/**
 * å¢žåР选䏭CSS
 */
function addSelectCss() {
    const target = document.getElementById("f" + splitWin + "_d" + windowsNum);
    target.classList.add('active');
}
/**
 * åˆ†å±åˆ‡æ¢
 * @param tagNum  åˆ†å±æ•°
 */
function fenping(tagNum) {
    if (windowsNum) {
        removeSelectCss();
    }
    playList = {};
    //重置切换前的分屏窗口
    reloadView(splitWin);
    //赋值当前分屏数
    splitWin = tagNum;
    //重置切换后的分屏窗口
    reloadView(splitWin);
    //切换分屏图标及页面
    if (tagNum === 1) {
        $("#f_1").attr("src", "/img/web/group/fp_1_active.png");
        $("#f_4").attr("src", "/img/web/group/fp_4.png");
        $("#f_9").attr("src", "/img/web/group/fp_9.png");
        $("#video_1").css('display', 'grid');
        $("#video_4").css('display', 'none');
        $("#video_9").css('display', 'none');
    }
    if (tagNum === 4) {
        $("#f_1").attr("src", "/img/web/group/fp_1.png");
        $("#f_4").attr("src", "/img/web/group/fp_4_active.png");
        $("#f_9").attr("src", "/img/web/group/fp_9.png");
        $("#video_1").css('display', 'none');
        $("#video_4").css('display', 'grid');
        $("#video_9").css('display', 'none');
    }
    if (tagNum === 9) {
        $("#f_1").attr("src", "/img/web/group/fp_1.png");
        $("#f_4").attr("src", "/img/web/group/fp_4.png");
        $("#f_9").attr("src", "/img/web/group/fp_9_active.png");
        $("#video_1").css('display', 'none');
        $("#video_4").css('display', 'none');
        $("#video_9").css('display', 'grid');
    }
    windowsNum = null;
}
/**
 * é‡ç½®åˆ†å±å‰çš„窗口
 * @param num
 */
function reloadView(num) {
    var html;
    for (var i = 1; i <= num; i++) {
        html = '';
        html += '<video className="video" id="video' + num + '_' + i + ' autoPlay="" muted="" playsInline=""></video>';
        $("#f" + num + "_d" + i).html(html);
    }
}
/*============= vlc视频播放 ----- å¼€å§‹ ===============*/
function vlcToPlay() {
    $("#video").css('display','none');
    $("#vlcPlayer").css('display','block');
    var html = '';
    html += '<object type="application/x-vlc-plugin"' +
        'events="true" width="100%" height="100%"' +
        'pluginspage="http://www.videolan.org"' +
        'th:codebase="@{../../static/plugins/vlc/npapi-vlc-2.2.2.tar.xz}">' +
        '<param name="mrl" value="' + playUrl + '"/>'+
        '<param name="volume" value="50"/>' +
        '<param name="autoplay" value="true"/>' +
        '<param name="loop" value="false"/>' +
        '<param name="fullscreen" value="true"/>' +
        '<param name="toolbar" value="false"/>' +
        '</object>';
    $("#vlcPlayer").html(html);
function startPlay() {
    if(windowsNum > splitWin){
        windowsNum = 1;
    }
    if (!playCamera) {
        $("#f" + splitWin + "_d" + windowsNum).html("未获取到摄像头播放信息!!");
    }
    if (playCamera.playUrl) {
        if (windowsNum === 1) {
            playUrl1 = null;
            mediaStream1 = null;
            playUrl1 = playCamera.playUrl;
            videoId1 = playCamera.cameraId;
            webRtcToPlay1();
        }else if (windowsNum === 2) {
            playUrl2 = null;
            mediaStream2 = null;
            playUrl2 = playCamera.playUrl;
            videoId2 = playCamera.cameraId;
            webRtcToPlay2();
        }else if (windowsNum === 3) {
            playUrl3 = null;
            mediaStream3 = null;
            playUrl3 = playCamera.playUrl;
            videoId3 = playCamera.cameraId;
            webRtcToPlay3();
        }else if (windowsNum === 4) {
            playUrl4 = null;
            mediaStream4 = null;
            playUrl4 = playCamera.playUrl;
            videoId4 = playCamera.cameraId;
            webRtcToPlay4();
        }else if (windowsNum === 5) {
            playUrl5 = null;
            mediaStream5 = null;
            playUrl5 = playCamera.playUrl;
            videoId5 = playCamera.cameraId;
            webRtcToPlay5();
        }else if (windowsNum === 6) {
            playUrl6 = null;
            mediaStream6 = null;
            playUrl6 = playCamera.playUrl;
            videoId6 = playCamera.cameraId;
            webRtcToPlay6();
        }else if (windowsNum === 7) {
            playUrl7 = null;
            mediaStream7 = null;
            playUrl7 = playCamera.playUrl;
            videoId7 = playCamera.cameraId;
            webRtcToPlay7();
        }else if (windowsNum === 8) {
            playUrl8 = null;
            mediaStream8 = null;
            playUrl8 = playCamera.playUrl;
            videoId8 = playCamera.cameraId;
            webRtcToPlay8();
        }else if (windowsNum === 9) {
            playUr9 = null;
            mediaStream9 = null;
            playUrl9 = playCamera.playUrl;
            videoId9 = playCamera.cameraId;
            webRtcToPlay9();
        }
        curCamera = playList[windowsNum];
    } else {
        $("#f" + splitWin + "_d" + windowsNum).html("未获取到摄像头(" + playCamera.cameraName + ")播放信息!!");
    }
}
/*============= webRtc视频播放 ----- å¼€å§‹ ===============*/
/*============= webRtc视频播放 ----- å¼€å§‹ ===============*/
/*============= webRtc视频播放1 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay() {
    layer.msg("开始启动播放……");
    $("#vlcPlayer").css('display','none');
    $("#video").css('display','block');
    if(playUrl){
        mediaStream = new MediaStream();
        $("#video")[0].srcObject = mediaStream;
        webrtc = new RTCPeerConnection({
async function webRtcToPlay1() {
    if (playUrl1) {
        mediaStream1 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream1;
        webrtc1 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc.onsignalingstatechange = signalingstatechange;
        webrtc1.onsignalingstatechange = signalingstatechange1;
        webrtc.ontrack = ontrack
        let offer = await webrtc.createOffer({
        webrtc1.ontrack = ontrack1
        let offer = await webrtc1.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc.setLocalDescription(offer);
        await webrtc1.setLocalDescription(offer);
    }
}
function ontrack(event) {
    mediaStream.addTrack(event.track);
function ontrack1(event) {
    mediaStream1.addTrack(event.track);
}
async function signalingstatechange() {
    switch (webrtc.signalingState) {
async function signalingstatechange1() {
    switch (webrtc1.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl + "?uuid=" + cameraData.cameraId + "&channel=0";
            let url = playUrl1 + "?uuid=" + videoId1 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc.localDescription.sdp)
                data: btoa(webrtc1.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc.setRemoteDescription(new RTCSessionDescription({
                    webrtc1.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
@@ -257,9 +348,519 @@
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc.signalingState}`);
            console.log(`unhandled signalingState is ${webrtc1.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放2 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay2() {
    if (playUrl2) {
        mediaStream2 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream2;
        webrtc2 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc2.onsignalingstatechange = signalingstatechange2;
        webrtc2.ontrack = ontrack2
        let offer = await webrtc2.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc2.setLocalDescription(offer);
    }
}
function ontrack2(event) {
    mediaStream2.addTrack(event.track);
}
async function signalingstatechange2() {
    switch (webrtc2.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl2 + "?uuid=" + videoId2 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc2.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc2.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc2.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放3 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay3() {
    if (playUrl3) {
        mediaStream3 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream3;
        webrtc3 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc3.onsignalingstatechange = signalingstatechange3;
        webrtc3.ontrack = ontrack3
        let offer = await webrtc3.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc3.setLocalDescription(offer);
    }
}
function ontrack3(event) {
    mediaStream3.addTrack(event.track);
}
async function signalingstatechange3() {
    switch (webrtc3.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl3 + "?uuid=" + videoId3 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc3.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc3.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc3.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放4 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay4() {
    if (playUrl4) {
        mediaStream4 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream4;
        webrtc4 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc4.onsignalingstatechange = signalingstatechange4;
        webrtc4.ontrack = ontrack4
        let offer = await webrtc4.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc4.setLocalDescription(offer);
    }
}
function ontrack4(event) {
    mediaStream4.addTrack(event.track);
}
async function signalingstatechange4() {
    switch (webrtc4.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl4 + "?uuid=" + videoId4 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc4.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc4.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc4.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放5 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay5() {
    if (playUrl5) {
        mediaStream5 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream5;
        webrtc5 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc5.onsignalingstatechange = signalingstatechange5;
        webrtc5.ontrack = ontrack5
        let offer = await webrtc4.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc5.setLocalDescription(offer);
    }
}
function ontrack5(event) {
    mediaStream5.addTrack(event.track);
}
async function signalingstatechange5() {
    switch (webrtc5.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl5 + "?uuid=" + videoId5 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc5.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc5.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc5.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放4 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay6() {
    if (playUrl6) {
        mediaStream6 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream6;
        webrtc6 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc6.onsignalingstatechange = signalingstatechange6;
        webrtc6.ontrack = ontrack6
        let offer = await webrtc4.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc6.setLocalDescription(offer);
    }
}
function ontrack6(event) {
    mediaStream6.addTrack(event.track);
}
async function signalingstatechange6() {
    switch (webrtc6.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl6 + "?uuid=" + videoId6 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc6.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc6.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc6.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放7 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay7() {
    if (playUrl7) {
        mediaStream7 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream7;
        webrtc7 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc7.onsignalingstatechange = signalingstatechange7;
        webrtc7.ontrack = ontrack7
        let offer = await webrtc7.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc7.setLocalDescription(offer);
    }
}
function ontrack7(event) {
    mediaStream7.addTrack(event.track);
}
async function signalingstatechange7() {
    switch (webrtc7.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl7 + "?uuid=" + videoId7 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc7.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc7.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc7.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放8 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay8() {
    if (playUrl8) {
        mediaStream8 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream8;
        webrtc8 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrt8.onsignalingstatechange = signalingstatechange8;
        webrtc8.ontrack = ontrack8
        let offer = await webrtc8.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc8.setLocalDescription(offer);
    }
}
function ontrack8(event) {
    mediaStream8.addTrack(event.track);
}
async function signalingstatechange8() {
    switch (webrtc8.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl8 + "?uuid=" + videoId8 + "&channel=0";
            $.post(url, {
                data: btoa(webrtc8.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc8.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc8.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
/*============= webRtc视频播放9 ----- å¼€å§‹ ===============*/
/**
 * å¼€å§‹æ’­æ”¾
 * @param winTag  æ’­æ”¾çª—口
 * @returns {Promise<void>}
 */
async function webRtcToPlay9() {
    if (playUrl9) {
        mediaStream9 = new MediaStream();
        $("#video" + splitWin + "_" + windowsNum)[0].srcObject = mediaStream9;
        webrtc9 = new RTCPeerConnection({
            iceServers: [{
                urls: ["stun:stun.l.google.com:19302"]
            }],
            sdpSemantics: "unified-plan"
        });
        webrtc9.onsignalingstatechange = signalingstatechange9;
        webrtc9.ontrack = ontrack9
        let offer = await webrtc4.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        });
        await webrtc9.setLocalDescription(offer);
    }
}
function ontrack9(event) {
    mediaStream9.addTrack(event.track);
}
async function signalingstatechange9() {
    switch (webrtc9.signalingState) {
        case 'have-local-offer':
            // let uuid = $('#uuid').val();
            let url = playUrl9 + "?uuid=" + videoId9 + "&channel=0";
            $.post(url, {
                data: btoa(webrt9.localDescription.sdp)
            }, function (data) {
                try {
                    console.log(data);
                    webrtc9.setRemoteDescription(new RTCSessionDescription({
                        type: 'answer',
                        sdp: atob(data)
                    }))
                } catch (e) {
                    console.warn(e);
                }
            });
            break;
        case 'stable':
            break;
        case 'closed':
            break;
        default:
            console.log(`unhandled signalingState is ${webrtc9.signalingState}`);
            break;
    }
}
/*============= è§†é¢‘播放 ----- ç»“束 ===============*/
fzzy-igdss-web/src/main/resources/static/security/video-list.css
@@ -354,7 +354,7 @@
.sp-sxBtn{
    /*float: left;*/
    float: left;
}
.sp-fdBtn{
fzzy-igdss-web/src/main/resources/templates/security/video-list-dept.html
@@ -16,6 +16,12 @@
            height: 100%;
            overflow-y: hidden;
        }
        /* é¡µé¢åŸºç¡€é‡ç½® */
        body {
            margin: 0;
            padding: 0;
            background-color: #000;
        }
        .layui-fluid {
            position: relative;
@@ -91,59 +97,127 @@
            color: #fff;
            cursor: pointer;
        }
        .sp-table {
            overflow: auto;
        }
        .sp-table::-webkit-scrollbar {
            display: none; /* éšè—æ»šåŠ¨æ¡ */
        }
        .fenping_icon {
            position: absolute;
            right: 30px;
            top: 16px;
        }
        .div1 {
            width: 100%;
            height: 100%;
            display: grid;
            grid-template-columns: 1fr;
            grid-template-rows: 1fr;
            gap: 1px; /* ä¸­é—´1px白色分隔线 */
            background-color: #777; /* gap和padding的颜色=白色分隔线 */
            padding: 1px; /* å‘¨ç•Œ1px白色分隔线 */
            box-sizing: border-box;
            overflow: hidden;
        }
        .div_v1 {
            width: 99.8%;
            height: 760px;
            float: left;
            background-color: #333;
            text-align: center;
            color: #FFF;
            font-size: 20px;
            font-size: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            position: relative; /* ä¸ºçº¢æ¡†ä¼ªå…ƒç´ å®šä½ */
            box-sizing: border-box;
            z-index: 1; /* åŸºç¡€å±‚级 */
        }
        /* é€‰ä¸­é¡¹ï¼šçº¢è‰²è¾¹æ¡†ï¼ˆè¦†ç›–自身周边的白色分隔线) */
        .div_v1.active::after {
            content: '';
            position: absolute;
            /* çº¢æ¡†èŒƒå›´ï¼šè¶…出自身,刚好覆盖周边1px白线 */
            top: -1px;
            left: -1px;
            right: -1px;
            bottom: -1px;
            border: 2px solid red; /* çº¢æ¡†å®½åº¦å¯è°ƒæ•´ */
            z-index: 2; /* é«˜äºŽè‡ªèº«ï¼Œè¦†ç›–白线;低于其他项的基础层级,不遮挡其他白线 */
            box-sizing: border-box;
            transition: all 0.2s ease;
        }
        .div4 {
            width: 100%;
            height: 100%;
            display: grid;
            grid-template-columns: 1fr 1fr;
            grid-template-rows: 1fr 1fr;
            gap: 1px; /* ä¸­é—´1px白色分隔线 */
            background-color: #777; /* gap和padding的颜色=白色分隔线 */
            padding: 1px; /* å‘¨ç•Œ1px白色分隔线 */
            box-sizing: border-box;
            overflow: hidden;
        }
        .div_v4 {
            width: 49.88%;
            height: 379.5px;
            float: left;
            background-color: #333;
            text-align: center;
            color: #FFF;
            font-size: 20px;
            font-size: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            position: relative; /* ä¸ºçº¢æ¡†ä¼ªå…ƒç´ å®šä½ */
            box-sizing: border-box;
            z-index: 1; /* åŸºç¡€å±‚级 */
        }
        /* é€‰ä¸­é¡¹ï¼šçº¢è‰²è¾¹æ¡†ï¼ˆè¦†ç›–自身周边的白色分隔线) */
        .div_v4.active::after {
            content: '';
            position: absolute;
            /* çº¢æ¡†èŒƒå›´ï¼šè¶…出自身,刚好覆盖周边1px白线 */
            top: -1px;
            left: -1px;
            right: -1px;
            bottom: -1px;
            border: 2px solid red; /* çº¢æ¡†å®½åº¦å¯è°ƒæ•´ */
            z-index: 2; /* é«˜äºŽè‡ªèº«ï¼Œè¦†ç›–白线;低于其他项的基础层级,不遮挡其他白线 */
            box-sizing: border-box;
            transition: all 0.2s ease;
        }
        .div9 {
            width: 100%;
            height: 100%;
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            grid-template-rows: 1fr 1fr 1fr;
            gap: 1px; /* ä¸­é—´1px白色分隔线 */
            background-color: #777; /* gap和padding的颜色=白色分隔线 */
            padding: 1px; /* å‘¨ç•Œ1px白色分隔线 */
            box-sizing: border-box;
            overflow: hidden;
        }
        .div_v9 {
            width: 33.22%;
            height: 252.6px;
            float: left;
            background-color: #333;
            text-align: center;
            color: #FFF;
            font-size: 20px;
            font-size: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            position: relative; /* ä¸ºçº¢æ¡†ä¼ªå…ƒç´ å®šä½ */
            box-sizing: border-box;
            z-index: 1; /* åŸºç¡€å±‚级 */
        }
        .bor_t_l {
            border-top: 1px solid #777;
            border-left: 1px solid #777;
        }
        .bor_b {
            border-bottom: 1px solid #777;
        }
        .bor_r {
            border-right: 1px solid #777;
        }
        .selectWin {
            border: 1px solid #a52222;
        /* é€‰ä¸­é¡¹ï¼šçº¢è‰²è¾¹æ¡†ï¼ˆè¦†ç›–自身周边的白色分隔线) */
        .div_v9.active::after {
            content: '';
            position: absolute;
            /* çº¢æ¡†èŒƒå›´ï¼šè¶…出自身,刚好覆盖周边1px白线 */
            top: -1px;
            left: -1px;
            right: -1px;
            bottom: -1px;
            border: 2px solid red; /* çº¢æ¡†å®½åº¦å¯è°ƒæ•´ */
            z-index: 2; /* é«˜äºŽè‡ªèº«ï¼Œè¦†ç›–白线;低于其他项的基础层级,不遮挡其他白线 */
            box-sizing: border-box;
            transition: all 0.2s ease;
        }
        .video {
            width: 100%;
@@ -171,16 +245,16 @@
                            </div>
                        </div>
                        <div class="sp-tab-db" style="padding: 5px 10px 15px 10px;">
                        <div class="sp-tab-db" style="padding: 5px 10px 15px 10px;height: 765px;">
                            <!--一分屏 é»˜è®¤æ˜¾ç¤º-->
                            <div id="video_1" class="right-videoWrap">
                            <div id="video_1" class="div1">
                                <div id="f1_d1" onclick="selectWin(1,1)" class="div_v1 bor_t_l bor_b bor_r">
                                    <video class="video" id="video1_1" autoplay="" muted="" playsinline=""></video>
                                </div>
                            </div>
                            <!--四分屏 é»˜è®¤æ˜¾ç¤º-->
                            <div id="video_4" class="right-videoWrap" style="display: none;">
                            <div id="video_4" class="div4" style="display: none;">
                                <div id="f4_d1" onclick="selectWin(4,1)" class="div_v4 bor_t_l">
                                    <video class="video" id="video4_1" autoplay="" muted="" playsinline=""></video>
                                </div>
@@ -196,7 +270,7 @@
                            </div>
                            <!--九分屏 é»˜è®¤éšè—-->
                            <div id="video_9" class="right-videoWrap" style="display: none;">
                            <div id="video_9" class="div9" style="display: none;">
                                <div id="f9_d1" onclick="selectWin(9,1)" class="div_v9 bor_t_l">
                                    <video class="video" id="video9_1" autoplay="" muted="" playsinline=""></video>
                                </div>