资讯动态

Unity视频播放器开发实战:VideoPlayer组件从入门到精通

发布时间:2026/10/18 18:54:25 来源:尧图企业网站定制
1. 为什么要在Unity里手搓一个播放器很多人第一次听到“在Unity里做播放器”这个需求第一反应都是这不是脱裤子放屁吗系统自带播放器不香吗我一开始也这么想直到前两年接了一个互动展示类的项目客户要求在展厅的大屏上循环播放几段宣传片同时还要在视频播放到特定时间点时触发一些3D模型动画和灯光变化。这时候系统播放器就彻底不够用了——你没法让一个外部播放器去通知Unity“嘿第12秒到了该让那个机械臂动起来了”。这就是Unity播放器存在的意义它不是要替代系统播放器而是要把视频播放这件事纳入到Unity的运行时逻辑里让视频成为整个交互系统的一个可控元素。你可以精确控制播放进度、监听播放事件、在任意时刻暂停或跳转甚至把视频画面当作纹理贴到3D物体表面上去。这些能力在互动展览、虚拟仿真、教育课件、游戏内过场动画等场景里都是刚需。这篇文章面向的是有一定Unity基础、但没怎么碰过视频播放模块的开发者。我会从最核心的VideoPlayer组件讲起一步步拆解怎么做出一个功能完整、界面简洁的播放器包括播放控制、进度条拖拽、音量调节、全屏切换这些基础功能也会聊到一些实际项目中踩过的坑比如视频格式兼容性、内存占用、不同平台的渲染差异等等。你不需要有音视频编解码的背景知识只要会用Unity的基本UI系统和C#脚本就能跟着做出来。2. 核心组件选型与整体设计思路2.1 VideoPlayer还是AVPro先搞清楚需求边界Unity自带的视频播放方案主要有两个一个是老牌的MovieTexture已经废弃不建议再用另一个是2017版本之后引入的VideoPlayer组件。VideoPlayer是目前官方主推的方案支持本地视频文件和URL流两种来源能在全平台运行而且可以直接把画面渲染到Camera、RenderTexture或者UI的RawImage上。第三方插件里比较出名的是AVPro Video功能确实强大支持硬件解码、透明视频、HAP编码等高级特性但它是收费的而且对于“简易播放器”这个需求来说属于杀鸡用牛刀。我个人的判断标准很简单如果你的需求不涉及8K视频、透明通道视频、或者极低延迟的直播流Unity自带的VideoPlayer完全够用。这个项目里我们用的就是VideoPlayer零成本、零依赖、跨平台。选型确定之后整体架构其实就很清晰了。我们需要一个RawImage来承载视频画面一个VideoPlayer组件来控制播放逻辑一组UI按钮和Slider来做交互再加一个C#脚本把这些东西串起来。整个播放器的核心逻辑就是用户操作UI → 脚本调用VideoPlayer的API → VideoPlayer更新画面和状态 → 脚本监听事件更新UI显示。2.2 渲染路径的选择RawImage、RenderTexture还是CameraVideoPlayer的输出方式有三种Render Mode可以设为Camera Far Plane、Camera Near Plane、Render Texture或者API Only。这几种方式的区别直接影响到你的播放器怎么显示画面。Camera Far Plane和Camera Near Plane是把视频画面直接渲染到摄像机的远/近裁剪面上相当于给整个屏幕铺了一层视频背景。这种方式最简单但缺点是你没法控制视频画面在UI层级中的位置也没法把视频贴到某个具体的UI元素上。Render Texture方式是把视频渲染到一张RenderTexture上然后你可以把这张RT赋给任何需要显示它的地方——RawImage、Material、甚至粒子系统。这是最灵活的方式也是我做播放器时的首选。具体操作就是创建一个RenderTexture资源把它赋给VideoPlayer的Target Texture属性再把同一个RT赋给RawImage的Texture属性。这样视频画面就会出现在RawImage所在的位置你可以随意调整它的大小、位置、层级关系。API Only模式不自动渲染完全靠脚本通过VideoPlayer.texture来手动获取每一帧的画面。这种方式适合需要自己做后期处理的场景比如给视频加滤镜、做画面分析等。对于简易播放器来说用RenderTexture就够了。注意RenderTexture的分辨率不要设得太大。我见过有人直接设成4K结果在移动端上内存直接爆掉。一般来说设成视频源分辨率的一半到一倍之间就够了具体要看目标设备的性能。2.3 UI布局的规划别一上来就拖控件很多新手做UI的习惯是打开Unity就开始往Canvas里拖按钮、拖Slider拖完发现布局乱七八糟改起来又得重新调。我的建议是先在纸上或者脑子里把布局想清楚再用Unity的RectTransform来精确控制。一个简易播放器需要这些UI元素视频显示区域RawImage、播放/暂停按钮、停止按钮、进度条Slider、当前时间文本、总时长文本、音量滑块、全屏按钮。布局上我习惯把视频区域放在上半部分占大头控制栏放在底部一条进度条横跨整个宽度按钮和时间文本分布在进度条下方。Unity的UI系统提供了Horizontal Layout Group和Vertical Layout Group这些自动布局组件用好了能省很多手动调整的时间。但要注意自动布局组件和RectTransform的手动调整有时候会打架我的经验是大框架用锚点Anchor来适配不同分辨率局部小元素用Layout Group来排列这样既灵活又不容易乱。3. 核心细节解析与实操要点3.1 VideoPlayer的关键属性逐个拆解VideoPlayer组件的属性面板看起来东西不多但每一个都挺有讲究的。我挑几个最关键的来说。Source属性决定视频来源是Video Clip本地文件还是URL网络流。本地文件直接拖进去就行网络流需要填完整的URL地址。这里有个坑不同平台对视频格式的支持是不一样的。Windows上通常支持MP4H.264编码、WebM等Android上对H.265的支持要看具体设备iOS对H.264的支持最好。如果你要做跨平台项目最稳妥的选择是MP4容器加H.264编码分辨率不要超过1080P帧率30fps就够了。Play On Awake属性控制是否在场景加载时自动播放。做播放器的话一般关掉它由脚本在合适的时机调用Play()方法。Wait For First Frame属性决定是否等第一帧准备好再开始播放勾上它画面不会出现短暂的黑屏或白屏体验更好。Loop属性控制循环播放Playback Speed控制播放速度。这两个都很直观不多说。Render Mode前面已经讲过了选Render Texture。Target Texture就是你的RenderTexture资源。Audio Output Mode一般选Direct让声音直接通过AudioListener输出。如果你的项目用了AudioMixer来做音频管理也可以选Audio Source然后指定一个AudioSource组件来接管音频输出。Track Enabled属性用来控制哪个音轨被启用。有些视频文件有多条音轨比如不同语言的配音你可以通过这个属性来切换。不过简易播放器一般用不到这个功能。3.2 事件系统的接入用代码驱动UI更新VideoPlayer提供了一系列事件让我们可以在播放状态变化时做出响应。最常用的几个是started开始播放时触发、prepareCompleted准备完成时触发、loopPointReached播放到结尾或循环点时触发、seekCompleted跳转完成时触发。这些事件都是C#的event用来注册回调函数。比如videoPlayer.prepareCompleted OnPrepareCompleted; videoPlayer.loopPointReached OnLoopPointReached;prepareCompleted事件特别重要。VideoPlayer在播放之前需要先“准备”视频源这个准备过程是异步的尤其是网络视频可能需要几秒钟。在准备完成之前你没法获取视频的总时长videoPlayer.length也没法进行跳转操作。所以正确的做法是在prepareCompleted回调里再去更新总时长文本、启用进度条、设置Slider的最大值。loopPointReached事件在视频播放到结尾时触发。如果是循环播放模式它会在每次循环结束时触发如果不是循环模式它会在视频结束时触发一次。我们可以在这个回调里做“播放下一个视频”或者“重置播放状态”之类的逻辑。3.3 进度条拖拽的实现细节进度条是播放器里交互最复杂的部分。它既要能显示当前播放进度又要能响应用户的拖拽操作。这里的关键问题是怎么区分“用户正在拖拽”和“视频正常播放导致的进度变化”。我的做法是用一个布尔变量isDragging来标记拖拽状态。在Slider的onValueChanged事件里判断如果isDragging为true说明是用户在拖拽这时候只更新当前时间文本不调用videoPlayer.time如果isDragging为false说明是代码在更新进度条不做任何额外操作。拖拽的开始和结束通过EventTrigger或者实现IBeginDragHandler、IEndDragHandler接口来监听。开始拖拽时把isDragging设为true结束时设为false并且调用videoPlayer.time slider.value来跳转到用户选择的位置。这里有个细节videoPlayer.time的单位是秒而Slider的value范围我们一般设成0到视频总时长也是秒。这样Slider的值和时间就是一一对应的不需要做额外的换算。但要注意videoPlayer.length在视频准备好之前是0所以Slider的maxValue要在prepareCompleted之后才能正确设置。实操心得拖拽过程中如果频繁调用videoPlayer.time可能会导致画面卡顿。我的做法是拖拽过程中只更新UI显示等用户松手IEndDragHandler时才真正执行跳转。这样既流畅又准确。3.4 音量控制与静音切换音量控制看起来简单但实际做的时候有几个点要注意。VideoPlayer的音量是通过videoPlayer.SetDirectAudioVolume(0, volume)来设置的第一个参数是音轨索引第二个参数是音量值0到1之间。如果你想让音量滑块的手感更好可以考虑把Slider的value映射到对数刻度上。因为人耳对音量的感知是对数关系的线性滑块在低音量区域变化太快高音量区域变化太慢。不过对于简易播放器来说线性也够用了看个人取舍。静音切换就是记录一个lastVolume变量静音时把音量设为0取消静音时恢复lastVolume的值。这个逻辑很简单但要注意在静音状态下拖动音量滑块时应该自动取消静音否则用户会觉得滑块失灵了。4. 完整实操流程与核心代码实现4.1 场景搭建与资源准备先把项目结构理清楚。我习惯在Assets下建这几个文件夹Scenes放场景文件Scripts放C#脚本Videos放视频文件RenderTextures放RT资源Prefabs放预制体。这不是强制要求但养成好习惯后面维护起来轻松很多。场景里需要这些东西一个CanvasScreen Space - Overlay模式Canvas下面放RawImage作为视频显示区域再放一个Panel作为控制栏。控制栏里放播放按钮、停止按钮、进度条Slider、时间文本、音量Slider、全屏按钮。最后在场景里建一个空物体叫VideoController挂上VideoPlayer组件和我们的控制脚本。RenderTexture的创建在Project窗口右键 → Create → Render Texture命名成VideoRT。Size我一般设成1920x1080如果你的目标平台是移动端可以降到1280x720。Color Format选ARGB32Depth Buffer选No depth buffer视频渲染不需要深度信息。视频文件方面准备一个MP4格式的测试视频H.264编码分辨率1080P以内。把它拖到Videos文件夹里Unity会自动把它识别为VideoClip资源。4.2 VideoPlayer组件的配置选中VideoController物体在Inspector里配置VideoPlayer组件Source: Video ClipVideo Clip: 拖入你的测试视频Play On Awake: 取消勾选Wait For First Frame: 勾选Loop: 根据需求决定Playback Speed: 1Render Mode: Render TextureTarget Texture: 拖入VideoRTAudio Output Mode: Direct然后把VideoRT拖到RawImage的Texture属性上。这时候运行场景如果VideoPlayer的Play On Awake是勾选的你应该能在RawImage区域看到视频画面了。如果没看到检查一下RawImage的尺寸是不是太小或者RenderTexture有没有正确赋值。4.3 控制脚本的完整实现下面是核心控制脚本的完整代码。我把它拆成几个部分来讲方便理解每一块在做什么。using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; public class SimpleVideoPlayer : MonoBehaviour { [Header(组件引用)] public VideoPlayer videoPlayer; public RawImage videoScreen; public Slider progressSlider; public Slider volumeSlider; public Text currentTimeText; public Text totalTimeText; public Button playPauseButton; public Button stopButton; public Button fullscreenButton; public Text playPauseText; [Header(图标)] public Sprite playIcon; public Sprite pauseIcon; private bool isDragging false; private bool isFullscreen false; private float lastVolume 1f; private RenderTexture videoRT; void Start() { videoRT videoScreen.texture as RenderTexture; videoPlayer.prepareCompleted OnPrepareCompleted; videoPlayer.loopPointReached OnLoopPointReached; videoPlayer.seekCompleted OnSeekCompleted; progressSlider.onValueChanged.AddListener(OnProgressChanged); volumeSlider.onValueChanged.AddListener(OnVolumeChanged); // 注册拖拽事件 var trigger progressSlider.gameObject.AddComponentUnityEngine.EventSystems.EventTrigger(); var beginDrag new UnityEngine.EventSystems.EventTrigger.Entry(); beginDrag.eventID UnityEngine.EventSystems.EventTriggerType.BeginDrag; beginDrag.callback.AddListener((data) { isDragging true; }); trigger.triggers.Add(beginDrag); var endDrag new UnityEngine.EventSystems.EventTrigger.Entry(); endDrag.eventID UnityEngine.EventSystems.EventTriggerType.EndDrag; endDrag.callback.AddListener((data) { OnDragEnd(); }); trigger.triggers.Add(endDrag); videoPlayer.Prepare(); } void Update() { if (videoPlayer.isPlaying !isDragging) { progressSlider.value (float)videoPlayer.time; UpdateTimeText(); } } void OnPrepareCompleted(VideoPlayer vp) { progressSlider.maxValue (float)vp.length; totalTimeText.text FormatTime((float)vp.length); currentTimeText.text 00:00; progressSlider.value 0; } void OnLoopPointReached(VideoPlayer vp) { if (!vp.isLooping) { playPauseText.text 播放; progressSlider.value 0; currentTimeText.text 00:00; } } void OnSeekCompleted(VideoPlayer vp) { // 跳转完成后可以做一些额外处理 } public void TogglePlayPause() { if (videoPlayer.isPlaying) { videoPlayer.Pause(); playPauseText.text 播放; } else { videoPlayer.Play(); playPauseText.text 暂停; } } public void StopVideo() { videoPlayer.Stop(); videoPlayer.Prepare(); playPauseText.text 播放; progressSlider.value 0; currentTimeText.text 00:00; } void OnProgressChanged(float value) { if (isDragging) { currentTimeText.text FormatTime(value); } } void OnDragEnd() { isDragging false; videoPlayer.time progressSlider.value; } void OnVolumeChanged(float value) { videoPlayer.SetDirectAudioVolume(0, value); lastVolume value; } public void ToggleMute() { if (volumeSlider.value 0) { lastVolume volumeSlider.value; volumeSlider.value 0; } else { volumeSlider.value lastVolume; } } public void ToggleFullscreen() { isFullscreen !isFullscreen; if (isFullscreen) { videoScreen.rectTransform.anchorMin Vector2.zero; videoScreen.rectTransform.anchorMax Vector2.one; videoScreen.rectTransform.offsetMin Vector2.zero; videoScreen.rectTransform.offsetMax Vector2.zero; } else { videoScreen.rectTransform.anchorMin new Vector2(0.05f, 0.2f); videoScreen.rectTransform.anchorMax new Vector2(0.95f, 0.95f); videoScreen.rectTransform.offsetMin Vector2.zero; videoScreen.rectTransform.offsetMax Vector2.zero; } } void UpdateTimeText() { currentTimeText.text FormatTime((float)videoPlayer.time); } string FormatTime(float seconds) { int min Mathf.FloorToInt(seconds / 60); int sec Mathf.FloorToInt(seconds % 60); return string.Format({0:00}:{1:00}, min, sec); } }这段代码涵盖了播放、暂停、停止、进度拖拽、音量控制、静音、全屏切换这些核心功能。下面我挑几个关键点展开说一下。Start()方法里先获取RenderTexture的引用然后注册VideoPlayer的各种事件回调。注意videoPlayer.Prepare()是手动触发的因为我们把Play On Awake关掉了。Prepare的过程是异步的完成后会触发prepareCompleted事件。Update()方法里每帧检查是否正在播放且没有在拖拽如果是就更新进度条的值和时间文本。这里用videoPlayer.time获取当前播放时间单位是秒。OnPrepareCompleted回调里设置进度条的最大值和总时长文本。这个回调只会在Prepare完成后触发一次所以是初始化进度条的最佳时机。OnDragEnd方法在用户松开进度条时调用把isDragging设回false然后执行跳转。跳转是通过设置videoPlayer.time实现的这是一个异步操作跳转完成后会触发seekCompleted事件。ToggleFullscreen方法通过修改RawImage的锚点来实现全屏切换。全屏时锚点设为(0,0)到(1,1)铺满整个Canvas退出全屏时恢复到原来的锚点范围。这种方式比修改Camera的viewport要简单得多而且不会影响其他UI元素的显示。4.4 按钮事件的绑定在Unity Editor里把上面脚本里的public方法绑定到对应按钮的OnClick事件上。具体操作是选中按钮 → 在Inspector的Button组件里找到OnClick() → 点加号添加一个事件 → 把VideoController物体拖到对象槽里 → 在下拉菜单里选择对应的方法。播放/暂停按钮绑定TogglePlayPause停止按钮绑定StopVideo全屏按钮绑定ToggleFullscreen。音量滑块和进度滑块的onValueChanged事件已经在代码里用AddListener注册了不需要在Editor里再绑。实操心得按钮上的文字用Text组件来显示播放状态显示“暂停”暂停状态显示“播放”。这个逻辑在TogglePlayPause和StopVideo方法里都有处理。如果你用的是图标而不是文字就换成Image组件在代码里切换Sprite。5. 常见问题与排查技巧实录5.1 视频播放黑屏或白屏这是最常见的问题原因可能有以下几种。第一种是RenderTexture没有正确赋值检查RawImage的Texture属性和VideoPlayer的Target Texture属性是不是指向同一个RT资源。第二种是视频还没准备好就开始播放了确保在prepareCompleted之后再调用Play()。第三种是视频格式不支持换一个标准的H.264 MP4文件试试。第四种是RenderTexture的尺寸和视频分辨率不匹配导致画面被拉伸或裁剪调整RT的Size到合适值。还有一种比较隐蔽的情况RawImage的Color属性被设成了透明或者黑色。检查一下RawImage组件的Color是不是白色255,255,255,255如果不是就改回来。5.2 进度条拖拽后视频卡住不动这个问题通常是因为拖拽结束后没有正确恢复播放状态。检查OnDragEnd方法里是不是只设置了videoPlayer.time但没有调用Play()。如果视频之前是暂停状态设置time之后需要手动调用Play()才能继续播放。如果视频之前是播放状态设置time之后VideoPlayer会自动继续播放不需要额外操作。另一个可能的原因是isDragging变量没有被正确重置。如果IEndDragHandler没有被触发比如拖拽过程中鼠标移出了Slider区域isDragging会一直保持true导致Update()里的进度更新逻辑被跳过。解决办法是在OnDragEnd里强制把isDragging设为false并且加一个超时保护机制。5.3 移动端上视频无法播放移动端的问题通常和权限、路径、编码格式有关。Android上需要确保视频文件放在StreamingAssets文件夹里并且用Application.streamingAssetsPath来拼接路径。iOS上对视频编码的要求更严格必须是H.264编码的MP4文件而且分辨率不能超过设备支持的最大值。还有一个容易被忽略的点Android 9及以上版本默认禁止明文HTTP流量如果你的视频是通过URL加载的需要确保URL是HTTPS的或者在AndroidManifest里配置网络安全策略。5.4 内存占用过高视频播放是内存消耗大户尤其是高分辨率视频。几个优化方向降低RenderTexture的分辨率1080P的视频用720P的RT来渲染肉眼几乎看不出差别但内存能省一半。及时释放不再使用的视频资源调用videoPlayer.Stop()之后把VideoClip引用置空让GC能回收内存。避免同时加载多个视频文件如果确实需要切换视频先卸载当前的再加载新的。下面这张表整理了我实际项目中遇到过的典型问题和对应的解决方案方便快速查阅问题现象可能原因排查方法解决方案画面全黑RT未赋值或Color为透明检查RawImage和VideoPlayer的RT引用重新赋值RTColor设为白色画面全白Wait For First Frame未勾选检查VideoPlayer属性面板勾选Wait For First Frame有声音无画面Render Mode设置错误检查Render Mode是否为Render Texture改为Render Texture并指定RT有画面无声音Audio Output Mode设置错误检查Audio Output Mode改为Direct或指定AudioSource拖拽后卡住isDragging未重置在OnDragEnd里打印日志强制重置isDragging并调用Play移动端崩溃视频编码不兼容用系统播放器测试同一文件转码为H.264 MP4内存暴涨RT分辨率过高在Profiler里查看RT内存降低RT分辨率至720P进度条不同步length在Prepare前为0在prepareCompleted里打印length在prepareCompleted后设置maxValue5.5 几个容易被忽略的细节视频的音频输出默认走的是AudioListener如果你的场景里有多个AudioListener比如多个Camera会出现声音重叠或无声的问题。确保场景里只有一个激活的AudioListener。VideoPlayer的time属性在跳转时是异步的设置之后不会立即生效。如果你在设置time之后马上读取time拿到的还是旧值。需要等seekCompleted事件触发后才能获取到新的时间。全屏切换时如果RawImage的父物体有Layout Group组件修改锚点可能不会生效因为Layout Group会覆盖锚点设置。解决办法是把RawImage从Layout Group里移出来或者临时禁用Layout Group组件。提示在Editor里调试视频播放时如果发现视频画面在Game视图里显示正常但在Scene视图里看不到这是正常的。VideoPlayer渲染到RenderTexture的内容不会在Scene视图里显示只看Game视图就行。6. 功能扩展与进阶方向6.1 播放列表与自动切换简易播放器做出来之后很自然的扩展需求就是支持多个视频的播放列表。实现思路是维护一个VideoClip数组和一个当前索引在loopPointReached事件里判断是否还有下一个视频如果有就加载并播放如果没有就停止或循环回第一个。加载新视频的流程是先调用videoPlayer.Stop()然后设置videoPlayer.clip nextClip再调用videoPlayer.Prepare()等prepareCompleted触发后调用Play()。注意每次切换视频都要重新Prepare不能直接改clip就Play。播放列表的UI可以用一个ScrollView来展示每个列表项显示视频名称和时长点击时切换到对应的视频。这部分UI工作量比较大但逻辑本身不复杂。6.2 视频画面作为3D物体纹理把视频画面贴到3D物体表面上是Unity播放器的一个特色功能。实现方式很简单把RenderTexture赋给物体的Material的Main Texture属性。这样视频就会实时渲染到物体表面你可以做出电视机、广告牌、投影幕布之类的效果。如果想让视频画面有发光效果可以把Material的Shader换成Unlit/Texture然后在视频画面上叠加一个Bloom后期效果。如果想让视频画面有曲面效果可以把RenderTexture赋给一个曲面Mesh的Material视频会自动适配曲面的UV。6.3 字幕与弹幕系统字幕功能的实现思路是解析SRT或ASS字幕文件把每条字幕的时间戳和文本存到一个数据结构里然后在Update()里根据当前播放时间查找对应的字幕文本并显示到UI上。SRT格式比较简单自己写个解析器就行ASS格式复杂一些可以考虑用现成的开源库。弹幕系统稍微复杂一点需要管理弹幕的生成、移动、销毁。每条弹幕是一个UI元素从屏幕右侧生成以一定速度向左移动移出屏幕左侧后销毁。弹幕的发送时间要和视频时间关联这样拖动进度条时弹幕也能跟着跳转。6.4 性能优化建议视频播放的性能开销主要在解码和渲染两个环节。解码方面VideoPlayer会自动选择硬件解码还是软件解码一般来说硬件解码效率更高但兼容性稍差。你可以在VideoPlayer的Inspector里看到当前使用的是哪种解码方式。渲染方面如果视频画面不需要每帧更新比如暂停状态可以把RawImage的enabled设为false来跳过渲染。如果视频画面被其他UI遮挡也可以用Canvas Group的alpha来控制透明度减少overdraw。还有一个技巧是预加载。如果播放列表里有多个视频可以在当前视频播放到后半段时提前Prepare下一个视频这样切换时几乎无延迟。不过要注意内存占用预加载的视频会占用额外的解码资源。7. 我踩过的那些坑做播放器的过程中有几个坑让我印象特别深这里分享出来希望能帮你省点时间。第一个坑是RenderTexture的释放问题。我在一个项目里频繁切换视频每次切换都新建一个RenderTexture结果跑了半小时后内存直接爆了。后来才发现RenderTexture是UnityEngine.Object的子类不会自动被GC回收必须手动调用Destroy(rt)或者rt.Release()来释放。正确的做法是复用同一个RenderTexture而不是每次新建。第二个坑是视频时长获取的时机。我一开始在Start()里就直接读videoPlayer.length来设置进度条的最大值结果拿到的永远是0。后来查文档才知道length只有在Prepare完成后才有值。这个坑其实很好避免只要记住“所有和视频元数据相关的操作都要放在prepareCompleted之后”。第三个坑是移动端的视频路径问题。在Editor里用videoPlayer.url file:// Application.dataPath /Videos/test.mp4能正常播放打包到Android上就黑屏。原因是Android上Application.dataPath的路径和Editor里不一样而且Android不支持直接通过文件路径访问APK内的资源。正确的做法是把视频放在StreamingAssets文件夹里用Application.streamingAssetsPath来拼接路径Android上还需要用UnityWebRequest来读取。第四个坑是音频和视频不同步。这个问题通常出现在低性能设备上原因是视频解码和音频解码的速度不一致。解决办法是降低视频分辨率或帧率或者在VideoPlayer里调整Playback Speed来强制同步。Unity的VideoPlayer内部有音视频同步机制但设备性能太差的话还是会出问题。第五个坑是Slider的拖拽事件和点击事件冲突。Slider默认支持点击轨道跳转但如果你同时实现了拖拽逻辑点击轨道时可能会触发两次跳转。解决办法是在Slider的onValueChanged里判断isDragging只有非拖拽状态下的值变化才执行跳转。或者在Slider上覆盖一个透明的Image来拦截点击事件只允许通过拖拽手柄来改变进度。这些坑说到底都是对VideoPlayer的异步特性和平台差异理解不够深入导致的。多做几个项目、多踩几次坑自然就记住了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