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

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

海康开放平台

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

接口详情:

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

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

java 复制代码
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";

代码:

java 复制代码
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";

java 复制代码
 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, 打开播放, 关闭断开销毁。

java 复制代码
<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-close
            class="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 = name
    shb.value = number  // 监控点唯一标识
    // 下面三个是 需要提供的,可以从后端返回,将appSecret 加密返回,前端解密 ,aes
    secrt.value = key  // appSecret
    k.value = app     // appId
    host.value = ip   // 连接ip 

    initPlugin()

}

defineExpose({init})


// 加载脚本
const loadScript = (src) => {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = src
        script.onload = resolve
        script.onerror = reject
        document.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) return

    const 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 - 50
    const width = playWndRef.value.clientWidth
    const 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",                       // 指定容器id
        iServicePortStart: 15900,                           // 指定起止端口号,建议使用该值
        iServicePortEnd: 15900,
        szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11",   // 用于IE10使用ActiveX的clsid
        cbConnectSuccess: 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 || 1000
                const height = playWndRef.value?.clientHeight || 600

                oWebControl.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协议,默认443
    var snapDir = "D:\\SnapDir";                       //抓图存储路径
    var videoDir = "D:\\VideoDir";                     //紧急录像或录像剪辑存储路径
    var layout = "1x1";                                //playMode指定模式的布局
    var enableHTTPS = 1;                               //是否启用HTTPS协议与综合安防管理平台交互,这里总是填1
    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.value.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) {
        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) return

    const streamMode = 0
    const transMode = 1
    const gpuMode = 0
    const wndId = -1

    oWebControl.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) return

    const rect = playWndRef.value.getBoundingClientRect()
    const viewportWidth = window.innerWidth
    const viewportHeight = window.innerHeight

    let 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.clientWidth
    const height = playWndRef.value.clientHeight

    coverLeft = 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>

调用播放:

导入

配置:

事件点击播放

效果:

相关推荐
uzong2 小时前
技术故障复盘模版
后端
GetcharZp2 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程2 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研2 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
一只爱撸猫的程序猿3 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋4 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack4 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt