首页 / 资讯中心 / 文章详情

Unity WebGL播放海康RTSP监控流:基于AVProVideo的实战Demo解析

Unity WebGL播放海康RTSP监控流:基于AVProVideo的实战Demo解析 ★ FEATURED ARTICLE
简介面向 Unity 开发者的 WebGL 平台海康威视监控画面实时播放 Demo直接解决 AVProVideo 在 WebGL 发布后无法拉流的痛点适用于安防接入、视频上屏等场景。源码重写了 HTTP 请求模块集成 AVProVideo 无水印版本并附 WebGL 发布设置指导配置文件只需按配表修改请求字段即可在浏览器端正常运行本地部署还可参考 nginx 方案。压缩包共 701 个文件、约 80MB核心包括 C# 业务脚本、Unity 场景与配置资产、Shader、平台依赖库so/dll/aar/jar和示例视频与贴图目录分类清晰便于按需提取移植。已有 1214 人学习下载需要快速在 WebGL 工程中对接海康设备或搭建跨端视频播放方案的开发者可拿来即用。1. 从「视频墙黑屏」到真正跑通这份海康监控 Demo 解决的核心痛点如果你在 Unity 里接海康的 RTSP 流大概率被坑过用 Unity 自带的 VideoPlayer界面要么黑屏要么卡成幻灯片要么只能在编辑器里跑、打包后直接报废。原因不难理解——VideoPlayer 底层不支持硬解对 H.265 的兼容也相当勉强而海康 IPC 出厂默认主码流通常就是 H.265。某公司做智慧园区大屏项目时试过 UMP、试过自己写原生插件最后换到 AVProVideo 才把 16 路监控墙稳定跑起来。这份 WebGL 平台基于 AVProVideo 播放海康设备监控实时画面的 Demo 源码就是当时沉淀下来的最小可运行工程核心思路一句话Unity 侧通过 AVProVideo 拉 RTSP 流PC 端打 WebGL 包前端页面直接嵌视频画面省去转码服务器。这份资源适合两类人一类是用 Unity 做数字孪生、可视化大屏需要把海康、大华等品牌摄像头实时画面接进场景的开发者另一类是在 WebGL 打包链路里反复踩坑想知道 RTSP 流到底该走哪条路的人。项目不大但把「解码器选型 → 拉流地址拼接 → 材质贴图 → WebGL 发布 → 运行时交互」整条链路完整打通了属于典型的拿来就能改的参考工程。2. 技术栈与工程结构为什么是 AVProVideo 而不是 VideoPlayer先捋清选型逻辑。Unity 原生 VideoPlayer 在 Android 和 WebGL 上的表现都一般尤其 WebGL 平台受限更多因为浏览器端默认不支持 RTSP 协议原生 VideoPlayer 根本没有可用的拉流通道。AVProVideo 是商业插件它最值钱的地方不是播放器本身而是提供了 Multiple Camera 和多平台底层抽象——在 WebGL 上它走的是 WebGLMediaPlayer HTML5 video 或 WASM 解码在 Windows 上走 DirectShow底层实现的差异被封装成统一的 MediaPlayer 组件Unity 侧代码不用为平台写分支。但这个 Demo 里真正值得注意的是它没有把 AVProVideo 当「播放器」用而是当「解码器 视频纹理源」用。视频画面最终是赋值给一个 RenderTexture 或直接给 Material 的 _MainTex再由场景里的 RawImage 或 Quad 网格显示这样监控画面才能和场景中的 3D 物体、UI 图层做叠加混合而不是弹一个独立播放窗体。工程结构按 Unity 常规目录组织核心部分如下Assets/ ├── Plugins/ │ ├── AVProVideo/ // 插件本体包含 WebGL 和 Windows 运行库 │ └── x86_64/ // Windows 平台原生解码库 ├── Scenes/ │ └── CameraPlayer.unity // 主场景挂播放器、材质、UI 控件 ├── Scripts/ │ ├── CameraController.cs // 控制播放、切流、自动重连 │ ├── UIController.cs // 按钮、输入框、状态文本绑定 │ └── StreamConfig.cs // RTSP 地址、解码参数配置类 ├── Materials/ │ └── VideoMat.mat // 接收视频纹理的材质 └── StreamingAssets/ └── config.json // 运行时读取的摄像头列表理解这份结构的关键是注意StreamingAssets/config.json这个文件。很多 Unity 开发者习惯把摄像头 IP、端口、用户名密码直接写死在代码里但这个项目走了配置外置的路线——WebGL 打包后是纯静态资源任何修改都得重新构建把设备列表放 StreamingAssets 里至少能改配置不重打包也算是在浏览器架构限制下的一个实用妥协。2.1 播放链路从 RTSP 地址到屏幕上的像素整个链路可以拆成五段每一段都有它的坑位。第一段是地址拼接海康的 RTSP 地址格式一般是rtsp://用户名:密码IP:554/Streaming/Channels/101其中101代表通道 1 主码流102是通道 1 子码流。Demo 里用 StreamConfig 类做拼接避免手写地址出错// StreamConfig.cs using System; [Serializable] public class StreamConfig { public string ip; public string username; public string password; public int channel 1; // 通道号海康一般从 1 开始 public int streamType 1; // 1主码流2子码流 public string BuildRtspUrl() { // 海康 RTSP 地址规则rtsp://user:passip:554/Streaming/Channels/101 // 101 通道1主码流102 通道1子码流依此类推 return $rtsp://{username}:{password}{ip}:554/Streaming/Channels/{channel:00}{streamType}; } }这段代码的细节在格式化字符串里。{channel:00}的意思是把通道号补成两位通道 1 输出01再拼上streamType的1得到101正好对上海康的 URL 规则。如果你接的是大华或宇视设备这个拼接规则要按厂家文档改写但代码结构不用动这正是配置类的好处——换设备型号只改一个方法。第二段是 AVProVideo 的 MediaPlayer 初始化。Demo 的做法是在相机或空物体上挂 MediaPlayer 组件然后把 config.json 里解析出的第一个地址赋给它// CameraController.cs 初始化片段 using RenderHeads.Media.AVProVideo; public MediaPlayer mediaPlayer; public RawImage displayTarget; private StreamConfig currentStream; private void Start() { LoadConfig(); PlayStream(currentStream); } private void PlayStream(StreamConfig config) { mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, config.BuildRtspUrl(), true); mediaPlayer.Play(); }OpenMedia的第三个参数true表示自动播放。AVProVideo 在 WebGL 平台上对自动播放有浏览器策略限制部分浏览器要求用户手势后才能出声但纯视频画面不受这个限制所以 Demo 里Play()直接调用问题不大如果后续要加音频就必须在按钮点击事件里触发播放。第三段是把解码后的视频帧绑定到材质。AVProVideo 的 MediaPlayer 每一帧会把视频纹理写到texture属性Demo 里的做法是把材质实例化后直接赋值private void AssignTextureToMaterial() { Texture videoTexture mediaPlayer.texture; if (videoTexture ! null) { videoMat.mainTexture videoTexture; displayTarget.texture videoTexture; // RawImage 或者 Quad 都能用 } }videoMat是VideoMat.mat的运行时实例。如果直接用videoMat.mainTexture xxx会污染 Assets 下的源材质所以正确姿势是先Instantiate运行时改实例工程里这个细节是处理过的二次开发时别把这个习惯丢掉。第四段是每一帧的刷新。AVProVideo 的解码是异步的不能在Start里同步拿到纹理所以要在Update里轮询private void Update() { if (mediaPlayer null) return; // 等纹理就绪后再绑定避免第一帧黑屏 if (mediaPlayer.texture ! null !textureBound) { AssignTextureToMaterial(); textureBound true; } // 断线重连检测 if (!mediaPlayer.IsPlaying() Time.time - lastRetryTime retryInterval) { lastRetryTime Time.time; mediaPlayer.Play(); } }这个轮询逻辑是拉流稳定性的关键。textureBound是个 bool 标志避免每帧都执行材质赋值IsPlaying()判断本质上是查解码器状态如果摄像头掉线或网络闪断AVProVideo 会停止播放这个位置补一个Play()调用就能实现最简单的自动重连。真实项目里建议加重连次数上限和状态 UI 反馈Demo 里只做了无限重连二次开发时需要加上限。2.2 WebGL 构建特殊处理关闭异常和内存陷阱从 Unity 编辑器切到 WebGL 平台最容易踩的不是 C# 语法坑而是 Unity 的 WebGL 内存分配机制。WebGL 默认内存上限是 256MB如果场景里加载了高分辨率视频纹理解码器内部缓冲会直接撑爆堆内存表现就是浏览器标签页崩溃或画面变绿。Demo 在 Project Settings 里做了三个关键设置Player Settings 的 WebGL 内存大小调到 512MBUse preallocated memory保持勾选Compression Format 选择 Brotli减小首屏加载体积Linking的 Exception Handling 保持 Disabled避免异常处理代码膨胀。这几项都在 Player Settings → WebGL 面板下配好后打包体积和运行内存会明显改善。另一个 WebGL 专属坑是跨域。WebGL 页面在浏览器里拉 RTSP 流时AVProVideo 底层实际上是先把流转封装成 HTTP 或 WebSocket 再喂给浏览器解码器如果摄像头和页面不在同一个域会被 CORS 策略拦截。Demo 的处理方式是建议摄像机侧打开「匿名访问」或通过 Nginx 转发 RTSP 时附加 CORS 头实际操作时最省事的办法是把 WebGL 页面部署到和摄像头同网段的服务器上再从 http 页面访问这点在 README 里也标注了。2.3 为什么把 UI 独立成 UIController这个 Demo 的交互部分写得相对克制一个输入框用于填摄像头 IP 或通道号一个播放按钮、一个停止按钮外加一个状态文本。UIController.cs把 UI 元素引用和事件绑定集中管理避免和 CameraController 的播放逻辑耦合// UIController.cs 按钮绑定逻辑 public void OnConnectButtonClicked() { // 从输入框构造新的 StreamConfig交给 CameraController 切换流 var config new StreamConfig { ip ipInput.text, username usernameInput.text, password passwordInput.text, channel int.Parse(channelInput.text) }; cameraController.SwitchStream(config); statusText.text 连接中... config.ip; }SwitchStream内部的处理值得一看不是直接OpenMedia而是先CloseMedia再OpenMedia新地址中间留一个 0.5 秒的间隔否则 AVProVideo 在某些版本上会因解码器未释放而报无效操作异常——这个细节我会在后面的避坑章节展开。整体代码量不大但边界情况基本都考虑到了空输入校验、非法 IP 格式提示、切流时旧画面保留还是清空这些都在工程里一一对应。3. 核心模块拆解拉流、纹理绑定与自动重连的完整链路前面把工程结构和选型讲完了这一章直接把每一段核心代码铺开按一次完整的拉流生命周期走一遍。从配置加载、首次连接、播放中帧刷新到断线重连每个环节落在哪个方法、哪个字段上等下就用代码说话。3.1 配置加载StreamingAssets 里的 JSON 怎么变成对象WebGL 平台不能直接读Application.streamingAssetsPath下的文件这是个常见误区。Unity 的StreamingAssets在 WebGL 上会被打包成远程资源路径前缀变成http://域名/StreamingAssets直接File.ReadAllText会抛异常。Demo 里用了UnityWebRequest异步加载这是个正确姿势// CameraController.cs 配置加载片段 using UnityEngine.Networking; private IEnumerator LoadConfig() { string path Application.streamingAssetsPath /config.json; #if UNITY_WEBGL // WebGL 下 StreamingAssets 走 HTTP必须用 UnityWebRequest using (var request UnityWebRequest.Get(path)) { yield return request.SendWebRequest(); if (request.result UnityWebRequest.Result.Success) { ParseConfig(request.downloadHandler.text); } } #else // 编辑器/桌面平台直接用 File API string json System.IO.File.ReadAllText(path); ParseConfig(json); #endif }这段代码里最值得留意的是#if UNITY_WEBGL条件编译。很多项目只写了编辑器版本打包到 WebGL 后才暴露出问题这里两种平台各走各的逻辑避免不必要的跨平台兼容故障。ParseConfig方法内部用JsonUtility.FromJson反序列化由于 Demo 的配置结构是数组所以包了一层StreamConfigList类这个细节如果漏掉JsonUtility会因为无法直接反序列化数组而抛异常[Serializable] public class StreamConfigList { public StreamConfig[] items; }UnityWebRequest的SendWebRequest()是一个协程方法必须在IEnumerator里调用所以Start()里启动协程而不是直接执行这也是 WebGL 异步模型的必然要求。注意ParseConfig里处理了设备列表为空或某个字段为 null 的情况避免在后续BuildRtspUrl()拼接时产生rtsp://null:null...这样的脏链接。3.2 首次连接OpenMedia 和 Play 的前后顺序AVProVideo 的MediaPlayer组件是核心控制入口但它的公开 API 在不同版本略有差异。Demo 基于现代 AVProVideo 版本编写OpenMedia和Play的标准调用顺序是先OpenMedia让解码器初始化再Play启动播放。如果倒过来部分平台会报NotReady错误表现为画面持续黑屏或者 Debug 日志刷错误。private void OpenAndPlay(StreamConfig config) { string url config.BuildRtspUrl(); // 先关旧流防止解码器状态残留 if (mediaPlayer.IsPlaying()) { mediaPlayer.CloseMedia(); // 留一帧间隔确保解码器完全释放 StartCoroutine(OpenAfterClose(url)); } else { mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, url, true); mediaPlayer.Play(); } } private IEnumerator OpenAfterClose(string url) { yield return null; // 等待一帧 mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, url, true); mediaPlayer.Play(); }CloseMedia的意义在切换摄像头时尤其明显。如果不关闭直接 Open 新流多数情况下 AVProVideo 内部状态机会停在旧流的解码器状态新地址虽然传进去了但解码器不会重新初始化导致画面卡在旧帧。yield return null等待一帧是最轻量的释放缓冲实测比Thread.Sleep可靠也不阻塞主线程。MediaPathType.AbsolutePathOrURL这个枚举的含义是告诉插件传入的字符串是绝对路径或完整 URL而不是相对资源路径。如果地址里既有rtsp://又有localhost这个枚举是唯一选择用RelativePath会导致插件找不到资源。3.3 帧同步机制什么时候材质能拿到完整纹理一个最常见的「黑屏但不报错」情况是Start()里调用了OpenMedia然后立刻AssignTextureToMaterial()结果拿到的是空纹理。解码不是同步操作尤其是 RTSP 流的建立需要经过 TCP 握手、RTSP 协商、RTP 收包、解码器输出等一串动作耗时从几百毫秒到几秒不等。Demo 的处理是轮询mediaPlayer.texture是否为空配合textureBound标志防重复绑定private void LateUpdate() { // LateUpdate 执行顺序在 Update 之后场景内其他逻辑先执行 // 此时解码器产出的纹理最可能是完整可用的 if (!textureBound mediaPlayer ! null mediaPlayer.texture ! null) { videoMat.mainTexture mediaPlayer.texture; textureBound true; // 根据视频纹理尺寸自动调整 UI 显示比例 float aspect (float)mediaPlayer.texture.width / mediaPlayer.texture.height; displayTarget.rectTransform.sizeDelta new Vector2(640, 640 / aspect); } }这里选LateUpdate而不是Update的原因是如果场景里有其他脚本也在改材质或 RawImageLateUpdate保证本脚本的绑定操作位于帧末其他脚本的修改不会被本帧的后续逻辑覆盖。aspect的自动调整是很多人忽略的功能点海康主码流通常是 1080p 或者 4:3 的老式模拟摄像头直接拉伸到 UI 固定尺寸会变形Demo 里这段代码让显示组件始终按视频原始宽高比保持比例。顺带一提mediaPlayer.texture这个 API 在不同 AVProVideo 版本里可能叫OutputTexture或GetTexture()如果你拿到一个不同版本的文档先检查这个 API 名不然编译期会报找不到属性。这也是为什么 Demo 代码里用了简短的别名封装方便统一改一处。3.4 自动重连IsPlaying 轮询的代价与替代方案监控场景里摄像头掉线是常态网络抖动、摄像头过热重启、NVR 通道被占用都能导致断流。Demo 的重连策略是每 3 秒查一次IsPlaying()如果为 false 就重新Play()简单但有效private float retryInterval 3f; private float lastRetryTime 0f; private int retryCount 0; private const int MaxRetry 5; private void CheckConnection() { if (!mediaPlayer.IsPlaying() Time.time - lastRetryTime retryInterval) { lastRetryTime Time.time; retryCount; if (retryCount MaxRetry) { // 超过最大重试次数切换备用码流或上报 UI statusText.text 连接失败请检查网络; return; // 不再继续尝试 } statusText.text $重连中... {retryCount}/{MaxRetry}; mediaPlayer.Play(); } else if (mediaPlayer.IsPlaying()) { retryCount 0; // 播放正常时重置计数 } }IsPlaying()在 AVProVideo 中的语义是「当前是否有解码帧正在输出」不是「网络是否联通」。所以网络断了但解码器还残留着最后一帧时IsPlaying()可能仍返回 true这时会继续显示旧画面。真实场景中需要区分处理如果只是画面冻结但还在输出帧可以判断mediaPlayer.GetCurrentTimeMs()是否在增长Demo 出于篇幅保留了简单逻辑但注释里提醒了二次开发时可按帧时间戳判断。重试间隔retryInterval设置成 3 秒是吸取的教训。设得太短比如 0.5 秒摄像头还在重新建立连接时就去连反而加重设备负担设得太长比如 10 秒用户体验上感觉卡顿明显。海康设备重启 RTSP 服务一般需要 510 秒3 秒的探测节奏配合上限 5 次正好覆盖一次设备重启周期。3.5 切流过程多摄像头切换的旧帧处理SwitchStream是 Demo 中交互最频繁的功能。它的核心问题不是打开新流而是旧流没有完全关闭时新流的解码器会继承旧流参数出现花屏或错位。UI 上处理方式是切流时先把画面置为黑色占位图再异步打开新流public void SwitchStream(StreamConfig newConfig) { // 切流前先清空画面避免新流未就绪时显示旧流残影 displayTarget.texture null; textureBound false; // 正在播放旧流时走完整关闭流程 if (mediaPlayer.IsPlaying()) { mediaPlayer.CloseMedia(); StartCoroutine(OpenAfterClose(newConfig.BuildRtspUrl())); } else { mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, newConfig.BuildRtspUrl(), true); mediaPlayer.Play(); } statusText.text 切换频道: newConfig.channel; }textureBound false这个复位操作容易被遗漏。如果切流前绑定过纹理切新流后LateUpdate里textureBound仍然为 true新流解码出的新纹理就不会被绑定到材质上画面永远停在黑场。这个 bug 在测试时往往发现不了因为单流环境下textureBound只从 false 变 true 一次但多流切换时必现。如果项目要支持多窗口同时预览多个摄像头不能直接复制这个脚本需要把CameraController改成组件数组或对象池每个播放器实例维护独立的textureBound和重连状态。这个改造思路在末尾章节展开。4. 避坑记录WebGL 拉 RTSP 流的四条血泪经验这部分记录在调试这个 Demo 时踩过的具体坑每一条都有现象、原因和解决办法。如果你也在 WebGL 平台接视频流对照排查能省不少时间。4.1 黑屏但不报错OpenMedia 后立即取纹理现象点击播放后界面全黑控制台无异常AVProVideo 的 Debug 日志也显示正常但mediaPlayer.texture始终为 null。原因解码是异步操作OpenMedia只是发起了网络请求和解码器初始化真正输出纹理要等 RTP 包到达并解码完成。编辑器模式下可能 1 秒内就有输出但 WebGL 平台受浏览器网络栈和 WASM 解码速度影响延迟会长很多。如果代码在Start()里同步取纹理必然拿到空值。解决改成在LateUpdate里轮询mediaPlayer.texture绑定后设标志位。不要用OnEnable()或Start()里同步绑定也别用InvokeRepeating短间隔轮询LateUpdate每帧检查一次足够开销可以忽略。4.2 WebGL 控制台报 „Unable to fetch“ 或 404现象打包后部署到服务器浏览器控制台报Unable to fetch StreamingAssets/config.json或直接 404。原因WebGL 的StreamingAssets默认会打包到输出目录的StreamingAssets文件夹但如果项目里勾选了「打包时将 StreamingAssets 压缩进.data 文件」这个路径就不可作为独立 URL 访问。另一种情况是部署时没把StreamingAssets文件夹一并上传到服务器。解决确认 Player Settings 里的Compression Format不是Brotli或Gzip导致的路径变化压缩方式不影响 StreamingAssets 路径但会改变 .data 结构。关键是检查发布目录里StreamingAssets/config.json文件是否存在并用浏览器直接访问这个 URL 验证。如果被防火墙拦截改用 Nginx 静态目录托管并设置正确的 MIME 类型。4.3 切流后花屏CloseMedia 和 OpenMedia 间隔太短现象切换摄像头后新画面出现横向撕裂、颜色错乱甚至画面颠倒但持续几秒后可能恢复。原因AVProVideo 底层解码器在CloseMedia后需要时间释放 GPU 资源和内部缓冲。如果紧跟着OpenMedia新的解码会话可能复用未完全清理的内存导致输出帧错位。解决切流时CloseMedia()后至少等待一帧yield return null实测最稳妥的是等待 2 帧或 100ms。如果设备支持也可以CloseMedia后强制调用mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, url, true)但打开前Application.GarbageCollect()不是必须的反而可能引入卡顿。4.4 H.265 主码流在浏览器端黑屏现象海康摄像头主码流设为 H.265 编码AVProVideo 在 Windows 编辑器里播放正常但 WebGL 打包后黑屏只有声音或什么都不显示。原因浏览器内置解码器对 H.265 的支持参差不齐Chrome 默认支持有限Safari 较新版本部分支持但 WebGL 的 WASM 解码器如果没包含 H.265 解码模块就会失败。AVProVideo 的 WebGL 版本挺依赖浏览器原生能力不像 Windows 平台那样内置了完整解码器。解决这个 Demo 的妥协方案是海康摄像头里把子码流设成 H.264代码里streamType改成 2画面优先用子码流承载。实际项目里也可以同时开两条流需要高清时用主码流桌面端需要兼容时用子码流WebGL 端。设备端改编码格式需要在海康 web 管理后台操作路径一般在「视音频管理 → 通道 → 主码流/子码流 → 视频编码」里把 H.265 改成 H.264不做这个改动 WebGL 多半是跑不通的。4.5 自动播放限制浏览器禁止带声音的自动播放现象页面加载后画面正常但没有任何声音手动点击一次播放按钮后声音恢复。原因Chrome、Firefox 等浏览器强制要求媒体元素必须通过用户手势触发才能带声音播放。AVProVideo 的Play()会自动创建 HTML5 video 元素如果触发了自动播放且带音频轨道浏览器会静音或直接拒绝播放。解决最省事的做法是在代码里先静音播放mediaPlayer.AudioMuted true等用户点击 UI 上的音量按钮时再设为 false。或者把首次Play()放在一个按钮点击事件里。这个 Demo 里没加音量控制但如果你想在监控画面中加对讲或音频这个坑必然会遇到。5. 跑起来从下载到 WebGL 包落地全流程拿到 Demo 源码后跑通它大概需要二十分钟前提是你本机已经装了 Unity Hub 和对应版本的 Unity 编辑器。整个流程分四步导入工程、配设备地址、切 WebGL 平台、打包部署。5.1 导入与工程配置用 Unity Hub 打开工程时选择项目根目录而不是 Assets 文件夹。如果当年的 Unity 版本提示升级建议直接取消升级因为 AVProVideo 的二进制库和旧版本 Unity 的兼容性最好。打开工程后会有很多编译警告只要没有红色报错就能继续。首次打开可能要重新输入 AVProVideo 的许可证因为插件绑定的是机器换成新电脑会提示激活。Demo 源码里如果附带了许可证文件直接导入即可如果没有用自己的插件许可证重新激活一次这个不影响 Demo 的功能演示。5.2 配置摄像头地址打开Scenes/CameraPlayer.unity在 Hierarchy 里选中CameraApp对象Inspector 里找到CameraController脚本。把StreamingAssets/config.json里的摄像头地址改成你的实际设备{ items: [ { ip: 192.168.1.64, username: admin, password: yourpassword, channel: 1, streamType: 1 } ] }如果摄像头的 RTSP 服务端口不是默认的 554可以在StreamConfig类里加一个port字段并在BuildRtspUrl()中用$rtsp://{username}:{password}{ip}:{port}/Streaming/Channels/{channel:00}{streamType}。海康默认 554 一般不用改但如果你的网络环境通过端口映射转发这个字段就有用了。注意密码里如果有或:等特殊字符RTSP URL 会解析出错因为 URL 格式里用户名和密码区段不能包含这两个符号。这种情况需要在BuildRtspUrl()里先做 URL 编码把特殊字符转成%XX形式否则拉流会报认证失败。5.3 切换 WebGL 平台与打包在File → Build Settings里选择WebGL点击Switch Platform。这一步会自动重新导入插件资源耗时约一分钟。随后打开Project Settings → Player把 WebGL 内存大小调到 512MBWebGL Template建议用默认或 Minimal前者带加载界面后者更纯粹。构建时渠道组等选项用默认即可。点Build And Run会在本地起一个临时服务器但 Unity 的预览服务器有时会缓存旧文件建议构建后手动启动一个静态文件服务器来访问输出目录cd build/webgl python3 -m http.server 8080浏览器访问http://localhost:8080如果摄像头在局域网内页面加载后大约 38 秒会看到画面。如果黑屏超过 10 秒按 F12 打开控制台查看报错多半是 CORS 或地址拼写问题对照上一章的避坑记录排查即可。5.4 WebGL 包的正常退出与资源释放浏览器切走标签页或刷新页面时WebGL 的音频和视频解码器可能残留导致摄像头侧 RTSP 连接未断开设备并发连接数被占满。Demo 里没有专门处理页面卸载事件但二次开发时建议在 JavaScript 里捕获window.onbeforeunload调用instance.SendMessage通知 Unity 关闭所有 MediaPlayer这个技巧属于 WebGL 交互增强放在后面一并讲。6. 二次开发技巧从单画面到多路监控墙的改造思路Demo 现在是单路播放真实项目里最常见需求是把它改成多路同时预览。改法不复杂但要把单例思路改成组件实例化思路顺便把 AVProVideo 的 MediaPlayer 做成对象池避免每路都重复创建组件。下面给出一个可直接套用的改造方案。6.1 多路播放器的对象池管理思路做一个VideoWallManager启动时根据配置列表创建 N 个 MediaPlayer 和 RawImage每个实例独立控制播放、重连。核心是避免每路都挂一个完整 CameraController而是把通用逻辑抽到SingleStreamPlayer组件// SingleStreamPlayer.cs 多路播放器组件 public class SingleStreamPlayer : MonoBehaviour { public MediaPlayer mediaPlayer; public RawImage display; public StreamConfig config; private bool textureBound false; public void Initialize(StreamConfig config, Material targetMaterial) { this.config config; mediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, config.BuildRtspUrl(), true); mediaPlayer.Play(); } private void LateUpdate() { if (!textureBound mediaPlayer.texture ! null) { display.texture mediaPlayer.texture; textureBound true; } } }改造后原来的CameraController退化成只负责解析配置和生成 UI 布局的管理器。布局部分可按行列计算 RawImage 的 anchor 和尺寸行列数由设备数量动态决定。注意每个SingleStreamPlayer的LateUpdate都会轮询自己的纹理这个开销可控但 16 路同时 1080p 时 WebGL 内存会极快上升需要把主码流切到子码流或者利用 AVProVideo 的GetTexture()返回同一纹理对象来共享显存。6.2 在 WebGL 页面里加前端控制按钮AVProVideo 的交互不止在 Unity 内部还可以通过 WebGL 的 JavaScript 接口操作 Unity 对象。比如做一个 HTML 按钮来切换通道不需要改 Unity UI直接在页面里用SendMessagebutton onclickswitchChannel(3)切换通道3/button script function switchChannel(ch) { // unityInstance 是 Unity Loader 创建的对象 unityInstance.SendMessage(CameraApp, OnWebButtonClicked, ch); } /scriptUnity 侧对应// CameraController.cs 接收 JS 消息 public void OnWebButtonClicked(int channel) { var cfg configList.items[channel - 1]; SwitchStream(cfg); }SendMessage的第一个参数是场景中对象的名称必须在 Hierachy 里对应存在第二个参数是脚本中 public 方法名第三个参数是传递的值支持 string、int、float。这是 WebGL 构建里比较常用的混合交互方式比在 Unity 里做全屏 UI 更轻量也更容易复用已有的前端框架。6.3 接入 NVR 多通道的统一管理海康 NVR 通常在一个 IP 下暴露多个通道RTSP 地址规则是rtsp://user:passip:554/Streaming/Channels/101对应通道 1 主码流201对应通道 2 主码流。Demo 的config.json数组天然适合批量配置{ items: [ { ip: 192.168.1.50, username: admin, password: pass, channel: 1, streamType: 1 }, { ip: 192.168.1.50, username: admin, password: pass, channel: 2, streamType: 1 }, { ip: 192.168.1.50, username: admin, password: pass, channel: 3, streamType: 1 } ] }NVR 的并发能力取决于它的硬件解码通道数一般 8 路或 16 路是上限超过会掉帧或拒绝连接。改造时建议在StreamConfig里加一个enabled字段运行时只拉取enabledtrue的通道避免无效连接占用 NVR 资源。6.4 验证改动是否成功的检查清单每次改完代码按这个清单走一遍能快速定位回归问题改完BuildRtspUrl()后先用编辑器的Debug.Log打印最终 URL并对照海康官方格式核对。改完材质绑定逻辑后确认textureBound在切流时复位否则会复现画面卡死在旧帧。改完多路播放器后逐个通道用CameraPlayer.unity场景测试不要一次性开满 16 路。WebGL 构建后用浏览器无痕模式访问排除缓存引起的旧版本问题。验证自动重连拔掉摄像头网线等 10 秒后查看 UI 状态是否按重试逻辑刷新。记得当时在项目里第一次把多路 Demo 跑起来画面墙铺满的时候确实松了一口气后来遇到 H.265 黑屏、CORS 拦截这类问题又回去翻了源码里的注释才找到出口。从那以后我每次做拉流相关改动都强制走一遍检查清单先把地址打印出来再验纹理绑定最后才打包。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站