1. 项目概述为什么选择AVPro Video在Unity项目里处理视频播放尤其是需要应对复杂格式、高清流媒体或者追求极致性能时Unity内置的VideoPlayer组件常常会显得力不从心。我接手过不少需要播放4K HDR视频、处理RTSP监控流或者是在WebGL平台上稳定播放的项目内置方案要么功能缺失要么性能开销巨大要么兼容性堪忧。这时候一个成熟、强大的第三方视频播放插件就成了刚需。AVPro Video特别是其Ultra Edition版本在Unity开发者社区里几乎是处理“硬骨头”视频任务时的首选方案。它不仅仅是一个播放器更像是一个完整的视频处理管线从解码、渲染到音频同步都提供了远超内置组件的控制力和性能。这次我们聚焦的1.11.0版本是一个功能相对稳定且被广泛验证的版本非常适合作为项目实战的起点。这个实战指南的核心不是简单地复述官方文档而是基于我在多个商业项目包括VR内容、大屏互动、移动端应用中踩过的坑和总结的经验告诉你如何把AVPro Video 1.11.0真正“用起来”集成到你的项目工作流中并解决那些官方文档可能一笔带过但在实际开发中却频频出现的棘手问题。无论你是需要在UI上播放宣传片还是在3D物体表面投射动态视频或是处理复杂的直播流这里的内容都能给你提供一条清晰的路径。2. 核心需求解析与插件选型考量在决定使用AVPro Video之前我们需要明确自己的项目到底需要什么。盲目引入一个功能强大的插件有时反而会增加项目的复杂度和维护成本。2.1 何时你需要AVPro Video如果你的项目遇到以下任何一种情况那么AVPro Video很可能就是你的正确选择需要播放特殊格式的视频比如MOV带Alpha通道、MKV、FLV或者一些专业摄像机生成的编码。Unity内置播放器对格式的支持非常有限尤其在移动端。对性能有极致要求例如在VR中同时播放多个高清视频或者需要在低端移动设备上流畅播放。AVPro Video可以利用硬件解码在支持的平台上大幅降低CPU占用。需要高级播放功能比如精确的帧步进、慢动作播放、音频波形可视化、视频拼接无缝循环播放多个视频、区域播放只播放视频的某一部分等。需要处理流媒体播放HLS.m3u8、DASH或RTSP/RTP流。这在直播、监控或者自适应码率视频场景中至关重要。跨平台一致性你的项目需要发布到Windows、macOS、iOS、Android、WebGL、各种游戏主机等多个平台并且希望视频播放的行为和性能尽可能一致。需要渲染到RenderTexture这是AVPro Video的强项可以高效地将视频渲染到一张纹理上然后你可以把这纹理用在任何地方——UI Image、3D物体的材质甚至作为另一个摄像机的输入源比如做画中画效果。2.2 AVPro Video 1.11.0版本特性与注意事项1.11.0是一个承上启下的版本。它已经包含了核心的稳定功能但相比最新版本可能缺少一些最新的优化或对新平台系统的适配。选择这个版本进行实战讲解是因为其稳定性和资料丰富性。核心优势广泛的编解码器支持通过集成平台原生播放器如Android的MediaPlayer, iOS的AVPlayer或使用其自定义解码器支持了海量格式。高效的渲染路径支持DirectX 11/12, Metal, OpenGL Core等多种图形API并能智能选择最高效的纹理更新方式。丰富的API提供了几乎控制播放器每一个细节的接口从播放、暂停、跳转到获取音频采样数据。强大的组件生态除了核心的MediaPlayer组件还提供了DisplayUGUI用于UI、ApplyToMaterial用于3D物体、AudioOutput等专用组件开箱即用。实战前的重要提醒注意AVPro Video是一个商业插件你需要从Asset Store购买许可证。1.11.0版本可能不包含后续的免费更新。在开始前请确保你拥有合法授权。此外务必阅读插件包内的README和CHANGELOG了解该版本已知的问题和平台限制。3. 项目集成与环境配置实战拿到插件包后直接导入整个项目是新手常犯的错误。这可能会引入大量当前平台不需要的库文件导致构建体积无谓增大。3.1 插件导入与平台筛选创建备份在导入任何大型插件前备份你的项目。这是一个好习惯。选择性导入在Unity的Package Manager或Asset Store下载后不要直接点击“Import”。在Import Unity Package窗口中仔细查看文件列表。AVPro Video通常会为不同平台Android, iOS, Windows等提供不同的插件文件夹如Plugins/Android,Plugins/iOS。只勾选你当前目标平台所需的插件文件以及所有平台通用的Scripts、Prefabs、Materials、Shaders文件夹。对于其他平台的本地库.so,.a,.bundle文件可以先不导入等切换平台时再单独导入对应的部分。​处理编译错误导入后Unity可能会报一些编译错误通常是因为缺少命名空间引用。确保你的脚本中引用了RenderHeads.Media.AVProVideo。如果插件提供了示例场景先打开一个最简单的示例场景如Scenes/Example/00_StartHere确保它能正常运行。这是验证插件导入是否成功的快速方法。3.2 核心组件初识与场景搭建AVPro Video的核心是MediaPlayer组件。它负责视频的加载、解码和控制。但MediaPlayer本身不负责显示需要配合其他组件。一个最基础的播放器搭建在Hierarchy中创建一个空GameObject命名为“VideoPlayer”。为其添加MediaPlayer组件。在UI Canvas下创建一个Image对象命名为“VideoDisplay”。为这个Image对象添加DisplayUGUI组件。将MediaPlayer组件拖拽到DisplayUGUI组件的“Media Player”字段上进行关联。在MediaPlayer组件的“Media Source”中选择你的视频文件支持拖拽Project中的视频文件或填写URL。运行场景你应该能看到视频在UI Image上播放。这个过程看似简单但每个组件背后都有大量可配置参数。MediaPlayer组件关键参数解析Auto Start是否在组件Start()时自动开始加载和播放。Auto Open是否自动打开Media Source中设置的源。如果设为false你需要手动调用OpenMedia()。Loop是否循环播放。Playback Rate播放速率1.0为正常速度可用于实现快慢放。Volume音频音量。Audio Muted是否静音。Video Source决定使用哪个解码/渲染路径。对于大多数情况使用MediaPlayer即可它会自动选择最佳方案。DisplayUGUI组件关键参数解析Display Background当视频未加载或加载失败时显示的背景颜色。Scale Mode缩放模式如StretchToFill拉伸填充、FitInside适应内部保持比例等类似于UI Image的Image Type。No Default Display勾选后需要手动通过脚本将视频纹理赋值给某个RawImage/Image。这提供了更大的灵活性。3.3 针对不同平台的初始设置不同的平台需要不同的前置设置否则视频可能无法播放或没有声音。Android平台Player Settings确保Minimum API Level设置在21Android 5.0或以上以获得更好的兼容性。权限在Player Settings - Android - Other Settings中如果视频文件位于外部存储如SD卡你可能需要勾选WRITE_EXTERNAL_STORAGE或READ_EXTERNAL_STORAGE权限。对于网络流播放需要INTERNET权限。脚本后端IL2CPP是推荐的选择。如果遇到问题可以尝试切换回Mono进行测试。关键设置在MediaPlayer组件上检查Platform Options折叠栏下的Android设置。通常需要确保Use Fast OES Path如果可用被勾选以启用更高效的OpenGL ES渲染路径。iOS平台权限同样需要NSAppleMusicUsageDescription如果访问媒体库或NSMicrophoneUsageDescription如果使用麦克风音频等隐私描述即使你只是播放本地视频添加一个通用的媒体库使用描述也更安全。Audio OutputiOS上音频处理比较特殊。确保MediaPlayer组件的Audio Output设置正确。通常使用Unity Audio即可但如果遇到音频延迟或同步问题可以尝试启用External (iOS Native)选项并配置好AudioOutput组件。WebGL平台视频格式WebGL的限制最大。它严重依赖浏览器自身的视频播放能力。MP4 (H.264编码 AAC音频) 是兼容性最广的格式。避免使用MKV、MOV等格式。流媒体播放HLS.m3u8在WebGL上是可能的但依赖于浏览器的MSEMedia Source Extensions支持且需要正确的CORS头部配置。自动播放策略现代浏览器禁止音频自动播放。你必须通过用户的交互如点击按钮来触发视频的Play()方法否则视频可能只有画面没有声音或者完全无法播放。MediaPlayer的Auto Start在WebGL上很可能失效。构建大小AVPro Video for WebGL会包含一个解码器模块这会显著增加构建后.data文件的大小。务必进行测试。实操心得在项目初期就建立多平台的测试流程。针对每个目标平台准备一个最简单的“视频播放测试场景”包含本地文件和网络流两种源。在每次重大改动或Unity版本升级后都在所有目标平台上跑一遍这个测试场景能提前发现很多兼容性问题。4. 核心功能实现与脚本控制掌握了基础播放后我们通过脚本来实现更动态、更复杂的功能。AVPro Video提供了非常详细的API。4.1 基础播放控制与状态监听创建一个C#脚本VideoController将其挂载到含有MediaPlayer组件的GameObject上。using RenderHeads.Media.AVProVideo; using UnityEngine; using UnityEngine.UI; // 如果需要更新UI public class VideoController : MonoBehaviour { public MediaPlayer mediaPlayer; public Slider progressSlider; // 用于显示进度 public Text timeText; // 用于显示当前时间/总时间 void Start() { // 确保有MediaPlayer引用 if (mediaPlayer null) { mediaPlayer GetComponentMediaPlayer(); } // 监听事件 if (mediaPlayer ! null) { mediaPlayer.Events.AddListener(OnMediaPlayerEvent); } } void Update() { // 更新进度条和时间显示 if (mediaPlayer ! null mediaPlayer.Control ! null mediaPlayer.Control.IsPlaying()) { float currentTime mediaPlayer.Control.GetCurrentTime(); float duration mediaPlayer.Info.GetDuration(); if (progressSlider ! null) { progressSlider.value (duration 0) ? (currentTime / duration) : 0; } if (timeText ! null) { timeText.text FormatTime(currentTime) / FormatTime(duration); } } } // 格式化时间秒 - 分:秒 private string FormatTime(float timeInSeconds) { int minutes Mathf.FloorToInt(timeInSeconds / 60); int seconds Mathf.FloorToInt(timeInSeconds % 60); return string.Format({0:00}:{1:00}, minutes, seconds); } // 处理MediaPlayer事件 private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType eventType, ErrorCode error) { switch (eventType) { case MediaPlayerEvent.EventType.ReadyToPlay: Debug.Log(视频准备就绪可以开始播放。); // 可以在这里获取视频信息 Debug.Log($视频分辨率: {mp.Info.GetVideoWidth()}x{mp.Info.GetVideoHeight()}); break; case MediaPlayerEvent.EventType.Started: Debug.Log(视频开始播放。); break; case MediaPlayerEvent.EventType.FirstFrameReady: Debug.Log(第一帧已准备好。); break; case MediaPlayerEvent.EventType.FinishedPlaying: Debug.Log(视频播放完成。); // 如果是循环播放这里也会触发 break; case MediaPlayerEvent.EventType.Error: Debug.LogError($播放出错: {error}); // 处理错误如显示错误信息、重试等 break; } } // 提供给UI按钮调用的方法 public void PlayPause() { if (mediaPlayer ! null mediaPlayer.Control ! null) { if (mediaPlayer.Control.IsPlaying()) { mediaPlayer.Control.Pause(); } else { mediaPlayer.Control.Play(); } } } public void Seek(float normalizedTime) { if (mediaPlayer ! null mediaPlayer.Control ! null) { float duration mediaPlayer.Info.GetDuration(); mediaPlayer.Control.Seek(normalizedTime * duration); } } }关键点解析mediaPlayer.Control这是控制播放、暂停、跳转等操作的接口。在调用其方法前务必检查其是否为null。mediaPlayer.Info用于获取视频信息如时长、宽高、帧率等。同样需要检查是否为null并且通常在ReadyToPlay事件之后才能获取到有效信息。事件监听通过mediaPlayer.Events.AddListener订阅事件是响应视频状态变化最可靠的方式。不要依赖Update轮询去判断状态。4.2 播放动态视频源与流媒体视频源不一定是固定的文件。我们可能需要根据用户选择或网络情况动态加载。public void LoadVideoFromPath(string videoPathOrUrl) { if (mediaPlayer null) return; // 先停止并关闭当前播放的视频 if (mediaPlayer.Control ! null mediaPlayer.Control.IsPlaying()) { mediaPlayer.Control.Stop(); } mediaPlayer.CloseMedia(); // 设置新的媒体源 mediaPlayer.m_VideoPath videoPathOrUrl; // 根据路径判断是本地文件还是网络URL这是一个简单判断 if (videoPathOrUrl.StartsWith(http://) || videoPathOrUrl.StartsWith(https://) || videoPathOrUrl.StartsWith(rtsp://)) { mediaPlayer.m_VideoLocation MediaPlayer.FileLocation.AbsolutePathOrURL; } else { // 假设是相对于StreamingAssets的路径 mediaPlayer.m_VideoLocation MediaPlayer.FileLocation.RelativeToStreamingAssetsFolder; } // 打开媒体 mediaPlayer.OpenMedia(); } // 示例播放一个HLS流 public void PlayHLSStream(string m3u8Url) { LoadVideoFromPath(m3u8Url); }处理StreamingAssets中的视频Unity的StreamingAssets文件夹在构建后其访问方式因平台而异。AVPro Video的MediaPlayer.FileLocation.RelativeToStreamingAssetsFolder选项帮我们处理了这些差异。你只需要将视频放在Assets/StreamingAssets下然后传入相对路径如MyVideo.mp4即可。注意事项对于Android平台StreamingAssets中的文件在APK内是压缩的首次读取可能会有延迟。对于大视频文件考虑将其放在PersistentDataPath并提前下载或者使用Application.streamingAssetsPath构建完整路径后以FileLocation.AbsolutePathOrURL方式加载。4.3 渲染到RenderTexture与3D物体应用这是AVPro Video最强大的功能之一。你可以将视频渲染到一张RenderTexture上然后像使用普通纹理一样使用它。创建RenderTexture在Project中右键 - Create - Render Texture。根据视频分辨率设置其大小例如1920x1080。如果视频需要透明通道记得设置Color Format为ARGB32或RGBAFloat等支持Alpha的格式。配置MediaPlayer在MediaPlayer组件的Output折叠栏下将Texture Output设置为RenderTexture然后将你创建的RenderTexture拖入Target Texture槽中。应用到3D物体创建一个3D物体如Quad或Plane。创建一个新的材质使用Standard或Unlit/Texture着色器。将上一步的RenderTexture拖拽到材质的Albedo或Base Map通道上。将该材质赋予3D物体。或者使用ApplyToMaterial组件更简单的方法是在3D物体上添加ApplyToMaterial组件然后将MediaPlayer拖拽给它。它会自动处理纹理的更新和UV映射。脚本动态控制public RenderTexture targetRenderTexture; public Material targetMaterial; // 要应用视频纹理的材质 public string texturePropertyName _MainTex; // 材质中纹理属性的名称 void Start() { if (mediaPlayer ! null targetRenderTexture ! null) { // 方法1通过MediaPlayer的Output设置如果在编辑器里没设 // mediaPlayer.m_TargetTexture targetRenderTexture; // 方法2直接赋值给材质 if (targetMaterial ! null) { // 监听纹理就绪事件 mediaPlayer.Events.AddListener(OnMediaPlayerEvent); } } } private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType eventType, ErrorCode error) { if (eventType MediaPlayerEvent.EventType.FirstFrameReady) { // 获取视频纹理并赋值给材质 Texture2D videoTex mp.TextureProducer.GetTexture(0) as Texture2D; // 获取第一个视频轨道的纹理 if (videoTex ! null targetMaterial ! null) { // 注意这里获取的是Texture2D如果需要RenderTexture应使用上述Output设置方法。 // 这里演示动态赋值的逻辑。 targetMaterial.SetTexture(texturePropertyName, videoTex); } } }应用场景电视/屏幕模型在虚拟场景中创建一个电视机模型播放动态内容。视频投影将视频投射到复杂的3D曲面物体上。动态天空盒将360度全景视频渲染到天空盒材质上创造动态环境。后期处理特效源将视频作为自定义后处理效果的输入。5. 性能优化与高级技巧当视频播放成为项目核心尤其是需要同时播放多个高清视频时性能优化就至关重要。5.1 内存与CPU优化策略纹理流送与Mipmap对于渲染到3D物体且物体可能离摄像机较远的视频可以考虑启用Generate Mip Maps在RenderTexture设置中。但这会增加GPU内存和生成开销需权衡。对于始终全屏播放的UI视频则不需要。分辨率适配不要总是使用视频的原始分辨率进行渲染。如果播放区域很小可以创建一个更低分辨率的RenderTexture来接收视频输出以节省GPU带宽和内存。可以通过脚本根据播放区域的大小动态创建合适尺寸的RenderTexture。及时释放当视频播放完毕或不再需要时调用mediaPlayer.CloseMedia()来释放解码器和相关资源。如果彻底不再使用该MediaPlayer销毁GameObject前确保先关闭媒体。预加载对于已知即将播放的关键视频可以在空闲时如进入场景时提前创建MediaPlayer实例并调用OpenMedia()然后暂停。这样当需要播放时调用Play()的延迟会大大降低。但要注意内存占用。限制并发播放数在移动设备上同时硬件解码多个1080p视频可能超出系统能力。需要设计逻辑来管理视频播放的优先级确保前台关键视频流畅暂停或降低后台视频的码率/分辨率如果支持。5.2 音频处理与同步音频问题特别是音画不同步是视频播放的常见难题。音频输出模式选择Unity Audio (Default)音频数据由AVPro Video解码后送入Unity的音频系统。兼容性好易于与Unity的其他音频混合。External (Platform Native)音频直接由平台原生播放器处理如iOS的AVPlayer。这通常能提供最低的延迟和最好的同步但可能更难与Unity的音频音量控制等集成。实践建议在移动平台iOS/Android上如果遇到同步问题优先尝试切换到External模式并配置AudioOutput组件。使用AudioOutput组件当选择External音频模式时必须使用AudioOutput组件。将其添加到MediaPlayer所在的GameObject并正确关联。它允许你指定音频输出到哪个Unity的AudioSource或者进行更底层的音频采样数据获取用于波形可视化。同步问题排查检查视频文件本身用专业播放器如VLC检查音画是否同步。有些视频文件在制作时就有问题。调整Audio DelayMediaPlayer组件上有一个Audio Delay参数单位秒。如果声音总是比画面慢就设置一个负值如-0.1来提前音频反之则设正值。监控性能音画不同步往往是性能不足的表现。使用Unity Profiler监控MediaPlayer相关的CPU开销以及Audio模块的DSP负载。确保帧率稳定。5.3 多实例管理与对象池在需要动态创建大量视频播放器的场景如视频列表、画廊频繁实例化和销毁MediaPlayerGameObject开销很大。实现一个简单的视频播放器对象池using System.Collections.Generic; using UnityEngine; public class MediaPlayerPool : MonoBehaviour { public MediaPlayer mediaPlayerPrefab; // 预制的MediaPlayer GameObject public int initialPoolSize 5; private QueueMediaPlayer _pool new QueueMediaPlayer(); private ListMediaPlayer _activePlayers new ListMediaPlayer(); void Start() { for (int i 0; i initialPoolSize; i) { CreateNewPlayerInPool(); } } private MediaPlayer CreateNewPlayerInPool() { MediaPlayer newPlayer Instantiate(mediaPlayerPrefab, this.transform); newPlayer.gameObject.SetActive(false); // 初始化一些通用设置比如关闭AutoStart newPlayer.m_AutoStart false; _pool.Enqueue(newPlayer); return newPlayer; } public MediaPlayer GetMediaPlayer() { MediaPlayer player; if (_pool.Count 0) { player _pool.Dequeue(); } else { player CreateNewPlayerInPool(); } player.gameObject.SetActive(true); _activePlayers.Add(player); return player; } public void ReturnMediaPlayer(MediaPlayer player) { if (player ! null _activePlayers.Contains(player)) { player.Control.Stop(); player.CloseMedia(); player.gameObject.SetActive(false); _activePlayers.Remove(player); _pool.Enqueue(player); } } // 清理所有播放器 public void Cleanup() { foreach (var player in _activePlayers) { player.Control.Stop(); player.CloseMedia(); Destroy(player.gameObject); } _activePlayers.Clear(); foreach (var player in _pool) { Destroy(player.gameObject); } _pool.Clear(); } }使用时通过GetMediaPlayer()获取一个可用的播放器配置其视频源后播放。使用完毕后调用ReturnMediaPlayer()将其归还池中而不是Destroy。这能有效减少GC垃圾回收压力提升运行时性能。6. 常见问题排查与实战陷阱这里汇总了我在多个项目中使用AVPro Video 1.11.0时遇到的最典型问题及其解决方案。6.1 视频无法播放黑屏/无声音这是最常见的问题。请按照以下清单逐一排查现象可能原因解决方案黑屏无任何错误1. 视频路径错误或文件不存在。2. 平台不支持该视频编码格式。3.DisplayUGUI或ApplyToMaterial组件未正确关联MediaPlayer。4. 视频分辨率过大超出GPU支持纹理尺寸移动端常见。1. 检查MediaPlayer的Video Path使用绝对路径或确保文件在StreamingAssets中。对于网络流用浏览器测试URL是否可达。2. 将视频转换为平台通用格式如MP4 H.264。使用MediaPlayer的Platform Options查看当前平台支持的格式。3. 在Hierarchy中检查关联关系。4. 尝试降低视频分辨率或使用Resize功能如果插件版本支持。有声音无画面1. 着色器问题。视频纹理是YUV格式需要特殊着色器渲染。2. 渲染目标设置错误如渲染到了不存在的RenderTexture。1.DisplayUGUI和ApplyToMaterial组件自带正确的着色器。如果自定义材质确保使用AVProVideo/Unlit/Opaque或类似的AVPro Video专用着色器。2. 检查MediaPlayer的Output设置如果用了RenderTexture确保该纹理有效。有画面无声音1. 播放器或系统静音。2. WebGL平台的自动播放策略限制。3. 音频输出模式配置错误。4. 视频文件本身不含音频轨。1. 检查MediaPlayer的Volume和Audio Muted以及Unity的音频设置和系统音量。2. 确保播放是由用户交互如点击触发的。3. 检查Audio Output设置尝试在Unity Audio和External之间切换。4. 用其他播放器检查文件。播放几秒后卡住1. 流媒体网络缓冲中断。2. 移动设备发热降频或内存不足。3. 视频文件损坏。1. 监听Buffering事件显示缓冲进度。优化网络或提供备用源。2. 监控设备温度和内存降低视频码率或分辨率。3. 重新转码或下载视频文件。6.2 编辑器正常打包后异常这个问题非常普遍根本原因在于编辑器环境和真机环境的差异。路径问题这是头号杀手。在编辑器中Application.streamingAssetsPath可能指向Assets/StreamingAssets而打包后路径完全不同如Android的jar:file://。始终坚持使用MediaPlayer.FileLocation.RelativeToStreamingAssetsFolder来指定相对路径或者使用Application.streamingAssetsPath拼接出完整路径后使用AbsolutePathOrURL。避免在代码中写死类似C:/Users/.../video.mp4的路径。插件依赖缺失确保为目标平台正确导入了所有必需的本地插件.dll,.so,.a,.bundle。有时候需要手动在Player Settings - Other Settings中确保特定库被包含。权限问题尤其是Android和iOS检查AndroidManifest.xml或Info.plist中是否声明了必要的权限网络、存储访问。代码剥离Stripping如果使用了反射或动态加载Unity的代码剥离可能会移除AVPro Video某些看似“未使用”的类。尝试在Player Settings - Managed Stripping Level中将其设置为Low或Disabled进行测试。图形API确保打包设置的图形API与插件兼容。例如某些WebGL版本可能只支持WebGL 2.0。6.3 特定平台疑难杂症iOS音频中断当电话打入或切换到其他音频App时Unity音频会话可能被中断。恢复后AVPro Video的音频可能无法恢复。解决方案是监听Unity的OnApplicationPause事件在应用恢复时重新打开或 Seek 一下视频来“唤醒”音频输出。Android视频拉伸变形在某些Android设备上视频可能会被非均匀拉伸。这通常与设备屏幕的宽高比和Unity的UI缩放模式有关。确保DisplayUGUI的Scale Mode设置正确并检查Canvas的Canvas Scaler设置。有时需要在代码中根据视频宽高比动态调整显示区域的大小。WebGL首次加载缓慢或卡顿WebGL版本需要下载和初始化一个较大的解码器模块。可以通过显示加载界面并在后台提前初始化一个隐藏的MediaPlayer来“预热”解码器。另外确保视频文件本身经过良好的压缩并使用HTTP/2或CDN加速传输。通用内存泄漏如果频繁打开/关闭不同的视频可能会发现内存持续增长。确保在关闭视频CloseMedia()前先停止Stop()。将不用的MediaPlayer实例Dispose()如果通过代码创建或归还对象池。检查是否有事件监听器没有正确移除导致对象无法被垃圾回收。在组件的OnDestroy方法中调用mediaPlayer.Events.RemoveListener(...)。7. 项目实战构建一个功能完整的视频播放器UI我们将综合运用以上知识创建一个包含播放/暂停、进度条、音量控制、全屏切换的完整视频播放器UI。步骤UI布局创建一个Canvas包含以下元素一个RawImage作为视频显示区域挂载DisplayUGUI。一个半透明的Panel作为控制栏置于视频底部。控制栏内包含播放/暂停按钮、当前时间Text、进度条Slider、总时间Text、音量Slider、全屏按钮。脚本编写创建一个AdvancedVideoPlayerUI脚本。using RenderHeads.Media.AVProVideo; using UnityEngine; using UnityEngine.UI; public class AdvancedVideoPlayerUI : MonoBehaviour { public MediaPlayer mediaPlayer; public DisplayUGUI displayUGUI; public Button playPauseButton; public Image playPauseIcon; // 用于切换播放/暂停图标 public Sprite playSprite; public Sprite pauseSprite; public Slider progressSlider; public Text currentTimeText; public Text durationText; public Slider volumeSlider; public Button fullScreenButton; public CanvasGroup controlCanvasGroup; // 用于控制栏淡入淡出 private bool _isDraggingProgress false; private float _hideControlsTimer 0f; private const float CONTROLS_HIDE_DELAY 3f; void Start() { // 初始化UI状态 if (playPauseButton ! null) playPauseButton.onClick.AddListener(OnPlayPauseClicked); if (progressSlider ! null) { progressSlider.onValueChanged.AddListener(OnProgressSliderChanged); progressSlider.onPointerDown.AddListener(() _isDraggingProgress true); progressSlider.onPointerUp.AddListener(() { _isDraggingProgress false; SeekToSliderValue(); }); } if (volumeSlider ! null) { volumeSlider.onValueChanged.AddListener(OnVolumeChanged); if (mediaPlayer ! null) volumeSlider.value mediaPlayer.m_Volume; } if (fullScreenButton ! null) fullScreenButton.onClick.AddListener(ToggleFullScreen); if (mediaPlayer ! null) { mediaPlayer.Events.AddListener(OnMediaPlayerEvent); // 初始化时间显示 UpdateTimeDisplay(0, mediaPlayer.Info.GetDuration()); } // 初始隐藏控制栏几秒后 HideControls(); } void Update() { if (mediaPlayer null || mediaPlayer.Control null) return; // 更新进度条如果用户没有在拖动 if (!_isDraggingProgress mediaPlayer.Control.IsPlaying()) { float currentTime mediaPlayer.Control.GetCurrentTime(); float duration mediaPlayer.Info.GetDuration(); progressSlider.SetValueWithoutNotify((duration 0) ? (currentTime / duration) : 0); UpdateTimeDisplay(currentTime, duration); } // 控制栏自动隐藏逻辑 if (controlCanvasGroup ! null controlCanvasGroup.alpha 0) { _hideControlsTimer - Time.deltaTime; if (_hideControlsTimer 0) { HideControls(); } } } // 显示控制栏并重置隐藏计时器 public void ShowControls() { if (controlCanvasGroup ! null) { controlCanvasGroup.alpha 1; controlCanvasGroup.blocksRaycasts true; _hideControlsTimer CONTROLS_HIDE_DELAY; } } private void HideControls() { if (controlCanvasGroup ! null mediaPlayer ! null mediaPlayer.Control.IsPlaying()) { controlCanvasGroup.alpha 0; controlCanvasGroup.blocksRaycasts false; } } // 当用户与UI交互时调用此方法 public void OnUIClicked() { ShowControls(); } private void OnPlayPauseClicked() { if (mediaPlayer.Control.IsPlaying()) { mediaPlayer.Control.Pause(); if (playPauseIcon ! null) playPauseIcon.sprite playSprite; } else { mediaPlayer.Control.Play(); if (playPauseIcon ! null) playPauseIcon.sprite pauseSprite; } ShowControls(); } private void OnProgressSliderChanged(float value) { /* 仅更新预览时间不实际跳转 */ } private void SeekToSliderValue() { float duration mediaPlayer.Info.GetDuration(); mediaPlayer.Control.Seek(progressSlider.value * duration); ShowControls(); } private void OnVolumeChanged(float value) { if (mediaPlayer ! null) mediaPlayer.m_Volume value; ShowControls(); } private void ToggleFullScreen() { // 全屏切换逻辑需要根据你的UI系统实现如调整Canvas缩放模式、切换分辨率等 Debug.Log(全屏功能需根据项目UI框架实现); ShowControls(); } private void UpdateTimeDisplay(float current, float total) { currentTimeText.text FormatTime(current); durationText.text FormatTime(total); } private string FormatTime(float seconds) { /* 同前 */ } private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode ec) { if (et MediaPlayerEvent.EventType.Started) { if (playPauseIcon ! null) playPauseIcon.sprite pauseSprite; HideControls(); // 开始播放后延迟隐藏控制栏 } else if (et MediaPlayerEvent.EventType.Paused) { if (playPauseIcon ! null) playPauseIcon.sprite playSprite; ShowControls(); // 暂停时显示控制栏 } else if (et MediaPlayerEvent.EventType.FinishedPlaying) { if (playPauseIcon ! null) playPauseIcon.sprite playSprite; progressSlider.SetValueWithoutNotify(1f); UpdateTimeDisplay(mp.Info.GetDuration(), mp.Info.GetDuration()); ShowControls(); } else if (et MediaPlayerEvent.EventType.ReadyToPlay) { float duration mp.Info.GetDuration(); progressSlider.SetValueWithoutNotify(0); UpdateTimeDisplay(0, duration); durationText.text FormatTime(duration); } } }场景交互为视频显示区域RawImage添加一个Button组件或使用EventTrigger将其点击事件关联到AdvancedVideoPlayerUI.OnUIClicked()方法实现点击画面显示/隐藏控制栏。全屏实现全屏功能依赖于你的UI框架。对于简单的全屏你可以调整Canvas的渲染模式或控制一个全屏UI面板的显隐。对于真正的全屏可能需要调用Screen.SetResolution并隐藏其他UI元素。这是一个相对独立的功能模块需要根据项目需求定制。通过这个实战案例你将掌握AVPro Video从集成、配置、脚本控制到UI交互的完整流程并能应对开发中遇到的大部分挑战。记住视频播放的稳定性需要大量的真机测试尤其是在性能各异的移动设备上。