【springboot】对接海康威视视频监控技术文档

springboot项目对接海康威视视频监控接入技术文档

适用场景:综合安防管理平台(iSecure Center / Artemis 网关)对接,并在监控大屏上实时预览监控点画面。

(Spring Boot + Thymeleaf)。


一、总体架构

本项目的海康威视接入分为两条相互独立的链路:

链路 通信方 技术手段 用途
后端链路 后端服务 → 海康 Artemis 网关 artemis-http-client SDK(HTTPS + JSON) 获取预览取流 URL、区域/监控点列表、在线状态、门禁事件等
前端链路 浏览器 → 本地 Web 插件 → 安防平台 海康 WebControl 插件(VideoWebPlugin.exe + jsWebControl) 在网页中真正播放实时/回放画面

大屏预览的完整数据流:

复制代码
浏览器打开大屏
   ├─ 页面脚本调用 videoPreview()  ──► GET /show/video/preview
   │                                        │
   │                                        ▼
   │                              ShowServiceImpl.videoPreview()
   │                                查设备库(typeId=5, screenShow=1)
   │                                        │
   │                                        ▼
   │                              IsecureCenterService.previewURL()
   │                                        │
   │                                        ▼
   │                              POST /artemis/api/video/v1/cameras/previewURLs
   │
   └─ window.load 后初始化插件 initPlugin()
              │
              ▼
        WebControl 启动本地服务(15900-15909) → 创建播放窗口
              │
              ▼
        插件登录平台 init()(appkey/secret/ip)
              │
              ▼
        拿到监控点编号后逐个 startPreview(cameraIndexCode)

注意:前端播放并不依赖 后端返回的 url,插件是拿 cameraIndexCode 自己去平台取流的;后端返回的 url 主要留给需要 RTMP/HLS 网页播放的场景。


二、前置条件

  1. 海康综合安防管理平台已部署并可访问 ,需提供:
    • 平台 IP(本文示例 192.168.x.x)
    • Artemis 网关端口(启用 HTTPS 时默认 443)
    • appKey、appSecret(API 网关凭证)
  2. 平台侧已为 appKey 授权视频相关 API(如预览取流、资源查询等权限)。
  3. 客户端浏览器 需允许安装并运行本地插件,且本地 15900-15909 端口未被占用。
  4. 后端工程已能正常编译(JDK + Maven)。

三、步骤一:引入后端 SDK 依赖

在 dzjk-os/pom.xml 中引入海康 Artemis HTTP 客户端:

xml 复制代码
<!--对接海康威视 sdk需要 start-->
<dependency>
    <groupId>com.sun</groupId>
    <artifactId>jna</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.sun</groupId>
    <artifactId>examples</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.hikvision.ga</groupId>
    <artifactId>artemis-http-client</artifactId>
    <version>1.1.2</version>
</dependency>
<!--对接海康威视 sdk需要 end-->

四、步骤二:添加平台连接配置

在 dzjk-os/src/main/resources/application-dev.yml 中新增海康平台配置(其它环境 application-*.yml 同理):

yaml 复制代码
# 海康威视安防设备平台的相关内容
hikvision:
  artemis:
    host: 192.168.x.x
    appKey: xxx
    appSecret: xxx

host 只填 IP,不要带 https:// 前缀;SDK 内部会拼接 /artemis 前缀与协议。

说明:本文档中所有 appKey、appSecret、平台 IP 均已脱敏,实际部署时替换为真实值。


五、步骤三:封装 Artemis 通用请求

新增服务类 com.cecjx.rotaryring.service.IsecureCenterService,负责注入配置、初始化 ArtemisConfig、封装 POST 请求。

5.1 注入配置并初始化 SDK

java 复制代码
@Component
public class IsecureCenterService {
    private static final Logger logger = LoggerFactory.getLogger(IsecureCenterService.class);

