跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

SuperMap iClient3D for WebGL 集成大华 RTSP 与 WebSocket 视频投放方案

针对 SuperMap iClient3D for WebGL 无法直接加载大华私有 WebSocket 协议视频流的问题,通过替换官方依赖为大华提供的 WSPlayer 及 PlaySDKInterface 文件解决。修改代码以适配私有协议解析,实现将 RTSP 流投影至三维模型建筑上的功能。

怪力乱神发布于 2026/3/15更新于 2026/8/550 浏览
SuperMap iClient3D for WebGL 集成大华 RTSP 与 WebSocket 视频投放方案

前言

部分项目需求需使用第三方 RTSP 流和 WebSocket 地址进行视频投放。若直接使用示例代码更改视频流地址及 WebSocket 地址,往往无法加载出对应的流视频。

原因分析

以大华设备为例,主要原因是大华的 WebSocket 使用了私有子协议,而平台默认解析依赖基于明确的 WebSocket Streaming Protocol(公开 INIT/JOIN/WRAP 命令的子协议)。因此,标准解析器无法正确识别大华的 WebSocket 地址。

解决方案

大华官方提供了专用的依赖库用于解析其 WebSocket 地址。开发者需自行获取该依赖文件并替换原有解析逻辑。

实现步骤

1. 替换依赖文件

将代码中解析公开 WebSocket 子协议的 streamedian.js 替换为大华提供的 WSPlayer.js 和 PlaySDKInterface.js 文件。

引入资源代码如下:

<link href="../../Build/SuperMap3D/Widgets/widgets.css" rel="stylesheet">
<link href="./css/pretty.css" rel="stylesheet">
<link rel="stylesheet" href="./lib/WSPlayer/player.css">
<link rel="stylesheet" href="./lib/WSPlayer/window.division.css">
<script src="./js/echarts.min.js"></script>
<script src="js/polyfill.js"></script>
<script src="./js/jquery.min.js"></script>
<script src="./js/slider.js"></script>
<script type="text/javascript" src="../../Build/SuperMap3D/SuperMap3D.js"></script>
<script src="./js/config.js"></script>
<script type="text/javascript" src="./lib/WSPlayer/PlaySDKInterface.js"></script>
<script type="text/javascript" src="./lib/WSPlayer/WSPlayer.js"></script>

2. 修改 WebSocket 解析及视频播放逻辑

核心逻辑在于初始化 PlayerManager 并适配大华的私有协议接口。

<body>
<div id="Container"></div>
<div id="toolbar" class="param-container tool-bar">
    <button type="button" id="active" class="button black">重新投放</button>
    <button type="button" id="clear" class="button black">清除投放</button>
    <br />
    <label><input type="checkbox" id="visibleLine" checked> 显示投放参考线</label>
    <br><br>
    <div style="margin: 8px 0;"><b>宽度 (°)</b><br><input type="number" id="horizontal" min="1" step="0.1" value="20"></div>
    <div style="margin: 8px 0;"><b>高度 (°)</b><br><input type="number" id="vertical" min="1" step="0.1" value="10"></div>
    <div style="margin: 8px 0;"><b>投影距离 (m)</b><br><input type="number" id="distance" min="10" step="10" value="350"></div>
    <div style="margin-top:15px;"><small>WS URL:</small><br><input id="real-ws" value="ws://ip:40088" style="width:220px;"></div>
    <div style="margin-top:8px;"><small>RTSP:</small><br><input id="real-rtsp" value="rtsp://ip:40088/dss/monitor/param/cameraid=1003919%240%26substream=1" style="width:220px;"></div>
</div>
<div style="position: absolute; right: 20px; top: 120px;">
    <div id="msChart" style="width: 300px; height: 150px; background:rgba(0,0,0,0.4);"></div>
</div>
<div style="position: absolute; right: 20px; top: 320px;">
    <div id="fpsChart" style="width: 300px; height: 150px; background:rgba(0,0,0,0.4);"></div>
</div>
<div id="ws-real-player" style="width:720px; height:480px; visibility:hidden; position:absolute; top:-9999px; left:-9999px;"></div>
<script type="module">
import PlayerManager from './icc/PlayerManager.js';

