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

调用播放: 

 导入

配置:

事件点击播放

效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值