当前位置: 首页 > news >正文

springboot + vue3 拉取海康视频点位及播放

1: 文档地址: 这里有api列表

海康开放平台

  

2:运管中心的 api网关有api列表点击名称可以看到接口详情,可以在线测试接口数据 ,api 列表跟上面网页的api 列表差不多

接口详情: 

在线测试:  填写分页数据, key 和 secret 就可以了

3:springboot  海康签名工具类, 来自海康SDK包

package com.tang.object.utils;import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.*;public class HaiKangSignUtil {public static String CONTENTTYPE = "application/json";public HaiKangSignUtil() {}public static String sign(String secret, String method, String path, Map<String, Object> headers, Map<String, Object> querys, Map<String, Object> bodys, List<String> signHeaderPrefixList) {try {Mac hmacSha256 = Mac.getInstance("HmacSHA256");byte[] keyBytes = secret.getBytes("UTF-8");hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList);return new String(Base64.encodeBase64(hmacSha256.doFinal(stringToSign.getBytes("UTF-8"))), "UTF-8");} catch (Exception var14) {throw new RuntimeException(var14);}}private static String buildStringToSign(String method, String path, Map<String, Object> headers, Map<String, Object> querys, Map<String, Object> bodys, List<String> signHeaderPrefixList) {StringBuilder sb = new StringBuilder();sb.append(method.toUpperCase()).append("\n");if (null != headers) {if (null != headers.get("Accept")) {sb.append((String)headers.get("Accept"));sb.append("\n");}if (null != headers.get("Content-MD5")) {sb.append((String)headers.get("Content-MD5"));sb.append("\n");}if (null != headers.get("Content-Type")) {String contentType = (String)headers.get("Content-Type");if (contentType.contains("boundary")) {String[] strings = contentType.split(";");String[] var9 = strings;int var10 = strings.length;for(int var11 = 0; var11 < var10; ++var11) {String string = var9[var11];if (!string.contains("boundary")) {sb.append(string);sb.append(";");}}sb.deleteCharAt(sb.toString().length() - 1);} else {sb.append(contentType);}sb.append("\n");}if (null != headers.get("Date")) {sb.append((String)headers.get("Date"));sb.append("\n");}if (StringUtils.isNotEmpty((CharSequence)headers.get("x-ca-path"))) {path = (String)headers.get("x-ca-path");}}sb.append(buildHeaders(headers, signHeaderPrefixList));sb.append(buildResource(path, querys, bodys));return sb.toString();}private static String buildResource(String path, Map<String, Object> querys, Map<String, Object> bodys) {StringBuilder sb = new StringBuilder();if (!StringUtils.isBlank(path)) {sb.append(path);}Map<Object, Object> sortMap = new TreeMap();Iterator var5;Map.Entry body;if (null != querys) {var5 = querys.entrySet().iterator();while(var5.hasNext()) {body = (Map.Entry)var5.next();if (!StringUtils.isBlank((CharSequence)body.getKey())) {sortMap.put(body.getKey(), body.getValue());}}}if (null != bodys) {var5 = bodys.entrySet().iterator();while(var5.hasNext()) {body = (Map.Entry)var5.next();if (!StringUtils.isBlank((CharSequence)body.getKey())) {sortMap.put(body.getKey(), body.getValue());}}}StringBuilder sbParam = new StringBuilder();Iterator var9 = sortMap.entrySet().iterator();while(var9.hasNext()) {Map.Entry<String, Object> item = (Map.Entry)var9.next();if (!StringUtils.isBlank((CharSequence)item.getKey())) {if (0 < sbParam.length()) {sbParam.append("&");}sbParam.append((String)item.getKey());sbParam.append("=").append(item.getValue());}}if (0 < sbParam.length()) {sb.append("?");sb.append(sbParam);}return sb.toString();}private static String buildHeaders(Map<String, Object> headers, List<String> signHeaderPrefixList) {StringBuilder sb = new StringBuilder();if (null != signHeaderPrefixList) {signHeaderPrefixList.remove("x-ca-signature");signHeaderPrefixList.remove("Accept");signHeaderPrefixList.remove("Content-MD5");signHeaderPrefixList.remove("Content-Type");signHeaderPrefixList.remove("Date");Collections.sort(signHeaderPrefixList);}if (null != headers) {Map<String, Object> sortMap = new TreeMap();sortMap.putAll(headers);StringBuilder signHeadersStringBuilder = new StringBuilder();Iterator var5 = sortMap.entrySet().iterator();while(var5.hasNext()) {Map.Entry<String, String> header = (Map.Entry)var5.next();if (isHeaderToSign((String)header.getKey(), signHeaderPrefixList)) {sb.append((String)header.getKey());sb.append(":");if (!StringUtils.isBlank((CharSequence)header.getValue())) {sb.append((String)header.getValue());}sb.append("\n");if (0 < signHeadersStringBuilder.length()) {signHeadersStringBuilder.append(",");}signHeadersStringBuilder.append((String)header.getKey());}}headers.put("x-ca-signature-headers", signHeadersStringBuilder.toString());}return sb.toString();}private static boolean isHeaderToSign(String headerName, List<String> signHeaderPrefixList) {if (StringUtils.isBlank(headerName)) {return false;} else if ("x-ca-path".equals(headerName)) {return false;} else if (headerName.startsWith("x-ca-")) {return true;} else {if (null != signHeaderPrefixList) {Iterator var2 = signHeaderPrefixList.iterator();while(var2.hasNext()) {String signHeaderPrefix = (String)var2.next();if (headerName.equalsIgnoreCase(signHeaderPrefix)) {return true;}}}return false;}}public static String utf8ToIso88591(String str) {if (str == null) {return str;} else {try {return new String(str.getBytes("UTF-8"), "ISO-8859-1");} catch (UnsupportedEncodingException var2) {throw new RuntimeException(var2.getMessage(), var2);}}}public static Map<String, Object> initialBasicHeader(String method, String path, Map<String, Object> querys, Map<String, Object> bodys, String contentType, List<String> signHeaderPrefixList, String appKey, String appSecret) throws MalformedURLException {Map<String, Object> headers = new HashMap();headers.put("x-ca-timestamp", String.valueOf((new Date()).getTime()));headers.put("x-ca-nonce", UUID.randomUUID().toString());headers.put("x-ca-key", appKey);headers.put("Accept", "*/*");headers.put("Content-Type", contentType);headers.put("x-ca-signature", sign(appSecret, method, path, headers, querys, bodys, signHeaderPrefixList));return headers;}
}

