☰
Unity WebGL 播放海康监控流:AVProVideo 实战 Demo 与避坑指南
2026/10/16 3:20:35 网站建设 项目流程

简介:这份资源面向需要在Unity WebGL环境下接入海康监控设备实时画面的开发者,核心解决浏览器端播放RTSP/RTMP流并规避AVProVideo水印的问题。作者重写了Http请求模块,并附带无水印版AVProVideo的WebGL发布设置指导配置文件,只需手动修改Http请求配表中的字段值,即可正常发布WebGL运行,本地部署采用nginx,适合具备一定Unity与C#基础的流媒体开发人员。压缩包共701个文件,约80.17MB,包含99个cs脚本、30个asset资源、28个so配置、26个dll动态库、25个shader着色器,以及aar、jar、mat、mp4、png等素材与依赖,覆盖播放器内核、渲染与平台适配层。目前已有1213人学习下载。通过该demo可掌握WebGL下视频流请求改写、AVProVideo配置与发布流程,并参考nginx部署思路完成本地联调,快速搭建可运行的监控预览原型。

1. 从一次监控画面接入翻车说起:这个 Demo 到底解决了什么问题

去年帮一个做智慧园区的团队看现场,他们用 Unity 做了一套三维可视化大屏,模型、灯光、粒子都调得挺漂亮,结果卡在最后一步——海康摄像头的实时画面怎么都贴不到 WebGL 页面的模型上。VideoPlayer 组件在编辑器里跑得好好的,一发布到 WebGL 就黑屏,控制台报的错还都是些看不懂的编解码问题。折腾了三天,最后换了个思路才跑通。

这个 Demo 源码干的就是这件事:在 Unity WebGL 平台下,用 AVProVideo 插件播放海康设备监控的实时画面。它不是一个完整的业务系统,而是一个最小可运行的技术验证工程——把「海康 RTSP 流 → 转码 → AVProVideo 播放 → WebGL 渲染」这条链路完整跑通,让你能直接拿过去改。适合正在做安防可视化、数字孪生大屏、工业监控面板的 Unity 开发者,尤其是被 WebGL 视频播放坑过的人。源码里包含场景文件、播放控制脚本、材质配置和 WebGL 模板,拿到手改个流地址就能看到画面。

2. 技术选型拆解:为什么是 AVProVideo 而不是原生 VideoPlayer

2.1 WebGL 平台视频播放的硬约束

Unity 原生 VideoPlayer 在 WebGL 下的限制非常多。它依赖浏览器的 HTML5 video 标签,而浏览器对视频编码的支持是固定的——H.264 基本没问题,但海康设备默认输出的 RTSP 流用的是 H.265 或者私有封装格式,浏览器根本不认。更麻烦的是,VideoPlayer 在 WebGL 下不支持直接播放 RTSP 协议,你没法把rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101这样的地址直接丢给它。

AVProVideo 的思路不一样。它在 WebGL 平台下走的是 HTML5 video 元素 + MSE(Media Source Extensions)的路线,但做了大量兼容层封装。它支持 HLS(.m3u8)和 DASH 这类浏览器友好的流格式,也支持直接指定视频 URL。关键点在于:AVProVideo 帮你处理了 WebGL 下视频纹理的上传和渲染,你不需要自己写 WebGL 的 video texture 绑定逻辑。

注意:AVProVideo 在 WebGL 下依然不能直接播 RTSP,必须先把 RTSP 转成 HLS 或 WebRTC 等浏览器可播的格式。这个 Demo 的架构里,转码环节是独立于 Unity 工程之外的。

2.2 海康设备流的接入方式对比

海康摄像头常见的取流方式有三种,在这个 Demo 的场景下各有取舍:

取流方式协议延迟WebGL 兼容性适用场景
RTSP 直取RTSP over TCP低(200-500ms)不支持本地客户端、桌面端
HLS 切片HTTP + m3u8高(3-10s)原生支持WebGL、对延迟不敏感
WebRTC 推流WebRTC极低(<200ms)需额外信令实时性要求高的 WebGL

这个 Demo 采用的是 HLS 方案。原因很直接:AVProVideo 在 WebGL 下对 HLS 的支持最成熟,不需要引入额外的 WebRTC 信令服务器,部署成本最低。代价就是延迟偏高,大概在 3 到 5 秒左右。如果你的场景是实时操控,这个延迟可能不够;如果只是监控预览,完全够用。

2.3 AVProVideo 的核心 API 与播放控制

Demo 里控制播放的核心脚本挂在场景中的 MediaPlayer 对象上。下面这段代码是播放控制逻辑的简化版,保留了关键调用:

