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 网页播放的场景。
二、前置条件
- 海康综合安防管理平台已部署并可访问 ,需提供:
- 平台 IP(本文示例
192.168.x.x) - Artemis 网关端口(启用 HTTPS 时默认
443) appKey、appSecret(API 网关凭证)
- 平台 IP(本文示例
- 平台侧已为 appKey 授权视频相关 API(如预览取流、资源查询等权限)。
- 客户端浏览器 需允许安装并运行本地插件,且本地
15900-15909端口未被占用。 - 后端工程已能正常编译(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)。
十五、注意事项与常见问题
-
凭证重复与安全问题
后端
application-dev.yml与前端index-in.html中各自维护了一份appkey/appSecret/ip。前端secret只能通过插件getRSAPubKey+JSEncrypt加密后传输,但仍会暴露在页面源码里,建议改为后端下发或放入配置并由接口返回,避免明文硬编码。 -
前端播放不依赖后端返回的
url插件通过
cameraIndexCode自行取流,previewURL()返回的url目前未被使用。若后续要用<video>/播放器以 RTMP/HLS 方式播放,可直接使用该字段(RTMP 场景建议streamType=1子码流,降低带宽与解码压力)。 -
插件初始化时序
startPreview必须在oWebControl创建成功之后发起,所以代码用setTimeout(2000 * index)做延迟。监控点数量较多时该延迟会线性累加(第 N 个点等待2000*N毫秒),可按需改为「插件init完成后再统一遍历预览」,避免首屏等待过久。 -
插件未安装的兜底
cbConnectError中通过WebControl.JS_WakeUp("VideoWebPlugin://")唤醒插件,重试 3 次后引导下载VideoWebPlugin.exe。部署时需确保该安装包可被访问(本项目通过/common/sysFile/downloadFile下载)。 -
端口占用
插件本地服务占用
15900-15909,若被其它程序占用会导致启动失败,需排查端口冲突。 -
窗口尺寸写死为
1066 x 770JS_CreateWnd、JS_Resize均使用固定宽高。若大屏分辨率或容器尺寸变化,需要同步修改这些数值,否则会出现画面与容器错位。 -
HTTPS / 浏览器兼容
插件与平台交互默认走 HTTPS(
enableHTTPS=1、port=443),页面本身若是 HTTPS 部署,需注意与插件本地服务之间的混合内容限制;IE10 场景依赖szClassId走 ActiveX。 -
监控点编号维护
设备库
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即可。