4:拉取点位

接口名称 : 

private final static String previewURLsApi = "/artemis/api/resource/v1/cameras";

 代码:

private final static String previewURLsApi = "/artemis/api/resource/v1/cameras";public static String CONTENTTYPE = "application/json";// 请求方法public JSONObject requestByPostAddHeader(Object o, String url, MediaType mediaType, Map<String, Object> header) throws Exception {return CompletableFuture.supplyAsync(() -> {Mono<String> stringMono = ignoreSSLWebClient.post().uri(url).headers(httpHeaders -> {header.entrySet().stream().forEach(e->{httpHeaders.add(e.getKey(), e.getValue().toString());});})// 类型.contentType(mediaType)//参数.bodyValue(o).retrieve()//返回值类型.bodyToMono(String.class).timeout(Duration.ofSeconds(webclientTimeOut));JSONObject jsonObject = JSON.parseObject(stringMono.block(Duration.ofSeconds(webclientTimeOut)));if (null == jsonObject) {log.error(ExceptionEnum.WEBCLIENT_RESPONSE_IS_NULL.getMsg() + " 原因: " + jsonObject);throw new BusinessException(ExceptionEnum.WEBCLIENT_RESPONSE_IS_NULL);}return jsonObject;}, executorService)//异常处理.exceptionally((error) -> {// 处理任务  的返回值或异常error.printStackTrace();throw new BusinessException(ExceptionEnum.WEBCLIENT_EXCEPTION);}).get(webclientTimeOut, TimeUnit.SECONDS);}// 拉取点位
public viod test() {JSONObject body = new JSONObject();body.put("pageNo", 1);body.put("pageSize", 500);Map<String, Object> headers = HaiKangSignUtil.initialBasicHeader("POST", previewURLsApi, null, null, HaiKangSignUtil.CONTENTTYPE, null, wgHttpXyPO.getHtAppId(), wgHttpXyPO.getHtKey());for (Map.Entry<String, Object> entry : headers.entrySet()) {headers.put(entry.getKey(), HaiKangSignUtil.utf8ToIso88591(entry.getValue().toString()));}// 返回数据,获取分页总数,计算是否有下一页,重复调用,直到数据拉取完毕, 根据设备编号cameraIndexCode 监控点唯一标识进行增加或者修改。 saveOrUpdate 可以保存或更新。必要的几个字段
JSONObject jsonObject = webclientService.requestByPostAddHeader(body, ip+端口 + previewURLsApi, MediaType.APPLICATION_JSON, headers);}