using RenderHeads.Media.AVProVideo; using UnityEngine; public class HikvisionStreamPlayer : MonoBehaviour { // AVProVideo 的 MediaPlayer 组件引用 public MediaPlayer mediaPlayer; // 海康流转换后的 HLS 地址 public string streamUrl = "http://192.168.1.100:8080/live/stream.m3u8"; void Start() { if (mediaPlayer == null) { Debug.LogError("MediaPlayer 未赋值"); return; } // 打开媒体,第二个参数是自动播放 mediaPlayer.OpenMedia(new MediaPath(streamUrl, MediaPathType.AbsolutePathOrURL), true); } void Update() { // 每帧检查播放状态,处理缓冲和错误 if (mediaPlayer.Info != null) { if (mediaPlayer.Info.GetPlaybackState() == MediaPlayer.PlaybackState.Playing) { // 正常播放中,可以在这里做画面亮度、对比度调节 } else if (mediaPlayer.Info.GetPlaybackState() == MediaPlayer.PlaybackState.Buffering) { // 缓冲中,UI 上可以显示加载提示 } } } // 切换摄像头流地址 public void SwitchStream(string newUrl) { streamUrl = newUrl; mediaPlayer.OpenMedia(new MediaPath(streamUrl, MediaPathType.AbsolutePathOrURL), true); } }

这段代码的逻辑很直白:OpenMedia负责打开流地址,第二个参数true表示打开后自动播放。Update里通过mediaPlayer.Info.GetPlaybackState()拿到当前播放状态,你可以根据状态做 UI 反馈。SwitchStream是给多摄像头切换用的,直接换 URL 重新打开就行。

参数说明:MediaPath的第二个参数MediaPathType.AbsolutePathOrURL表示传入的是完整 URL,不是相对路径。如果你把流地址放在 StreamingAssets 下,要用RelativeToStreamingAssetsFolder。OpenMedia的第二个参数是autoPlay,WebGL 下浏览器有自动播放策略限制,可能需要用户先点一下页面才能触发播放。

3. 工程结构拆解:从场景到 WebGL 模板的完整链路

3.1 目录结构与关键文件说明

这个 Demo 的工程结构不复杂,但每个文件都有明确职责。下面是我整理的核心目录树:

WebGLHikvisionDemo/ ├── Assets/ │ ├── Scenes/ │ │ └── MainScene.unity # 主场景,包含摄像机、平面、UI │ ├── Scripts/ │ │ ├── HikvisionStreamPlayer.cs # 播放控制核心脚本 │ │ └── StreamSwitcher.cs # 多路流切换逻辑 │ ├── Materials/ │ │ └── VideoMaterial.mat # 用于显示视频的材质 │ ├── Plugins/ │ │ └── AVProVideo/ # AVProVideo 插件目录 │ └── WebGLTemplates/ │ └── CustomTemplate/ │ └── index.html # WebGL 页面模板 ├── ProjectSettings/ │ └── ProjectSettings.asset # 项目设置,含 WebGL 配置 └── Packages/ └── manifest.json # 包依赖清单

MainScene.unity里有一个 Quad 或 Plane 对象,挂载了VideoMaterial,材质上绑定了 AVProVideo 的ApplyToMaterial组件。摄像机正对这个平面,UI 层有播放/暂停按钮和流地址输入框。StreamSwitcher.cs负责在多个摄像头之间切换,逻辑就是换 URL 重新OpenMedia。

3.2 场景搭建与材质绑定步骤

如果你要自己从头搭这个场景,按下面几步走:

第一步,在 Hierarchy 里创建一个 3D Object → Quad,调整 Scale 为 (16, 9, 1),这样比例接近监控画面的 16:9。

第二步,在 Project 窗口右键创建 Material,命名为VideoMaterial,Shader 选Unlit/Texture。把这个材质拖到 Quad 上。

第三步,选中 Quad,在 Inspector 里 Add Component → AVProVideo → Apply To Material。把VideoMaterial拖到Material字段,把场景里的MediaPlayer拖到Media Player字段。

第四步,在场景里创建空物体,命名为MediaPlayer,Add Component → AVProVideo → Media Player。在Media Player组件的Global Settings里,把Video Location设为URL,Path填你的 HLS 地址。

第五步,把HikvisionStreamPlayer.cs挂到任意物体上,把MediaPlayer拖到脚本的mediaPlayer字段。

提示:WebGL 平台下,MediaPlayer的Platform Options里要勾选WebGL相关的选项,特别是Use WebGL Video和Auto Rotate这类设置,具体取决于 AVProVideo 的版本。

3.3 WebGL 发布配置与模板修改

WebGL 发布有几个关键设置,漏一个就可能黑屏:

在File → Build Settings里切换到 WebGL 平台,点击Player Settings。在Other Settings里,Color Space建议设为Gamma,Linear在某些浏览器下会导致视频颜色发灰。Api Compatibility Level设为.NET Standard 2.1或.NET 4.x,取决于 AVProVideo 版本要求。

在Publishing Settings里,Compression Format选Disabled或Gzip。如果选Brotli,某些服务器不支持,会导致加载失败。Decompression Fallback建议勾上,兼容性更好。

WebGL 模板的index.html里,需要确保 video 元素能被正确创建。AVProVideo 会在运行时动态插入 video 标签,你不需要手动写。但如果你用了自定义模板,要保证<body>里有足够的空间,并且没有 CSS 把 video 隐藏了。

<!-- WebGLTemplates/CustomTemplate/index.html 关键片段 --> <canvas id="unity-canvas" width="960" height="600"></canvas> <script> // Unity 加载器配置 var canvas = document.querySelector("#unity-canvas"); var buildUrl = "Build"; var loaderUrl = buildUrl + "/WebGLHikvisionDemo.loader.js"; var config = { dataUrl: buildUrl + "/WebGLHikvisionDemo.data", frameworkUrl: buildUrl + "/WebGLHikvisionDemo.framework.js", codeUrl: buildUrl + "/WebGLHikvisionDemo.wasm", streamingAssetsUrl: "StreamingAssets", companyName: "DefaultCompany", productName: "WebGLHikvisionDemo", productVersion: "0.1" }; // 创建 Unity 实例 var script = document.createElement("script"); script.src = loaderUrl; script.onload = () => { createUnityInstance(canvas, config, (progress) => { // 加载进度回调 }).then((unityInstance) => { // 加载完成 }); }; document.body.appendChild(script); </script>

这段 HTML 是 Unity WebGL 的标准加载模板,关键点是streamingAssetsUrl指向StreamingAssets目录,AVProVideo 的配置文件如果放在这里,需要确保路径正确。createUnityInstance返回的unityInstance可以用来和 Unity 侧通信,比如发送流地址切换指令。

4. 避坑与排查:WebGL 视频播放的五个血泪教训

4.1 黑屏但控制台无报错

现象:页面加载完成,Unity 场景正常渲染,但视频平面是纯黑,控制台没有明显错误。

原因:最常见的是浏览器自动播放策略拦截。Chrome 要求用户有交互行为(点击、触摸)后才能播放带声音的视频。AVProVideo 默认可能带音频轨道,被浏览器拦截了。

解决:在MediaPlayer的Audio设置里把Volume设为 0,或者勾选Mute。然后在页面上加一个「点击播放」的按钮,用户点击后再调用OpenMedia。另一个可能是 HLS 地址跨域,检查服务器是否返回了Access-Control-Allow-Origin: *。

4.2 画面卡顿、延迟越来越大

现象:刚开始播放正常,几分钟后延迟累积到十几秒,画面一卡一卡的。

原因:HLS 切片方案本身延迟就高,如果转码服务器的切片时长设得太大(比如 10 秒一片),延迟会非常明显。另外,WebGL 下视频解码走的是浏览器,如果分辨率太高(4K),浏览器解码压力大,也会卡。

解决:把 HLS 切片时长调到 1 到 2 秒,GOP 对齐。分辨率降到 1080p 或 720p。在 AVProVideo 的MediaPlayer里开启Buffer优化,适当增大缓冲区。如果还是卡,考虑换 WebRTC 方案。

4.3 发布后提示「Uncaught RuntimeError: memory access out of bounds」

现象:WebGL 构建加载到一半报内存越界错误,页面崩溃。

原因:Unity WebGL 默认内存池可能不够用,AVProVideo 在解码视频时需要额外内存。特别是你同时播放多路流的时候。

解决:在Player Settings → Publishing Settings里把Memory Size从默认的 256MB 调到 512MB 或 1024MB。注意不要超过浏览器限制,Chrome 单标签页大概能用到 2GB 左右。

4.4 流地址在编辑器里能播,WebGL 里不能播

现象:Unity 编辑器里填同样的 HLS 地址,播放正常;发布到 WebGL 后,一直缓冲或直接报错。

原因:编辑器下 AVProVideo 走的是原生解码路径,WebGL 下走的是浏览器解码路径,两者支持的编码格式不一样。编辑器能解的 H.265,浏览器不一定能解。

解决:确保转码服务器输出的是 H.264 编码的 HLS。用ffprobe检查流信息,确认Video Codec是h264。如果是hevc,需要重新转码。

4.5 多路流切换时画面残留或崩溃

现象:从摄像头 A 切到摄像头 B,画面没有更新,或者直接卡死。

原因:OpenMedia是异步操作,前一路流还没完全关闭就打开新流,导致资源竞争。

解决:在SwitchStream里加一个延迟或状态检查,等mediaPlayer.Info.GetPlaybackState()变成Stopped或Error后再打开新流。或者先调用mediaPlayer.CloseMedia(),等一帧后再OpenMedia。

public void SwitchStreamSafe(string newUrl) { StartCoroutine(SwitchRoutine(newUrl)); } private IEnumerator SwitchRoutine(string newUrl) { // 先关闭当前流 mediaPlayer.CloseMedia(); // 等待一帧,确保资源释放 yield return new WaitForEndOfFrame(); // 再打开新流 mediaPlayer.OpenMedia(new MediaPath(newUrl, MediaPathType.AbsolutePathOrURL), true); }

这段协程的逻辑是:先CloseMedia释放当前解码资源,等一帧让 AVProVideo 内部状态机完成清理,再打开新流。不加这个等待,快速切换时很容易触发空引用或内存越界。

5. 二次开发改造:从 Demo 到可交付项目的三个关键改动

5.1 把流地址配置化,别硬编码

Demo 里流地址是写死在脚本里的,实际项目肯定不能这样。我一般会做一个StreamConfig的 ScriptableObject,把摄像头列表、名称、URL 都配进去,运行时动态加载。

using UnityEngine; [CreateAssetMenu(fileName = "StreamConfig", menuName = "Hikvision/StreamConfig")] public class StreamConfig : ScriptableObject { [System.Serializable] public class CameraStream { public string cameraName; // 摄像头名称 public string streamUrl; // HLS 流地址 public bool isMainStream; // 是否主码流 } public CameraStream[] streams; }

在 Unity 里右键Create → Hikvision → StreamConfig创建配置文件,把摄像头信息填进去。运行时用Resources.Load<StreamConfig>("StreamConfig")加载,或者通过 Addressables 远程加载。这样改流地址不用重新打包 WebGL,改配置文件就行。

5.2 加一层播放状态回调,别在 Update 里轮询

Demo 里用Update每帧检查播放状态,实际项目里这样写性能不好,而且逻辑分散。AVProVideo 提供了事件回调,用事件驱动更干净。

void OnEnable() { mediaPlayer.Events.AddListener(OnMediaPlayerEvent); } void OnDisable() { mediaPlayer.Events.RemoveListener(OnMediaPlayerEvent); } private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode) { switch (et) { case MediaPlayerEvent.EventType.ReadyToPlay: // 流已就绪,可以开始播放 break; case MediaPlayerEvent.EventType.Started: // 播放开始 break; case MediaPlayerEvent.EventType.FirstFrameReady: // 第一帧渲染完成,可以隐藏加载动画 break; case MediaPlayerEvent.EventType.Error: // 播放出错,根据 errorCode 做重试或提示 Debug.LogError($"播放错误: {errorCode}"); break; } }

MediaPlayerEvent.EventType覆盖了从打开到播放到出错的完整生命周期。FirstFrameReady特别有用,你可以用它来隐藏 loading 动画,用户体验会好很多。Error事件里可以根据errorCode决定是重试还是提示用户检查网络。

5.3 多路流同屏的性能取舍

如果你要做 4 路或 9 路同屏,WebGL 下性能压力很大。我的经验是:超过 4 路就别用 HLS 了,延迟和 CPU 都扛不住。4 路以内,把分辨率压到 720p,帧率限制在 15fps,浏览器还能跑。

另一个技巧是用RenderTexture做画面池化。不要每个摄像头都创建一个MediaPlayer,而是用 2 到 3 个MediaPlayer轮询播放,切换时只换 URL。这样内存占用可控,但切换会有短暂黑屏。

// 画面池化示例:两个 MediaPlayer 轮询 public MediaPlayer[] playerPool; private int currentIndex = 0; public void PlayNext(string url) { var player = playerPool[currentIndex]; player.OpenMedia(new MediaPath(url, MediaPathType.AbsolutePathOrURL), true); currentIndex = (currentIndex + 1) % playerPool.Length; }

这个方案适合轮播场景,比如大屏上每隔 10 秒切换一个摄像头。如果是固定 4 宫格,还是得用 4 个MediaPlayer,但要把分辨率和帧率压下来。

5.4 一个验证技巧:用浏览器开发者工具看视频元素

WebGL 发布后,按 F12 打开开发者工具,在 Elements 面板里搜<video>。AVProVideo 会在 DOM 里插入 video 标签,你可以直接看到它的src、currentTime、readyState等属性。如果readyState一直是 0,说明流没加载成功;如果是 4,说明可以播放。这个技巧比看 Unity 控制台快得多,我每次排查播放问题都先看这里。

从那以后我每次做 WebGL 视频接入,都强制走一遍「先看 video 元素状态,再看 Unity 日志」的流程,能省掉一半的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询