    @Value("${hikvision.artemis.host}")
    private String host;
    @Value("${hikvision.artemis.appKey}")
    private String appkey;
    @Value("${hikvision.artemis.appSecret}")
    private String appSecret;
    // ... 其余依赖注入省略

5.2 通用 POST 方法

要点:接口路径必须拼接 /artemis 前缀;必须使用 doPostStringArtemis()(doPostFormArtemis() 传 JSON body 会有问题)。

java 复制代码
/**
 * 海康威视的 POST 请求
 *
 * @param api         api 接口的地址
 * @param body        form 表单请求的参数,键值对形式的 map;
 * @param querys      map 类型 get 请求的 url 查询参数
 * @param head        接受 head 参数,采用 map 类型
 * @param accept      指定客户端能够接收的内容类型
 * @param contentType 请求的与实体对应的 MIME 信息
 */
public String doPost(String api, JSONObject body, Map querys, String accept, String contentType, Map head) {
    if (ArtemisConfig.host == null || ArtemisConfig.appKey == null || ArtemisConfig.appSecret == null) {
        // artemis 网关服务器 ip 端口
        ArtemisConfig.host = host;
        // 秘钥 appkey
        ArtemisConfig.appKey = appkey;
        // 秘钥 appSecret
        ArtemisConfig.appSecret = appSecret;
    }

    // 接口路径
    final String getSecurityApi = "/artemis" + api;
    Map<String, String> path = new HashMap<String, String>(1) {
        {
            put("https://", getSecurityApi);
        }
    };
    String result = null;
    try {
        /*
         * 使用doPostStringArtemis()方法,不能使用 doPostFormArtemis()方法,
         * 因为后者中body参数使用map存储存在问题
         */
        if (head == null) {
            result = ArtemisHttpUtil.doPostStringArtemis(path, body.toJSONString(), null, null, "application/json");
        } else {
            result = ArtemisHttpUtil.doPostStringArtemis(path, body.toJSONString(), querys, accept, contentType, head);
        }
    } catch (Exception e) {
        logger.error("海康接口访问失败!");
    }
    return result;
}

public String doPost(String api, JSONObject body) {
    return doPost(api, body, null, null, null, null);
}

5.3 返回图片流的 POST(用于抓拍图下载)

java 复制代码
public HttpResponse doPostImg(String api, JSONObject body) {
    if (ArtemisConfig.host == null || ArtemisConfig.appKey == null || ArtemisConfig.appSecret == null) {
        ArtemisConfig.host = host;
        ArtemisConfig.appKey = appkey;
        ArtemisConfig.appSecret = appSecret;
    }

    // 接口路径
    final String getSecurityApi = "/artemis" + api;
    Map<String, String> path = new HashMap<String, String>(1) {
        {
            put("https://", getSecurityApi);
        }
    };
    HttpResponse result = null;
    try {
        result = ArtemisHttpUtil.doPostStringImgArtemis(path, body.toJSONString(), null, null, "application/json", null);
    } catch (Exception e) {
        logger.error("海康接口访问失败!");
    }
    return result;
}

六、步骤四:实现预览取流接口

调用海康「获取监控点预览取流 URL」接口 /api/video/v1/cameras/previewURLs,按协议拼装参数:

java 复制代码
/**
 * 获取监控点预览取流URL
 */
public String previewURL(String cameraIndexCode, String protocol) {
    String api = "/api/video/v1/cameras/previewURLs";
    JSONObject body = new JSONObject();
    body.put("cameraIndexCode", cameraIndexCode);
    //rtsp 只有当 streamType = 1,即获取子流码的时候 vlc 才能播放,若是都不匹配就使用 rtmp 协议
    switch (protocol) {
        case "rtsp":
            body.put("streamType", 0);
            body.put("protocol", "rtsp");
            body.put("transmode", 1);
            body.put("expand", "streamform=rtp");
            break;
        case "rtmp":
            body.put("streamType", 1);
            body.put("protocol", "rtmp");
            body.put("transmode", 1);
            body.put("expand", "streamform=rtp");
            break;
        case "rtmp2":
            // 使用主流码
            body.put("streamType", 0);
            body.put("protocol", "rtmp");
            body.put("transmode", 1);
            body.put("expand", "streamform=rtp");
            break;
        case "hls":
            body.put("streamType", 0);
            body.put("protocol", "hls");
            body.put("transmode", 1);
            body.put("expand", "streamform=ps");
            break;
        default:
            body.put("streamType", 0);
            body.put("protocol", "rtmp");
            body.put("transmode", 1);
            body.put("expand", "streamform=rtp");
    }
    String result = doPost(api, body);
    return result;
}

关键参数说明:

参数 含义 取值
cameraIndexCode 监控点编号 与平台中监控点一一对应
streamType 主子码流 0 主码流,1 子码流
protocol 取流协议 rtsp / rtmp / hls
transmode 传输协议 1 为 TCP(推荐,画面更稳)
expand 扩展参数 RTSP/RTMP 用 streamform=rtp,HLS 用 streamform=ps

七、步骤五:Controller 暴露大屏接口

在 com.cecjx.show.controller.ShowController 中新增接口:

java 复制代码
@ResponseBody
@Log("大屏展示-视频监控点")
@GetMapping("video/preview")
public R videoPreview() {
    return showService.videoPreview();
}

对外访问路径为 /show/video/preview(类上的 @RequestMapping("show") + 方法上的 video/preview)。


八、步骤六:查询大屏要展示的监控点

在 ShowServiceImpl 中查询设备表中的监控点,并逐个调用取流接口:

java 复制代码
@Override
public R videoPreview() {
    try {
        //先查出监控点设备(大屏展示的监控点)
        Map<String, Object> params = new HashMap<>();
        params.put("typeId", "5");        // 5 = 视频监控点类型
        params.put("screenShow", "1");    // 1 = 在大屏展示
        Page<InfoDO> list = infoDao.list(params).setOrderBy("showArea asc");
        List<Map<String, Object>> resultList = new ArrayList<>();
        if (null != list && list.size() > 0) {
            Map<String, Object> item;
            for (int i = 0; i < list.size(); i++) {
                InfoDO info = list.get(i);
                Integer showArea = info.getShowArea();
                item = new HashMap<>();
                item.put("name", info.getName());
                String url = isecureCenterService.previewURL(info.getCode(), "rtmp");
                item.put("url", url);
                item.put("showArea", info.getShowArea());
                item.put("code", info.getCode());
                resultList.add(item);
            }
        }
        return R.ok().put("data", resultList);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return R.error();
}

依赖的设备表 InfoDO 关键字段:

字段 说明
typeId 设备类型,5 表示视频监控点
screenShow 是否大屏展示,1 展示
showArea 大屏展示位置序号,用于排序/指定窗口
code 监控点编号,即海康的 cameraIndexCode
name 监控点名称

code 与海康平台的 cameraIndexCode 需要保持一致。若设备库中的名称/编号与平台不一致,可参考 ShowServiceImpl.updateCamera(),通过「根区域 → 下级区域 → 区域下监控点」逐级拉取监控点并回写 code。


九、步骤七:前端引入 Web 插件 SDK

海康 Web 播放能力依赖本地插件与官方 JS SDK,需在页面中引入并准备播放容器。

9.1 引入脚本

在 dzjk-os/src/main/resources/templates/show/index-in.html 中引入(顺序不能乱):

html 复制代码
<script th:inline="javascript"> var ctx = [[@{/}]] ; ctx=ctx.substring(0,ctx.length-1);</script>
<script type="text/javascript" th:src="@{/js/plugins/layer/layer.js}"></script>
<script type="text/javascript" th:src="@{/js/appjs/hikvision/websdk/jquery-1.12.4.min.js}"></script>
<script type="text/javascript" th:src="@{/js/appjs/hikvision/websdk/jsencrypt.min.js}"></script>
<script type="text/javascript" th:src="@{/js/appjs/hikvision/websdk/jsWebControl-1.0.0.min.js}"></script>

对应静态资源目录:

复制代码
src/main/resources/static/js/appjs/hikvision/websdk/
├── jquery-1.12.4.min.js
├── jsencrypt.min.js              # 用于 RSA 加密 secret
└── jsWebControl-1.0.0.min.js     # 海康 WebControl 插件 SDK

9.2 声明播放容器

插件的播放窗口会挂载到该容器上,id 必须与初始化时的 szPluginContainer 一致:

html 复制代码
<dl class="clear" id="video">
    <dd id="playWnd">
        <div class="sxcox_li">
            <img th:src="@{/show/images/ph_a1.png}"/>
            <p>市政府一办7楼机房:1号摄像头</p>
        </div>
    </dd>
    <!-- cameraTwo / cameraThree ... 其它占位省略 -->
</dl>

十、步骤八:初始化播放插件

在 window.load 之后创建 WebControl 实例、启动本地服务、创建播放窗口:

javascript 复制代码
//20230203添加:通过插件显示监控画面
//声明公用变量
var initCount = 0;
var pubKey = '';
var cameraIndexCodes = [];

//页面加载时创建播放实例初始化
$(window).load(function () {
    initPlugin();
});

// 创建播放实例
function initPlugin() {
    oWebControl = new WebControl({
        szPluginContainer: "playWnd",                       // 指定容器id
        iServicePortStart: 15900,                           // 指定起止端口号,建议使用该值
        iServicePortEnd: 15909,
        szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11",  // 用于IE10使用ActiveX的clsid
        cbConnectSuccess: function () {                     // 创建WebControl实例成功
            oWebControl.JS_StartService("window", {         // WebControl实例创建成功后需要启动服务
                dllPath: "./VideoPluginConnect.dll"         // 值"./VideoPluginConnect.dll"写死
            }).then(function () {                           // 启动插件服务成功
                oWebControl.JS_SetWindowControlCallback({   // 设置消息回调
                    cbIntegrationCallBack: cbIntegrationCallBack
                });

                oWebControl.JS_CreateWnd("playWnd", 1066, 770).then(function () { //JS_CreateWnd创建视频播放窗口,宽高可设定
                    init();  // 创建播放实例成功后初始化
                });
            }, function () { // 启动插件服务失败
            });
        },
        cbConnectError: function () { // 创建WebControl实例失败
            oWebControl = null;
            $("#playWnd").html("插件未启动,正在尝试启动,请稍候...");
            WebControl.JS_WakeUp("VideoWebPlugin://"); // 程序未启动时执行error函数,采用wakeup来启动程序
            initCount++;
            if (initCount < 3) {
                setTimeout(function () {
                    initPlugin();
                }, 3000)
            } else {
                $("#playWnd").html("插件启动失败,请检查插件是否安装!");
                downloadFileForTemplate('/plugin/VideoWebPlugin.exe');
            }
        },
        cbConnectClose: function (bNormalClose) {
            // 异常断开:bNormalClose = false
            // JS_Disconnect正常断开:bNormalClose = true
            console.log("cbConnectClose");
            oWebControl = null;
        }
    });
}

兜底逻辑:插件未安装时 cbConnectError 会先 JS_WakeUp 唤醒,重试 3 次仍失败则提示并引导下载 VideoWebPlugin.exe。


十一、步骤九:插件登录安防平台

插件启动后需要登录平台才能取流。secret 需先用平台公钥做 RSA 加密。

javascript 复制代码
//初始化
function init() {
    getPubKey(function () {
        ////////////////////////////////// 请自行修改以下变量值 ////////////////////////////////////
        var appkey = "xxxx";                           //综合安防管理平台提供的appkey,必填
        var secret = setEncrypt("xxxx");   //综合安防管理平台提供的secret,必填
        var ip = "192.168.x.x";                            //综合安防管理平台IP地址,必填
        var playMode = 0;                                  //初始播放模式:0-预览,1-回放
        var port = 443;                                    //综合安防管理平台端口,若启用HTTPS协议,默认443
        var snapDir = "C:\\SnapDir";                       //抓图存储路径
        var videoDir = "C:\\VideoDir";                     //紧急录像或录像剪辑存储路径
        var layout = "3x3";                                //playMode指定模式的布局
        var enableHTTPS = 1;                               //是否启用HTTPS协议与综合安防管理平台交互,是为1,否为0
        var encryptedFields = 'secret';                    //加密字段,默认加密领域为secret
        var showToolbar = 1;                               //是否显示工具栏,0-不显示,非0-显示
        var showSmart = 1;                                 //是否显示智能信息,0-不显示,非0-显示
        var buttonIDs = "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769";  //自定义工具条按钮
        ////////////////////////////////// 请自行修改以上变量值 ////////////////////////////////////

        oWebControl.JS_RequestInterface({
            funcName: "init",
            argument: JSON.stringify({
                appkey: appkey,                    //API网关提供的appkey
                secret: secret,                    //API网关提供的secret
                ip: ip,                            //API网关IP地址
                playMode: playMode,                //播放模式(决定显示预览还是回放界面)
                port: port,                        //端口
                snapDir: snapDir,                  //抓图存储路径
                videoDir: videoDir,                //紧急录像或录像剪辑存储路径
                layout: layout,                    //布局
                enableHTTPS: enableHTTPS,          //是否启用HTTPS协议
                encryptedFields: encryptedFields,  //加密字段
                showToolbar: showToolbar,          //是否显示工具栏
                showSmart: showSmart,              //是否显示智能信息
                buttonIDs: buttonIDs               //自定义工具条按钮
            })
        }).then(function (oData) {
            oWebControl.JS_Resize(1066, 770);  // 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题
        });
    });
}

//获取公钥
function getPubKey(callback) {
    oWebControl.JS_RequestInterface({
        funcName: "getRSAPubKey",
        argument: JSON.stringify({
            keyLength: 1024
        })
    }).then(function (oData) {
        console.log(oData);
        if (oData.responseMsg.data) {
            pubKey = oData.responseMsg.data;
            callback()
        }
    })
}

//RSA加密
function setEncrypt(value) {
    var encrypt = new JSEncrypt();
    encrypt.setPublicKey(pubKey);
    return encrypt.encrypt(value);
}

十二、步骤十:拉取监控点并开始预览

页面加载时用 AJAX 请求后端监控点列表,然后逐个调用 startPreview 取流播放。

12.1 拉取监控点列表

javascript 复制代码
//视频监控点
function videoPreview() {
    $.ajax({
        cache: true,
        type: "get",
        url: prefix + "/video/preview?times=" + new Date().getTime(),
        async: true,
        error: function (request) {
            // layer.alert("Connection error");
        },
        success: function (result) {
            if (result.code == 0) {
                var data = result.data;
                for (var i = 0; i < data.length; i++) {
                    startPreview(data[i].code, i + 1);
                }
            } else {
                // layer.alert(result.msg)
            }
        }
    });
}

12.2 逐个启动预览

javascript 复制代码
//视频预览功能
function startPreview(indexCode, index) {
    var cameraIndexCode = indexCode;     //获取输入的监控点编号值,必填
    setTimeout(function () {
        console.log("cameraIndexCode============" + indexCode);
        var streamMode = 0;              //主子码流标识:0-主码流,1-子码流
        var transMode = 1;               //传输协议:0-UDP,1-TCP
        var gpuMode = 0;                 //是否启用GPU硬解,0-不启用,1-启用
        var wndId = -1;                  //播放窗口序号(在2x2以上布局下可指定播放窗口)

        cameraIndexCode = cameraIndexCode.replace(/(^\s*)/g, "");
        cameraIndexCode = cameraIndexCode.replace(/(\s*$)/g, "");

        oWebControl.JS_RequestInterface({
            funcName: "startPreview",
            argument: JSON.stringify({
                cameraIndexCode: cameraIndexCode,   //监控点编号
                streamMode: streamMode,             //主子码流标识
                transMode: transMode,               //传输协议
                gpuMode: gpuMode,                   //是否开启GPU硬解
                wndId: wndId                        //可指定播放窗口
            })
        })
    }, 2000 * index);
}

12.3 页面调用顺序

javascript 复制代码
// 页面加载时拉取监控点编号,内部触发 startPreview 逐个预览
videoPreview();

插件初始化 initPlugin() 是在 window.load 之后单独执行的(见「步骤八」),与上面这行调用不在同一时机。

videoPreview() 在脚本解析阶段即执行,而插件在 window.load 之后才初始化,因此 startPreview 以 setTimeout(2000 * index) 逐个延迟发起,避免插件尚未就绪。


十三、步骤十一:窗口自适应与资源释放

插件窗口独立于 DOM,需要监听窗口变化并同步尺寸,页面关闭时释放连接。

javascript 复制代码
// 监听resize事件,使插件窗口尺寸跟随DIV窗口变化
$(window).resize(function () {
    if (oWebControl != null) {
        oWebControl.JS_Resize(1066, 770);
    }
});

// 监听滚动条scroll事件,使插件窗口跟随浏览器滚动而移动
$(window).scroll(function () {
    if (oWebControl != null) {
        oWebControl.JS_Resize(1066, 770);
    }
});

// 标签关闭
$(window).unload(function () {
    if (oWebControl != null) {
        oWebControl.JS_HideWnd();   // 先让窗口隐藏,规避可能的插件窗口滞后于浏览器消失问题
        oWebControl.JS_Disconnect().then(function () {   // 断开与插件服务连接成功
        }, function () {                                 // 断开与插件服务连接失败
        });
    }
});

//停止全部预览
$("#stopAllPreview").click(function () {
    oWebControl.JS_RequestInterface({
        funcName: "stopAllPreview"
    });
});

// 设置窗口控制回调
function setCallbacks() {
    oWebControl.JS_SetWindowControlCallback({
        cbIntegrationCallBack: cbIntegrationCallBack
    });
}

// 推送消息
function cbIntegrationCallBack(oData) {
    showCBInfo(JSON.stringify(oData.responseMsg));
}

十四、接口清单

14.1 大屏视频预览链路(本文档主线用到)

接口 方法 说明
/show/video/preview GET 大屏展示-视频监控点列表(含取流 URL、编号、位置)
/artemis/api/video/v1/cameras/previewURLs POST 海康:获取监控点预览取流 URL

大屏监控预览只用上面这 2 个接口:本地设备库查监控点 + 调海康取流接口。

14.2 区域/监控点维护链路(大屏监控未用到)

下列接口仅用于「监控点树展示」与「监控点编号回写」,大屏视频预览不依赖它们:

接口 方法 说明 实际调用方
/artemis/api/resource/v1/regions/root POST 海康:获取根区域信息 getCamerasTree()、ShowServiceImpl.updateCamera()
/artemis/api/resource/v1/regions/subRegions POST 海康:获取下一级区域 同上
/artemis/api/resource/v1/regions/regionIndexCode/cameras POST 海康:获取区域下监控点列表 同上

调用入口说明:

  • getCamerasTree() → 海康实时预览页 /hikvision/isecureCenter/security、回看页 /playback、门禁视频页的左侧监控点树。
  • ShowServiceImpl.updateCamera() → /show/updateCamera,把平台 cameraIndexCode 回写到设备库 InfoDO.code;该接口目前无页面/JS 调用,需手工触发。
  • 另 getRegionOrgList() 只用到其中 root + subRegions(不含 cameras)。

十五、注意事项与常见问题

  1. 凭证重复与安全问题

    后端 application-dev.yml 与前端 index-in.html 中各自维护了一份 appkey/appSecret/ip。前端 secret 只能通过插件 getRSAPubKey + JSEncrypt 加密后传输,但仍会暴露在页面源码里,建议改为后端下发或放入配置并由接口返回,避免明文硬编码。

  2. 前端播放不依赖后端返回的 url

    插件通过 cameraIndexCode 自行取流,previewURL() 返回的 url 目前未被使用。若后续要用 <video>/播放器以 RTMP/HLS 方式播放,可直接使用该字段(RTMP 场景建议 streamType=1 子码流,降低带宽与解码压力)。

  3. 插件初始化时序

    startPreview 必须在 oWebControl 创建成功之后发起,所以代码用 setTimeout(2000 * index) 做延迟。监控点数量较多时该延迟会线性累加(第 N 个点等待 2000*N 毫秒),可按需改为「插件 init 完成后再统一遍历预览」,避免首屏等待过久。

  4. 插件未安装的兜底

    cbConnectError 中通过 WebControl.JS_WakeUp("VideoWebPlugin://") 唤醒插件,重试 3 次后引导下载 VideoWebPlugin.exe。部署时需确保该安装包可被访问(本项目通过 /common/sysFile/downloadFile 下载)。

  5. 端口占用

    插件本地服务占用 15900-15909,若被其它程序占用会导致启动失败,需排查端口冲突。

  6. 窗口尺寸写死为 1066 x 770

    JS_CreateWnd、JS_Resize 均使用固定宽高。若大屏分辨率或容器尺寸变化,需要同步修改这些数值,否则会出现画面与容器错位。

  7. HTTPS / 浏览器兼容

    插件与平台交互默认走 HTTPS(enableHTTPS=1、port=443),页面本身若是 HTTPS 部署,需注意与插件本地服务之间的混合内容限制;IE10 场景依赖 szClassId 走 ActiveX。

  8. 监控点编号维护

    设备库 InfoDO.code 必须与平台 cameraIndexCode 一致。若平台侧调整了监控点,可通过 ShowServiceImpl.updateCamera() 重新按区域拉取并回写 code,否则会提示取流失败或黑屏。


十六、附录:完整源码(可直接复制使用)

说明:以下代码均为只保留视频监控部分的精简版,已剔除空调、UPS、门禁、流量等无关业务,但类结构、包名、依赖注入、import 保持完整,可直接复制进工程编译使用。

  • 后端:ShowController → ShowService → ShowServiceImpl → IsecureCenterService
  • 前端:index-in.html 的视频监控部分(容器 + 插件初始化 + 预览)
  • 配置:application-dev.yml 片段见「第四章」,pom.xml 依赖片段见「第三章」

16.1 后端源码

16.1.1 ShowController.java
java 复制代码
package com.cecjx.show.controller;

import com.cecjx.common.annotation.Log;
import com.cecjx.common.utils.R;
import com.cecjx.show.service.ShowService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author huangquanguang
 * @title 大屏展示功能(仅保留视频监控相关)
 * @description 大屏展示入口 + 视频监控点接口
 * @date 2020/4/1
 */
@Controller
@RequestMapping("show")
public class ShowController {
    @Value("${monitor.env}")
    private String monitorEnv;

    @Autowired
    private ShowService showService;

    /**
     * 大屏展示页面入口
     * 注意:dzww 环境返回 show/index-out 系列模板,非 dzww 返回 show/index-in
     */
    @GetMapping("index")
    @Log("大屏展示页面")
    String index(Model model, String type) {
        if ("dzww".equals(monitorEnv)) {
            if ("1".equals(type)) {
                return "show/index-out1";
            } else {
                return "show/index-out";
            }
        } else {
            return "show/index-in";
        }
    }

    @ResponseBody
    @Log("大屏展示-视频监控点")
    @GetMapping("video/preview")
    public R videoPreview() {
        return showService.videoPreview();
    }

    @ResponseBody
    @Log("更新摄像头编号")
    @GetMapping("updateCamera")
    public R updateCamera() {
        return showService.updateCamera();
    }
}
16.1.2 ShowService.java
java 复制代码
package com.cecjx.show.service;

import com.cecjx.common.utils.R;

/**
 * 大屏展示服务(仅保留视频监控相关)
 */
public interface ShowService {

    /**
     * 大屏展示-视频监控点列表(含取流 URL、编号、位置)
     */
    R videoPreview();

    /**
     * 从海康平台按区域重新拉取监控点,回写设备库编码
     */
    R updateCamera();
}
16.1.3 ShowServiceImpl.java
java 复制代码
package com.cecjx.show.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cecjx.common.utils.JSONUtils;
import com.cecjx.common.utils.R;
import com.cecjx.common.utils.StringUtils;
import com.cecjx.device.dao.InfoDao;
import com.cecjx.device.domain.InfoDO;
import com.cecjx.rotaryring.service.IsecureCenterService;
import com.cecjx.show.service.ShowService;
import com.github.pagehelper.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author huangquanguang
 * @title 大屏展示服务实现(仅保留视频监控相关)
 * @date 2020/4/1
 */
@Service
public class ShowServiceImpl implements ShowService {

    @Autowired
    private InfoDao infoDao;
    @Autowired
    private IsecureCenterService isecureCenterService;

    @Override
    public R videoPreview() {
        try {
            //先查出监控点设备(大屏展示的监控点)
            Map<String, Object> params = new HashMap<>();
            params.put("typeId", "5");        // 5 = 视频监控点类型
            params.put("screenShow", "1");    // 1 = 在大屏展示
            Page<InfoDO> list = infoDao.list(params).setOrderBy("showArea asc");
            List<Map<String, Object>> resultList = new ArrayList<>();
            if (null != list && list.size() > 0) {
                Map<String, Object> item;
                for (int i = 0; i < list.size(); i++) {
                    InfoDO info = list.get(i);
                    item = new HashMap<>();
                    item.put("name", info.getName());
                    String url = isecureCenterService.previewURL(info.getCode(), "rtmp");
                    item.put("url", url);
                    item.put("showArea", info.getShowArea());
                    item.put("code", info.getCode());
                    resultList.add(item);
                }
            }
            return R.ok().put("data", resultList);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return R.error();
    }

    /**
     * 更新摄像头编号操作(从海康平台按区域拉取监控点,回写设备库 code)
     *
     * @return
     */
    @Override
    public R updateCamera() {
        List<Map<String, Object>> reusltList = new ArrayList<>();
        Map<String, Object> firstMap = new HashMap<>();
        //获取根区域信息
        String firstRegion = isecureCenterService.getRegionsRoot();
        if (StringUtils.isEmpty(firstRegion)) {
            return R.error();
        }
        Map<String, Object> first = JSONUtils.jsonToMap(firstRegion);
        String code = (String) first.get("code");
        if (!"0".equals(code)) {
            return R.error();
        }
        JSONObject result = (JSONObject) first.get("data");
        String name = result.getString("name");
        String indexCode = result.getString("indexCode");

        firstMap.put("name", name);
        firstMap.put("indexCode", indexCode);

        //获取下一级区域信息(二级)
        String secondRegion = isecureCenterService.regionsSubRegions(indexCode);
        if (StringUtils.isEmpty(secondRegion)) {
            return R.error();
        }
        Map<String, Object> second = JSONUtils.jsonToMap(secondRegion);
        code = (String) second.get("code");
        if (!"0".equals(code)) {
            return R.error();
        }
        List secondList = new ArrayList();
        JSONArray secondArr = (JSONArray) second.get("data");
        for (int i = 0; i < secondArr.size(); i++) {
            Map secondMap = new HashMap<>();
            JSONObject secondItem = (JSONObject) secondArr.get(i);
            String secondName = secondItem.getString("name");
            String secondIndexCode = secondItem.getString("indexCode");
            secondMap.put("name", secondName);
            secondMap.put("indexCode", secondIndexCode);
            //获取下一级区域信息(三级)
            String thirdRegion = isecureCenterService.regionsCameras(secondIndexCode, 1, 1000);
            if (StringUtils.isEmpty(thirdRegion)) {
                continue;
            }
            Map<String, Object> third = JSONUtils.jsonToMap(thirdRegion);
            code = (String) third.get("code");
            if (!"0".equals(code)) {
                continue;
            }
            JSONObject data = (JSONObject) third.get("data");
            JSONArray listArr = data.getJSONArray("list");
            List thirdChildren = new ArrayList();
            for (int j = 0; j < listArr.size(); j++) {
                Map thirdMap = new HashMap<>();
                JSONObject thirdItem = (JSONObject) listArr.get(j);
                String cameraName = thirdItem.getString("cameraName");
                String cameraIndexCode = thirdItem.getString("cameraIndexCode");
                thirdMap.put("cameraName", cameraName);
                thirdMap.put("cameraIndexCode", cameraIndexCode);
                Map<String, Object> params = new HashMap<>();
                params.put("name", cameraName.replaceAll(" ", ""));
                Page<InfoDO> list = infoDao.list(params);
                if (null != list && list.size() > 0) {
                    InfoDO infoDO = list.getResult().get(0);
                    InfoDO updateDO = new InfoDO();
                    updateDO.setCode(cameraIndexCode);
                    updateDO.setId(infoDO.getId());
                    infoDao.update(updateDO);
                }
                thirdChildren.add(thirdMap);
            }
            secondMap.put("children", thirdChildren);
            secondList.add(secondMap);
        }
        firstMap.put("children", secondList);
        return R.ok().put("data", reusltList);
    }
}
16.1.4 IsecureCenterService.java(视频监控部分)
java 复制代码
package com.cecjx.rotaryring.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cecjx.common.utils.JSONUtils;
import com.cecjx.common.utils.StringUtils;
import com.hikvision.artemis.sdk.ArtemisHttpUtil;
import com.hikvision.artemis.sdk.config.ArtemisConfig;
import org.apache.http.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author huangquanguang
 * @date 2020/1/13 14:47
 * @description 海康威视对接安防平台接口处理(仅保留视频监控部分)
 */
@Component
public class IsecureCenterService {
    private static final Logger logger = LoggerFactory.getLogger(IsecureCenterService.class);

    @Value("${hikvision.artemis.host}")
    private String host;
    @Value("${hikvision.artemis.appKey}")
    private String appkey;
    @Value("${hikvision.artemis.appSecret}")
    private String appSecret;

    /***********************************
     * 视频服务接口
     **********************************/
    /**
     * 获取监控点预览取流URL
     */
    public String previewURL(String cameraIndexCode, String protocol) {
        String api = "/api/video/v1/cameras/previewURLs";
        JSONObject body = new JSONObject();
        body.put("cameraIndexCode", cameraIndexCode);
        //rtsp 只有当 streamType = 1,即获取子流码的时候 vlc 才能播放,若是都不匹配就使用 rtmp 协议
        switch (protocol) {
            case "rtsp":
                body.put("streamType", 0);
                body.put("protocol", "rtsp");
                body.put("transmode", 1);
                body.put("expand", "streamform=rtp");
                break;
            case "rtmp":
                body.put("streamType", 1);
                body.put("protocol", "rtmp");
                body.put("transmode", 1);
                body.put("expand", "streamform=rtp");
                break;
            case "rtmp2":
                // 使用主流码
                body.put("streamType", 0);
                body.put("protocol", "rtmp");
                body.put("transmode", 1);
                body.put("expand", "streamform=rtp");
                break;
            case "hls":
                body.put("streamType", 0);
                body.put("protocol", "hls");
                body.put("transmode", 1);
                body.put("expand", "streamform=ps");
                break;
            default:
                body.put("streamType", 0);
                body.put("protocol", "rtmp");
                body.put("transmode", 1);
                body.put("expand", "streamform=rtp");
        }
        String result = doPost(api, body);
        return result;
    }

    /***********************************
     * 区域信息接口
     **********************************/
    /**
     * 获取根区域信息
     */
    public String getRegionsRoot() {
        String api = "/api/resource/v1/regions/root";
        JSONObject body = new JSONObject();
        String result = doPost(api, body);
        return result;
    }

    /**
     * 根据区域编号获取下一级区域列表
     */
    public String regionsSubRegions(String parentIndexCode) {
        String api = "/api/resource/v1/regions/subRegions";
        JSONObject body = new JSONObject();
        body.put("parentIndexCode", parentIndexCode);
        String result = doPost(api, body);
        return result;
    }

    /**
     * 根据区域编号获取下级监控点列表
     */
    public String regionsCameras(String regionIndexCode, Integer pageNo, Integer pageSize) {
        String api = "/api/resource/v1/regions/regionIndexCode/cameras";
        JSONObject body = new JSONObject();
        body.put("regionIndexCode", regionIndexCode);
        body.put("pageNo", pageNo);
        body.put("pageSize", pageSize);
        String result = doPost(api, body);
        JSONObject resBody = JSONObject.parseObject(result);
        String code = null;
        if (null != resBody) {
            code = resBody.getString("code");
        } else {
            JSONObject response = new JSONObject();
            response.put("code", "0");
            JSONObject list = new JSONObject();
            list.put("list", new ArrayList<>());
            response.put("data", list);
            return response.toJSONString();
        }

        // 若是成功返回,则执行下面的代码过滤掉无用的属性
        if ("0".equals(code)) {
            JSONArray array = resBody.getJSONObject("data").getJSONArray("list");

            List<ModifyItem> modifyItems = new ArrayList<>();
            for (int i = 0; i < array.size(); i++) {
                JSONObject item = array.getJSONObject(i);
                ModifyItem modifyItem = new ModifyItem(item.getString("cameraIndexCode"), item.getString("cameraName"), item.getString("regionIndexCode"));
                modifyItems.add(modifyItem);
            }
            JSONObject response = new JSONObject();
            response.put("code", "0");
            JSONObject list = new JSONObject();
            // 对集合进行排序
            Collections.sort(modifyItems);
            list.put("list", modifyItems);
            response.put("data", list);
            return response.toJSONString();
        } else {
            JSONObject response = new JSONObject();
            response.put("code", "0");
            JSONObject list = new JSONObject();
            list.put("list", new ArrayList<>());
            response.put("data", list);
            return response.toJSONString();
        }
    }

    /**
     * 使用内部类进行调整后的摄像头 item 并进行排序
     */
    private class ModifyItem implements Comparable {
        private String cameraIndexCode;
        private String cameraName;
        private String regionIndexCode;
        private String picUrl = "";

        public ModifyItem(String cameraIndexCode, String cameraName, String regionIndexCode) {
            this.cameraIndexCode = cameraIndexCode;
            this.cameraName = cameraName;
            this.regionIndexCode = regionIndexCode;
        }

        public String getCameraIndexCode() {
            return cameraIndexCode;
        }

        public void setCameraIndexCode(String cameraIndexCode) {
            this.cameraIndexCode = cameraIndexCode;
        }

        public String getCameraName() {
            return cameraName;
        }

        public void setCameraName(String cameraName) {
            this.cameraName = cameraName;
        }

        public String getRegionIndexCode() {
            return regionIndexCode;
        }

        public void setRegionIndexCode(String regionIndexCode) {
            this.regionIndexCode = regionIndexCode;
        }

        public String getPicUrl() {
            return picUrl;
        }

        public void setPicUrl(String picUrl) {
            this.picUrl = picUrl;
        }

        @Override
        public int compareTo(Object o) {
            ModifyItem modifyItem = (ModifyItem) o;
            return cameraName.compareTo(modifyItem.getCameraName());
        }
    }

    /**
     * 获取监控点列表的Tree(供监控点树页面使用,大屏预览不需要)
     */
    public List<Map<String, Object>> getCamerasTree() {
        List<Map<String, Object>> reusltList = new ArrayList<>();
        try {
            //获取根区域信息
            Map<String, Object> firstMap = new HashMap<>();
            String firstRegion = getRegionsRoot();
            if (StringUtils.isEmpty(firstRegion)) {
                return reusltList;
            }
            Map<String, Object> first = JSONUtils.jsonToMap(firstRegion);
            String code = (String) first.get("code");
            if (!"0".equals(code)) {
                return reusltList;
            }
            JSONObject result = (JSONObject) first.get("data");
            String name = result.getString("name");
            String indexCode = result.getString("indexCode");

            firstMap.put("name", name);
            firstMap.put("text", name);
            firstMap.put("indexCode", indexCode);

            //获取下一级区域信息(二级)
            String secondRegion = regionsSubRegions(indexCode);
            if (StringUtils.isEmpty(secondRegion)) {
                reusltList.add(firstMap);
                return reusltList;
            }
            Map<String, Object> second = JSONUtils.jsonToMap(secondRegion);
            code = (String) second.get("code");
            if (!"0".equals(code)) {
                reusltList.add(firstMap);
                return reusltList;
            }

            List secondList = new ArrayList();
            JSONArray secondArr = (JSONArray) second.get("data");
            for (int i = 0; i < secondArr.size(); i++) {
                Map secondMap = new HashMap<>();
                JSONObject secondItem = (JSONObject) secondArr.get(i);
                String secondName = secondItem.getString("name");
                String secondIndexCode = secondItem.getString("indexCode");
                secondMap.put("name", secondName);
                secondMap.put("text", secondName);
                secondMap.put("indexCode", secondIndexCode);

                //获取下一级区域信息(三级)
                String thirdRegion = regionsCameras(secondIndexCode, 1, 1000);
                if (StringUtils.isEmpty(thirdRegion)) {
                    continue;
                }
                Map<String, Object> third = JSONUtils.jsonToMap(thirdRegion);
                code = (String) third.get("code");
                if (!"0".equals(code)) {
                    continue;
                }
                JSONObject data = (JSONObject) third.get("data");
                JSONArray listArr = data.getJSONArray("list");
                List thirdChildren = new ArrayList();
                for (int j = 0; j < listArr.size(); j++) {
                    Map thirdMap = new HashMap<>();
                    JSONObject thirdItem = (JSONObject) listArr.get(j);
                    String cameraName = thirdItem.getString("cameraName");
                    String cameraIndexCode = thirdItem.getString("cameraIndexCode");
                    thirdMap.put("text", cameraName);
                    thirdMap.put("cameraName", cameraName);
                    thirdMap.put("cameraIndexCode", cameraIndexCode);
                    thirdChildren.add(thirdMap);
                }
                secondMap.put("children", thirdChildren);
                secondList.add(secondMap);
            }
            firstMap.put("children", secondList);
            reusltList.add(firstMap);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return reusltList;
    }

    /***********************************
     * 公共方法
     **********************************/

    /**
     * 海康威视的 POST 请求
     *
     * @param api         api 接口的地址
     * @param body        form 表单请求的参数,键值对形式的 map;
     * @param querys      map 类型 get 请求的 url 查询参数
     * @param head        接受 head 参数,采用 map 类型
     * @param accept      指定客户端能够接收的内容类型
     * @param contentType 请求的与实体对应的 MIME 信息
     * @return
     */
    public String doPost(String api, JSONObject body, Map querys, String accept, String contentType, Map head) {
        if (ArtemisConfig.host == null || ArtemisConfig.appKey == null || ArtemisConfig.appSecret == null) {
            // artemis 网关服务器 ip 端口
            ArtemisConfig.host = host;
            // 秘钥 appkey
            ArtemisConfig.appKey = appkey;
            // 秘钥 appSecret
            ArtemisConfig.appSecret = appSecret;
        }

        // 接口路径
        final String getSecurityApi = "/artemis" + api;
        Map<String, String> path = new HashMap<String, String>(1) {
            {
                put("https://", getSecurityApi);
            }
        };
        String result = null;
        try {
            /*
             * 使用doPostStringArtemis()方法,不能使用 doPostFormArtemis()方法,
             * 因为后者中body参数使用map存储存在问题
             */
            if (head == null) {
                result = ArtemisHttpUtil.doPostStringArtemis(path, body.toJSONString(), null, null, "application/json");
            } else {
                result = ArtemisHttpUtil.doPostStringArtemis(path, body.toJSONString(), querys, accept, contentType, head);
            }
        } catch (Exception e) {
            logger.error("海康接口访问失败!");
        }
        return result;
    }

    public String doPost(String api, JSONObject body) {
        return doPost(api, body, null, null, null, null);
    }

    /**
     * 需要下载图片流时使用(视频抓图/门禁抓拍等)
     */
    public HttpResponse doPostImg(String api, JSONObject body) {
        if (ArtemisConfig.host == null || ArtemisConfig.appKey == null || ArtemisConfig.appSecret == null) {
            ArtemisConfig.host = host;
            ArtemisConfig.appKey = appkey;
            ArtemisConfig.appSecret = appSecret;
        }

        // 接口路径
        final String getSecurityApi = "/artemis" + api;
        Map<String, String> path = new HashMap<String, String>(1) {
            {
                put("https://", getSecurityApi);
            }
        };
        HttpResponse result = null;
        try {
            result = ArtemisHttpUtil.doPostStringImgArtemis(path, body.toJSONString(), null, null, "application/json", null);
        } catch (Exception e) {
            logger.error("海康接口访问失败!");
        }
        return result;
    }
}

16.2 前端源码(index-in.html 视频监控部分)

已剔除系统连通性、OA、温湿度、UPS、门禁、流量等其它设备/模块的前端代码,仅保留视频监控容器与播放逻辑。

依赖的外部文件:layer.js(非必需,可去掉)、/js/appjs/hikvision/websdk/ 下的 jquery-1.12.4.min.js、jsencrypt.min.js、jsWebControl-1.0.0.min.js,以及 cecjx-utils.js 中的 downloadFileForTemplate()。

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <title>视频监控大屏</title>
    <link rel="stylesheet" type="text/css" th:href="@{/show/css/base.css}"/>
    <link rel="stylesheet" type="text/css" th:href="@{/show/css/style.css}"/>
    <!-- 大屏自身依赖的 jquery/echarts -->
    <script type="text/javascript" th:src="@{/show/js/jquery-2.1.4.min.js}"></script>
</head>
<body style="overflow: auto;" class="nei_con">
<!-- ========== 视频监控容器:插件播放窗口会挂到 id="playWnd" 上 ========== -->
<div class="in_box" style="width: 56%;">
    <!--监控摄像头-->
    <div class="sx_cox dd_three clear">
        <dl class="clear" id="video">
            <dd id="playWnd">
                <div class="sxcox_li">
                    <img th:src="@{/show/images/ph_a1.png}"/>
                    <p>市政府一办7楼机房:1号摄像头</p>
                </div>
            </dd>
            <dd id="cameraTwo">
                <div class="sxcox_li">
                    <img th:src="@{/show/images/ph_a2.png}"/>
                    <p>市政府一办7楼机房:2号摄像头</p>
                </div>
            </dd>
            <dd id="cameraThree">
                <div class="sxcox_li">
                    <img th:src="@{/show/images/ph_a3.png}"/>
                    <p>市政府一办7楼机房:3号摄像头</p>
                </div>
            </dd>
            <dd id="cameraFour">
                <div class="sxcox_li">
                    <img th:src="@{/show/images/ph_a4.png}"/>
                    <p>市政府一办7楼机房:4号摄像头</p>
                </div>
            </dd>
            <dd id="cameraFive">
                <div class="sxcox_li">
                    <img th:src="@{/show/images/ph_a5.png}"/>
                    <p>市政府一办7楼机房:5号摄像头</p>
                </div>
            </dd>
            <dd id="cameraSix">
                <div class="sxcox_li">
                    <img th:src="@{/show/images/ph_a6.png}"/>
                    <p>市政府一办7楼机房:6号摄像头</p>
                </div>
            </dd>
        </dl>
    </div>
</div>

<!-- 回调信息展示容器(可选,不需要可连同 showCBInfo 一起删除) -->
<div id="cbInfo" style="display: none;"></div>

<!-- ========== 海康 Web 插件 SDK ========== -->
<script th:inline="javascript"> var ctx = [[@{/}]] ; ctx=ctx.substring(0,ctx.length-1);</script>
<script type="text/javascript" th:src="@{/js/plugins/layer/layer.js}"></script>
<script type="text/javascript" th:src="@{/js/appjs/hikvision/websdk/jquery-1.12.4.min.js}"></script>
<script type="text/javascript" th:src="@{/js/appjs/hikvision/websdk/jsencrypt.min.js}"></script>
<script type="text/javascript" th:src="@{/js/appjs/hikvision/websdk/jsWebControl-1.0.0.min.js}"></script>
<script type="text/javascript">
    var prefix = ctx + "/show";
    var oWebControl = null;

    // ======================= 1. 拉取监控点并逐个预览 =======================

    //视频监控点
    function videoPreview() {
        $.ajax({
            cache: true,
            type: "get",
            url: prefix + "/video/preview?times=" + new Date().getTime(),
            async: true,
            error: function (request) {
                // layer.alert("Connection error");
            },
            success: function (result) {
                if (result.code == 0) {
                    var data = result.data;
                    for (var i = 0; i < data.length; i++) {
                        startPreview(data[i].code, i + 1);
                    }
                } else {
                    // layer.alert(result.msg)
                }
            }
        });
    }

    //视频预览功能
    function startPreview(indexCode, index) {
        var cameraIndexCode = indexCode;     //获取输入的监控点编号值,必填
        setTimeout(function () {
            console.log("cameraIndexCode============" + indexCode);
            var streamMode = 0;              //主子码流标识:0-主码流,1-子码流
            var transMode = 1;               //传输协议:0-UDP,1-TCP
            var gpuMode = 0;                 //是否启用GPU硬解,0-不启用,1-启用
            var wndId = -1;                  //播放窗口序号(在2x2以上布局下可指定播放窗口)

            cameraIndexCode = cameraIndexCode.replace(/(^\s*)/g, "");
            cameraIndexCode = cameraIndexCode.replace(/(\s*$)/g, "");

            oWebControl.JS_RequestInterface({
                funcName: "startPreview",
                argument: JSON.stringify({
                    cameraIndexCode: cameraIndexCode,   //监控点编号
                    streamMode: streamMode,             //主子码流标识
                    transMode: transMode,               //传输协议
                    gpuMode: gpuMode,                   //是否开启GPU硬解
                    wndId: wndId                        //可指定播放窗口
                })
            })
        }, 2000 * index);
    }

    // 页面加载时拉取监控点(插件在 window.load 后才初始化,故 startPreview 内部用 setTimeout 延迟)
    videoPreview();

    // ======================= 2. 播放插件初始化 =======================

    //声明公用变量
    var initCount = 0;
    var pubKey = '';
    var cameraIndexCodes = [];

    //页面加载时创建播放实例初始化
    $(window).load(function () {
        initPlugin();
    });

    // 创建播放实例
    function initPlugin() {
        oWebControl = new WebControl({
            szPluginContainer: "playWnd",                       // 指定容器id
            iServicePortStart: 15900,                           // 指定起止端口号,建议使用该值
            iServicePortEnd: 15909,
            szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11",  // 用于IE10使用ActiveX的clsid
            cbConnectSuccess: function () {                     // 创建WebControl实例成功
                oWebControl.JS_StartService("window", {         // WebControl实例创建成功后需要启动服务
                    dllPath: "./VideoPluginConnect.dll"         // 值"./VideoPluginConnect.dll"写死
                }).then(function () {                           // 启动插件服务成功
                    oWebControl.JS_SetWindowControlCallback({   // 设置消息回调
                        cbIntegrationCallBack: cbIntegrationCallBack
                    });

                    oWebControl.JS_CreateWnd("playWnd", 1066, 770).then(function () { //JS_CreateWnd创建视频播放窗口,宽高可设定
                        init();  // 创建播放实例成功后初始化
                    });
                }, function () { // 启动插件服务失败
                });
            },
            cbConnectError: function () { // 创建WebControl实例失败
                oWebControl = null;
                $("#playWnd").html("插件未启动,正在尝试启动,请稍候...");
                WebControl.JS_WakeUp("VideoWebPlugin://"); // 程序未启动时执行error函数,采用wakeup来启动程序
                initCount++;
                if (initCount < 3) {
                    setTimeout(function () {
                        initPlugin();
                    }, 3000)
                } else {
                    $("#playWnd").html("插件启动失败,请检查插件是否安装!");
                    downloadFileForTemplate('/plugin/VideoWebPlugin.exe');
                }
            },
            cbConnectClose: function (bNormalClose) {
                // 异常断开:bNormalClose = false
                // JS_Disconnect正常断开:bNormalClose = true
                console.log("cbConnectClose");
                oWebControl = null;
            }
        });
    }

    //初始化
    function init() {
        getPubKey(function () {
            ////////////////////////////////// 请自行修改以下变量值 ////////////////////////////////////
            var appkey = "xxxx";                           //综合安防管理平台提供的appkey,必填
            var secret = setEncrypt("xxxx");               //综合安防管理平台提供的secret,必填
            var ip = "192.168.x.x";                        //综合安防管理平台IP地址,必填
            var playMode = 0;                                  //初始播放模式:0-预览,1-回放
            var port = 443;                                    //综合安防管理平台端口,若启用HTTPS协议,默认443
            var snapDir = "C:\\SnapDir";                       //抓图存储路径
            var videoDir = "C:\\VideoDir";                     //紧急录像或录像剪辑存储路径
            var layout = "3x3";                                //playMode指定模式的布局
            var enableHTTPS = 1;                               //是否启用HTTPS协议与综合安防管理平台交互,是为1,否为0
            var encryptedFields = 'secret';                    //加密字段,默认加密领域为secret
            var showToolbar = 1;                               //是否显示工具栏,0-不显示,非0-显示
            var showSmart = 1;                                 //是否显示智能信息(如配置移动侦测后画面上的线框),0-不显示,非0-显示
            var buttonIDs = "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769";  //自定义工具条按钮
            ////////////////////////////////// 请自行修改以上变量值 ////////////////////////////////////

            oWebControl.JS_RequestInterface({
                funcName: "init",
                argument: JSON.stringify({
                    appkey: appkey,                            //API网关提供的appkey
                    secret: secret,                            //API网关提供的secret
                    ip: ip,                                    //API网关IP地址
                    playMode: playMode,                        //播放模式(决定显示预览还是回放界面)
                    port: port,                                //端口
                    snapDir: snapDir,                          //抓图存储路径
                    videoDir: videoDir,                        //紧急录像或录像剪辑存储路径
                    layout: layout,                            //布局
                    enableHTTPS: enableHTTPS,                  //是否启用HTTPS协议
                    encryptedFields: encryptedFields,          //加密字段
                    showToolbar: showToolbar,                  //是否显示工具栏
                    showSmart: showSmart,                      //是否显示智能信息
                    buttonIDs: buttonIDs                       //自定义工具条按钮
                })
            }).then(function (oData) {
                oWebControl.JS_Resize(1066, 770);  // 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题
            });
        });
    }

    //获取公钥
    function getPubKey(callback) {
        oWebControl.JS_RequestInterface({
            funcName: "getRSAPubKey",
            argument: JSON.stringify({
                keyLength: 1024
            })
        }).then(function (oData) {
            console.log(oData);
            if (oData.responseMsg.data) {
                pubKey = oData.responseMsg.data;
                callback()
            }
        })
    }

    //RSA加密
    function setEncrypt(value) {
        var encrypt = new JSEncrypt();
        encrypt.setPublicKey(pubKey);
        return encrypt.encrypt(value);
    }

    // ======================= 3. 窗口自适应 / 释放 / 回调 =======================

    // 监听resize事件,使插件窗口尺寸跟随DIV窗口变化
    $(window).resize(function () {
        if (oWebControl != null) {
            oWebControl.JS_Resize(1066, 770);
        }
    });

    // 监听滚动条scroll事件,使插件窗口跟随浏览器滚动而移动
    $(window).scroll(function () {
        if (oWebControl != null) {
            oWebControl.JS_Resize(1066, 770);
        }
    });

    // 标签关闭
    $(window).unload(function () {
        if (oWebControl != null) {
            oWebControl.JS_HideWnd();   // 先让窗口隐藏,规避可能的插件窗口滞后于浏览器消失问题
            oWebControl.JS_Disconnect().then(function () {  // 断开与插件服务连接成功
                },
                function () {  // 断开与插件服务连接失败
                });
        }
    });

    //停止全部预览
    $("#stopAllPreview").click(function () {
        oWebControl.JS_RequestInterface({
            funcName: "stopAllPreview"
        });
    });

    // 设置窗口控制回调
    function setCallbacks() {
        oWebControl.JS_SetWindowControlCallback({
            cbIntegrationCallBack: cbIntegrationCallBack
        });
    }

    // 推送消息
    function cbIntegrationCallBack(oData) {
        showCBInfo(JSON.stringify(oData.responseMsg));
    }

    // 显示回调信息
    function showCBInfo(szInfo, type) {
        if (type === 'error') {
            szInfo = "<div style='color: red;'>" + dateFormat(new Date(), "yyyy-MM-dd hh:mm:ss") + " " + szInfo + "</div>";
        } else {
            szInfo = "<div>" + dateFormat(new Date(), "yyyy-MM-dd hh:mm:ss") + " " + szInfo + "</div>";
        }
        $("#cbInfo").html(szInfo + $("#cbInfo").html());
    }

    // 格式化时间
    function dateFormat(oDate, fmt) {
        var o = {
            "M+": oDate.getMonth() + 1, //月份
            "d+": oDate.getDate(), //日
            "h+": oDate.getHours(), //小时
            "m+": oDate.getMinutes(), //分
            "s+": oDate.getSeconds(), //秒
            "q+": Math.floor((oDate.getMonth() + 3) / 3), //季度
            "S": oDate.getMilliseconds()//毫秒
        };
        if (/(y+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (oDate.getFullYear() + "").substr(4 - RegExp.$1.length));
        }
        for (var k in o) {
            if (new RegExp("(" + k + ")").test(fmt)) {
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            }
        }
        return fmt;
    }
</script>
</body>
</html>

说明:

  • 原页面中 #cbInfo、#stopAllPreview 两个元素并未渲染(仅 JS 里引用),附录里补上了 #cbInfo 容器;stopAllPreview 按钮如不需要可一并删除对应绑定。
  • downloadFileForTemplate() 来自 cecjx-utils.js,若单独复制本页需一并引入该工具 JS。
  • 页面为 Thymeleaf 模板,th:src / th:href 为模板语法;若改为纯静态页,替换为普通 src / href 并自行定义 ctx 即可。
相关推荐
小蒜学长2 小时前
基于SpringBoot+Vue的小学数学智能出题系统(代码+数据库+LW)
java·数据库·spring boot·后端·智能出题系统
FYKJ_20103 小时前
springboot贺州地方新闻网站87812-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·django·课程设计
MZ_ZXD0014 小时前
【关注可白嫖源码】--课程设计--毕业设计-- 42557基于springboot的大学生体质健康管理系统
spring boot·课程设计·毕设·大作业·程序开发·程序定制·毕设代做
w***48825 小时前
SpringBoot整合easy-es
spring boot·后端·elasticsearch
摇滚侠6 小时前
《Spring Boot 3:高级与架构设计》第 3 章 Bean 的全生命周期原理 Bean 的全生命周期概览 阅读笔记 9
spring boot·笔记·后端
光依旧8 小时前
MCP实战手记(九):生产化MCP Server的6层安全防护
java·人工智能·spring boot·安全·网络安全·ai agent·mcp
马剑威(威哥爱编程)8 小时前
【AI全栈后端12-04】Spring Boot 用结构化输出自动解析简历:让模型按你的 POJO 输出
java·人工智能·spring boot·后端
xcl092515 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐17 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计