function onload(SuperMap3D) {
    const EngineType = getEngineType();
    const viewer = new SuperMap3D.Viewer('Container', { contextOptions: { contextType: Number(EngineType) } });
    viewer.scenePromise.then(scene => init(SuperMap3D, scene, viewer));
}

function init(SuperMap3D, scene, viewer) {
    viewer.resolutionScale = window.devicePixelRatio;
    addBaseMap(viewer);

    // 性能监控
    const msChart = echarts.init(document.getElementById('msChart'));
    const fpsChart = echarts.init(document.getElementById('fpsChart'));
    let msData = [], fpsData = [], step = 0;
    scene.debugShowFramesPerSecond = true;
    scene.shadowMap.darkness = 1.275;
    scene.hdrEnabled = false;
    scene.sun.show = true;

    setInterval(() => {
        if (step <= 6 && scene.debugShowFramesPerSecond) {
            const msEl = document.querySelector(".supermap3d-performanceDisplay-ms");
            const fpsEl = document.querySelector(".supermap3d-performanceDisplay-fps");
            if (msEl && fpsEl) {
                msData[step] = parseFloat(msEl.innerHTML.replace(/[^0-9.]/g, ''));
                fpsData[step] = parseFloat(fpsEl.innerHTML.replace(/[^0-9.]/g, ''));
                step++;
            }
        } else {
            step = 0;
        }
    }, 200);

    setInterval(() => {
        msChart.setOption({ title: { subtext: 'MS', left: 'right', subtextStyle: { color: '#eee' } }, xAxis: { type: 'category', data: Array(7).fill(''), axisLabel: { color: '#ccc' } }, yAxis: { type: 'value', axisLabel: { color: '#ccc' } }, series: [{ data: msData, type: 'line' }], grid: { left: '5%', right: '5%', top: '25%', bottom: '5%', containLabel: true } });
        fpsChart.setOption({ title: { subtext: 'FPS', left: 'right', subtextStyle: { color: '#eee' } }, xAxis: { type: 'category', data: Array(7).fill(''), axisLabel: { color: '#ccc' } }, yAxis: { type: 'value', axisLabel: { color: '#ccc' } }, series: [{ data: fpsData, type: 'line', areaStyle: {} }], grid: { left: '5%', right: '5%', top: '25%', bottom: '5%', containLabel: true } });
    }, 1000);

    if (!scene.pickPositionSupported) alert('当前浏览器/设备不支持深度拾取,投影功能受限');

    // 加载模型
    SuperMap3D.when.all([
        scene.addS3MTilesLayerByScp(URL_CONFIG.SCP_CBD_BUILD, { name: 'build' }),
        scene.addS3MTilesLayerByScp(URL_CONFIG.SCP_CBD_GROUND1, { name: 'ground1' }),
        scene.addS3MTilesLayerByScp(URL_CONFIG.SCP_CBD_LAKE, { name: 'lake' }),
        scene.addS3MTilesLayerByScp(URL_CONFIG.SCP_CBD_TREE, { name: 'tree' }),
        scene.addS3MTilesLayerByScp(URL_CONFIG.SCP_CBD_ROAD, { name: 'road' }),
        scene.addS3MTilesLayerByScp(URL_CONFIG.SCP_CBD_BRIDGE, { name: 'bridge' })
    ], layers => {
        scene.camera.setView({ destination: SuperMap3D.Cartesian3.fromDegrees(116.4486, 39.9092, 91.3293), orientation: { heading: 3.179304500963121, pitch: -0.46239072362282485, roll: 6.283185307179583 } });
        layers.forEach(l => l.selectEnabled = false);
        const perfEl = document.querySelector(".supermap3d-performanceDisplay");
        if (perfEl) perfEl.style.display = "none";

        // 核心:视频投影对象
        const projectionImage = new SuperMap3D.ProjectionImage(scene);
        const targetLon = 116.4486895458964;
        const targetLat = 39.907729088565965;
        const targetHeight = 32.51768911137742;

        // WSPlayer 初始化
        const realPlayer = new PlayerManager({
            el: "ws-real-player",
            type: 'real',
            maxNum: 1,
            num: 1,
            showControl: false,
            prefixUrl: 'lib',
            receiveMessageFromWSPlayer: (method, data, err) => {
                if (method === 'initializationCompleted') {
                    realPlayer.setPlayerAdapter("stretching");
                    playVideoAndProject();
                } else if (method === 'realSuccess') {
                    console.log("视频流播放成功,开始构建投影");
                    const videoEl = document.querySelector('#ws-real-player video');
                    if (videoEl) {
                        projectionImage.setImage({ video: videoEl });
                        projectionImage.build();
                    }
                } else if (method === 'realError') {
                    console.error("视频流失败", err);
                }
            }
        });

        function projectToFixedPoint() {
            const cameraPos = scene.camera.positionCartographic;
            const viewPos = [SuperMap3D.Math.toDegrees(cameraPos.longitude), SuperMap3D.Math.toDegrees(cameraPos.latitude), cameraPos.height];
            projectionImage.viewPosition = viewPos;
            projectionImage.horizontalFov = Number($("#horizontal").val());
            projectionImage.verticalFov = Number($("#vertical").val());
            projectionImage.distance = Number($("#distance").val());
            projectionImage.hintLineVisible = $("#visibleLine").is(":checked");
            projectionImage.setDistDirByPoint([targetLon, targetLat, targetHeight]);
        }

        function playVideoAndProject() {
            const wsURL = $("#real-ws").val();
            const rtspURL = $("#real-rtsp").val();
            realPlayer.realByUrl({ rtspURL, wsURL, selectIndex: 0, channelId: '', streamType: 1, playerAdapter: 'stretching' });
            projectToFixedPoint();
        }

        setTimeout(() => playVideoAndProject(), 1500);

        $("#active").click(() => { realPlayer.close(0); setTimeout(playVideoAndProject, 300); });
        $("#clear").click(() => { realPlayer.close(0); projectionImage.destroy && projectionImage.destroy(); });
        $("#horizontal,#vertical,#distance").on("input", projectToFixedPoint);
        $("#visibleLine").on("change", projectToFixedPoint);
    });
}

