SuperMap iClient3D for WebGL如何使用大华的RTSP流和WebSocket地址做视频投放
前言
在有些项目的需求中会有遇到使用其他的RTSP流和WebSocket地址做视频投放的情况,会发现直接使用示例代码更改 视频流地址以及WebSocket地址后无法加载出对应的流视频.那么为什么呢?
原因
此处以大华为例,最大的原因是大华的WebSocket使用的子协议是私有的协议而咱们官网的解析WebSocket地址的依赖是基于明确的WSP 协议(WebSocket Streaming Protocol),有公开的 INIT/JOIN/WRAP 命令的WebSocket 子协议做的解析.所以无法正确解析大华的WebSocket 地址.
解决方法
大华的官方提供了解析大华WebSocket 地址的依赖.可自行前往大华官方下载也可下载下面网盘链接中的依赖文件使用: https://pan.baidu.com/s/1mo0D5jdyXZl6eTuJrpLiFA?pwd=me9g。
详细代码以及流程
可以看到上述网盘链接中有大华提供的加载示例代码。我们现在需要的就是将其解析方式搬到咱们SuperMap iClient3D for WebGL的代码中去,做视频流的解析,再去投放到建筑上。
替换依赖
将代码中解析公开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>替换WebSocket 解析以及视频播放代码
这里就直接贴代码了,不做过多赘述,可自行差查看代码理解逻辑
<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';functiononload(SuperMap3D){ const EngineType =getEngineType(); const viewer =newSuperMap3D.Viewer('Container',{ contextOptions:{ contextType:Number(EngineType)}}); viewer.scenePromise.then(scene =>{init(SuperMap3D, scene, viewer);});}functioninit(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 =newSuperMap3D.ProjectionImage(scene);// 固定投放目标点(经纬高) const targetLon =116.4486895458964; const targetLat =39.907729088565965; const targetHeight =32.51768911137742;// ── WSPlayer 初始化 ─────────────────────────────────── const realPlayer =newPlayerManager({ 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();}elseif(method ==='realSuccess'){ console.log("视频流播放成功,开始构建投影"); const videoEl = document.querySelector('#ws-real-player video');if(videoEl){ projectionImage.setImage({ video: videoEl }); projectionImage.build();}}elseif(method ==='realError'){ console.error("视频流失败", err);}}});// 投放核心逻辑functionprojectToFixedPoint(){ 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]);}// 播放视频并投影functionplayVideoAndProject(){ const wsURL = $("#real-ws").val(); const rtspURL = $("#real-rtsp").val(); realPlayer.realByUrl({ rtspURL, wsURL, selectIndex:0, channelId:'', streamType:1, playerAdapter:'stretching'});// 立即设置投影参数(视频可能还未ready,ready后会build)projectToFixedPoint();}// 页面加载完成后自动执行一次投放setTimeout(()=>{playVideoAndProject();},1500);// 延迟1.5秒,确保场景和播放器基本就绪// 按钮:手动重新投放(调试用) $("#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>最终加载效果
最终完成之后的效果如下图,可以看到视频流已经成功投放到模型建筑上