返回数据中 监控唯一标识,播放用到此字段, 监控点名称(cameraame)后面状态更新时用到。 

5: 状态更新

   接口名称:

 private final static String previewURLsApi = "/artemis/api/nms/v1/online/encode_device/get";

  

 private final static String previewURLsApi = "/artemis/api/nms/v1/online/encode_device/get";// 工具类已包含public static String CONTENTTYPE = "application/json";// 请求方法上面接口 拿出来公共类使用就行
public void test(){JSONObject  body = new JSONObject();body.put("pageNo", 1);body.put("pageSize", 500);Map<String, Object> headers = HaiKangSignUtil.initialBasicHeader("POST", previewURLsApi, null, null, HaiKangSignUtil.CONTENTTYPE, null, wgHttpXyPO.getHtAppId(), wgHttpXyPO.getHtKey());for (Map.Entry<String, Object> entry : headers.entrySet()) {headers.put(entry.getKey(), HaiKangSignUtil.utf8ToIso88591(entry.getValue().toString()));}// 返回值JSONObject jsonObject = webclientService.requestByPostAddHeader(body, wgHttpXyPO.getHtHttpUrl().trim() + previewURLsApi, MediaType.APPLICATION_JSON, headers);}

 接口返回,这个接口没有返回监控点唯一标识,但是有监控点名称。根据监控点名称更新就行

6:vue3 视频播放, 下载视频web插件, 这个页面有点慢,要等一会才出来

  海康开放平台

 安装bin 里面的插件, 可以做成zip 放到服务器 提供下载

public 目录下, 新建一个 common目录,复制js 到 common目录

三个文件复制 

vue3 代码:公共模块: videoInit.vue

 采用 el-dialog, 打开播放, 关闭断开销毁。

<template><el-dialog:close-on-click-modal="false":close-on-press-escape="false":model-value="videoDialogVisible"append-to-body@close="cancel"@opened="handleDialogOpened":title="titleName":lock-scroll="false"width="85%"destroy-on-closeclass="video-dialog"center><div class="dialog-content"><!-- 视频窗口 --><div id="playWnd" ref="playWndRef" class="play-wnd"></div></div></el-dialog>
</template><script setup>
import {ref, onMounted, onUnmounted, nextTick} from 'vue'
import {debounce} from 'lodash-es'
import formUtils from "@/utils/formUtils"
import Type from "@/utils/typeEnum"const props = defineProps(['videoDialogVisible'])
const emit = defineEmits(["update:videoDialogVisible"])const titleName = ref('')
const oWebControl = ref(null)
const pubKey = ref('')
const playWndRef = ref(null)
const resizeObserver = ref(null)const secrt = ref('')
const k = ref('')
const host = ref('')
const shb = ref('')// 暴露方法
const init = (name, number, app, key, ip) => {titleName.value = nameshb.value = number  // 监控点唯一标识// 下面三个是 需要提供的,可以从后端返回,将appSecret 加密返回,前端解密 ,aessecrt.value = key  // appSecretk.value = app     // appIdhost.value = ip   // 连接ip initPlugin()}defineExpose({init})// 加载脚本
const loadScript = (src) => {return new Promise((resolve, reject) => {const script = document.createElement('script')script.src = srcscript.onload = resolvescript.onerror = rejectdocument.body.appendChild(script)})
}// 对话框打开后初始化
const handleDialogOpened = async () => {await nextTick()initResizeObserver()adjustPlayerSize()
}// 初始化ResizeObserver监听
const initResizeObserver = () => {// 先断开之前的观察if (resizeObserver.value) {resizeObserver.value.disconnect()}const dialogElement = document.querySelector('.video-dialog')if (dialogElement) {resizeObserver.value = new ResizeObserver(debounce(() => {adjustPlayerSize()}, 100))resizeObserver.value.observe(dialogElement)}
}// 调整播放器尺寸
const adjustPlayerSize = () => {if (!playWndRef.value) returnconst dialogBody = document.querySelector('.video-dialog .el-dialog__body')if (!dialogBody) return// 计算可用高度const dialogStyle = window.getComputedStyle(dialogBody)const paddingTop = parseFloat(dialogStyle.paddingTop)const paddingBottom = parseFloat(dialogStyle.paddingBottom)// 可用高度 = 对话框内容高度 - 内边距 - 按钮区域高度(50px)const availableHeight = dialogBody.clientHeight - paddingTop - paddingBottom - 50const width = playWndRef.value.clientWidthconst height = Math.max(600, availableHeight) // 设置最小高度300px// 更新DOM尺寸playWndRef.value.style.height = `${height}px`// 更新插件窗口尺寸if (oWebControl.value) {oWebControl.value.JS_Resize(width, height)setWndCover()}
}const initPlugin = async () => {await loadScript('/common/jquery-1.12.4.min.js')await loadScript('/common/jsencrypt.min.js')await loadScript('/common/web-control_1.2.7.min.js')oWebControl.value = new WebControl({szPluginContainer: "playWnd",                       // 指定容器idiServicePortStart: 15900,                           // 指定起止端口号,建议使用该值iServicePortEnd: 15900,szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11",   // 用于IE10使用ActiveX的clsidcbConnectSuccess: function () {                     // 创建WebControl实例成功oWebControl.value.JS_StartService("window", {         // WebControl实例创建成功后需要启动服务dllPath: "./VideoPluginConnect.dll"         // 值"./VideoPluginConnect.dll"写死}).then(function () {                           // 启动插件服务成功oWebControl.value.JS_SetWindowControlCallback({   // 设置消息回调cbIntegrationCallBack: cbIntegrationCallBack});const width = playWndRef.value?.clientWidth || 1000const height = playWndRef.value?.clientHeight || 600oWebControl.value.JS_CreateWnd("playWnd", width, height).then(function () { //JS_CreateWnd创建视频播放窗口,宽高可设定getPubKey(() => {initSettings()adjustPlayerSize()})});}, function () { // 启动插件服务失败});},cbConnectError: function () { // 创建WebControl实例失败// 没安装的时候这里也没进来,不生效formUtils.confirm(Type.WARNING,'插件初始化失败,请确认是否已安装插件;如果未安装,请先下载插件后进行安装!',() => window.open(`${location.protocol}//${location.host}/download/plugin.zip`))}});}const initSettings = () => {var appkey = k.value;                           //综合安防管理平台提供的appkey,必填var secret = setEncrypt(secrt.value);   //综合安防管理平台提供的secret,必填var ip = host.value;                           //综合安防管理平台IP地址,必填var playMode = 0;                                  //初始播放模式:0-预览,1-回放var port = 443;                                    //综合安防管理平台端口,若启用HTTPS协议,默认443var snapDir = "D:\\SnapDir";                       //抓图存储路径var videoDir = "D:\\VideoDir";                     //紧急录像或录像剪辑存储路径var layout = "1x1";                                //playMode指定模式的布局var enableHTTPS = 1;                               //是否启用HTTPS协议与综合安防管理平台交互,这里总是填1var encryptedFields = 'secret';					   //加密字段,默认加密领域为secretvar 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.value.JS_RequestInterface({funcName: "init",argument: JSON.stringify({appkey: appkey,                            //API网关提供的appkeysecret: secret,                            //API网关提供的secretip: 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) {adjustPlayerSize()startPreview()});
}// 获取公钥
const getPubKey = (callback) => {oWebControl.value.JS_RequestInterface({funcName: "getRSAPubKey",argument: JSON.stringify({keyLength: 1024})}).then(function (oData) {console.log(oData);if (oData.responseMsg.data) {pubKey.value = oData.responseMsg.data;callback()}})
}// RSA加密
const setEncrypt = (value) => {const encrypt = new JSEncrypt()encrypt.setPublicKey(pubKey.value)return encrypt.encrypt(value)
}// 消息回调
const cbIntegrationCallBack = (oData) => {
}// 开始预览
const startPreview = () => {if (!oWebControl.value) returnconst streamMode = 0const transMode = 1const gpuMode = 0const wndId = -1oWebControl.value.JS_RequestInterface({funcName: "startPreview",argument: JSON.stringify({cameraIndexCode: shb.value,streamMode: streamMode,transMode: transMode,gpuMode: gpuMode,wndId: wndId})})
}// 停止预览
const stopAllPreview = () => {oWebControl.value?.JS_RequestInterface({funcName: "stopAllPreview"})
}// 窗口裁剪
const setWndCover = () => {if (!oWebControl.value || !playWndRef.value) returnconst rect = playWndRef.value.getBoundingClientRect()const viewportWidth = window.innerWidthconst viewportHeight = window.innerHeightlet coverLeft = Math.max(0, -rect.left)let coverTop = Math.max(0, -rect.top)let coverRight = Math.max(0, rect.right - viewportWidth)let coverBottom = Math.max(0, rect.bottom - viewportHeight)const width = playWndRef.value.clientWidthconst height = playWndRef.value.clientHeightcoverLeft = Math.min(coverLeft, width)coverTop = Math.min(coverTop, height)coverRight = Math.min(coverRight, width)coverBottom = Math.min(coverBottom, height)oWebControl.value.JS_RepairPartWindow(0, 0, width + 1, height)if (coverLeft > 0) oWebControl.value.JS_CuttingPartWindow(0, 0, coverLeft, height)if (coverTop > 0) oWebControl.value.JS_CuttingPartWindow(0, 0, width + 1, coverTop)if (coverRight > 0) oWebControl.value.JS_CuttingPartWindow(width - coverRight, 0, coverRight, height)if (coverBottom > 0) oWebControl.value.JS_CuttingPartWindow(0, height - coverBottom, width, coverBottom)
}// 处理窗口大小变化
const handleResize = debounce(() => {adjustPlayerSize()
}, 200)// 清理资源
const stop = () => {if (resizeObserver.value) {resizeObserver.value.disconnect()resizeObserver.value = null}window.removeEventListener('resize', handleResize)if (oWebControl.value) {stopAllPreview()oWebControl.value.JS_HideWnd()oWebControl.value.JS_DestroyWnd()oWebControl.value.JS_Disconnect().then(() => console.log("插件连接已断开"),() => console.log("插件断开连接失败"))oWebControl.value = null}
}// 关闭对话框
const cancel = () => {stop()emit("update:videoDialogVisible", false)
}// 生命周期
onMounted(() => {window.addEventListener('resize', handleResize)
})onUnmounted(() => {stop()
})
</script><style scoped>
.video-dialog {top: 5vh;
}.video-dialog :deep(.el-dialog) {display: flex;flex-direction: column;max-height: 90vh;
}.video-dialog :deep(.el-dialog__body) {flex: 1;padding: 20px;overflow: hidden;display: flex;flex-direction: column;
}.dialog-content {flex: 1;display: flex;flex-direction: column;height: 80vh;
}.play-wnd {flex: 1;min-height: 300px;border: 1px solid #ebeef5;border-radius: 4px;margin-top: 10px;background-color: #000;
}
</style>

调用播放: 

 导入

配置:

事件点击播放

效果:

http://www.dtcms.com/a/299237.html

相关文章:

  • Kafka——Java消费者是如何管理TCP连接的?
  • JavaWeb01——基础标签及样式(黑马视频笔记)
  • [2025CVPR:图象合成、生成方向]WF-VAE:通过小波驱动的能量流增强视频 VAE 的潜在视频扩散模型
  • SSRF_XXE_RCE_反序列化学习
  • 「iOS」——内存五大分区
  • C++核心编程学习--对象特性--对象模型和this指针
  • 旧设备HMI焕新陷阱:操作习惯继承与智能化升级的平衡点把控
  • ​机器学习从入门到实践:算法、特征工程与模型评估详解
  • pose调研
  • # JsSIP 从入门到实战:构建你的第一个 Web 电话
  • Vue》》@ 用法
  • 期货资管软件定制开发流程
  • Matlab学习笔记:自定义函数
  • Vue 3 与 Element Plus 中的 /deep/ 选择器问题
  • 如果在分支A上修改了内容,想要提交更新内容的话,如何与develop上的主分支的最新的代码拉齐
  • linux线程概念和控制
  • Node.js特训专栏-实战进阶:19.dotenv环境变量管理
  • 零基础学习性能测试第三章:jmeter构建性能业务场景
  • [C/C++内存安全]_[中级]_[再次探讨避免悬垂指针的方法和检测空指针的方法]
  • 《从零开始学 JSSIP:JavaScript 实时通信开发实战》
  • QT核心————信号槽
  • Qt 多线程编程最佳实践
  • 《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——6. 传统算法实战:用OpenCV测量螺丝尺寸
  • 基于粒子群算法优化高斯过程回归(PSO-GPR)的多输出回归
  • 数据科学与大数据技术专业的核心课程体系及发展路径全解析
  • Jenkins运行pytest时指令失效的原因以及解决办法
  • Java集合体系详解
  • docker常用命令集(3)
  • 【守护】同为科技SPD:AP-20D/4P产品解析
  • C语言--青蛙跳台阶问题