if (typeof SuperMap3D !== 'undefined') {
    window.startupCalled = true;
    onload(SuperMap3D);
}
</script>
</body>

效果展示

完成上述配置后,视频流可成功投影至三维模型建筑上。

视频投放效果

目录

  1. 前言
  2. 原因分析
  3. 解决方案
  4. 实现步骤
  5. 1. 替换依赖文件
  6. 2. 修改 WebSocket 解析及视频播放逻辑
  7. 效果展示
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于 Leaflet Trackplayer 的高速公路轨迹 WebGIS 可视化实战
  • OpenClaw 接入飞书机器人与 Kimi2.5 配置指南
  • 基于 Kubernetes 的 Apache IoTDB 集群实战与 TimechoDB 增强特性解析
  • Llama 3.1 开源模型快速部署指南
  • 滑动窗口算法实战:串联所有单词与最小覆盖子串
  • VR 音游音符轨道系统开发实录与原理解析
  • NeurIPS 2024 清华与加州理工提出强化自训练方法 ReST-MCTS*
  • Llama 2 ONNX 模型快速部署指南
  • 近端策略优化算法 (PPO) 详解与代码实现
  • SpringBoot 整合 Langchain4j RAG 技术深度使用解析
  • 高级 RAG 架构解析:提升大模型回答的准确性与可靠性
  • CentOS 系统定时执行 Python 邮件发送任务的五种方案
  • 1060 爱丁顿数 Python 实现与解析
  • Java JDK 下载与安装配置
  • CNN 经典架构演进与 PyTorch 实战指南
  • SLAM 与室内绝对定位融合方案解决机器人导航漂移问题
  • Java 类加载及 new 对象的过程
  • C++ 函数指针与回调函数深度解析
  • AI 产品经理面试指南:核心产品问题与能力解析
  • 字符串算法实战:公共前缀、回文子串与运算

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online