XOPlayer 阶段性总结——使用 Unity 的 VideoPlayer 组件开发全景播放器
现在还只是一个简陋的单 Scene 的应用,但已具备基本的视频播放和播放控制能力:
- 本地视频播放
- 进度显示、跳转
- 暂停、恢复、停止
- 音量调节
- 视频源类型切换
播放界面如下:
0. 开发、运行环境
- VS2019 Community。安装时务必勾选 Visual Studio Tools for Unity。
- Unity 2018.4.14f1。运行时修改菜单 Edit-Preferences 的 External Tools 选项页中的 External Script Editor 项值,关联到 VS2019,方能在 VS2019 中正确识别 Unity 工程和源码。
- AMD Ryzen7 1700 + GTX 1070 + DDR4 16GB
1. UI 布局一览
模块化的组合功能均以 Prefab 实现(蓝色组件)。Prefab 既提供了一种可复用手段(2次出现的 Slider 以同一 Prefab 实例化),又契合了高内聚,低耦合的设计理念。我的理解是:相比于直接在 Scene 中布局复杂界面,Prefab 是更优选择,能用则用。
注意 VideoPlayer 所在位置,出现在根 Canvas 之外,原因参考 2. VideoPlayer。
2. VideoPlayer 相关
以流媒体角度解读 VideoPlayer 的话,VideoPlayer 是一个 demuxers,decoders 和 renders 的组合。注意这里说的渲染,非指渲染到 UI,而是以某种标准格式渲染到内存或显存,从内存/显存渲染到 UI 是用户职责。所以,VideoPlayer 被设计为非 UI 组件,这也是上文提到 VideoPlayer 为什么出现在根 Canvas 之外的原因。
VideoPlayer 提供了多种 Render Mode,适配不同的渲染场景,XOPlayer 只用到了 Render Texture 一种。此纹理须在脚本中动态创建并关联到 VideoPlayer :
1 | // make sure: mPlayer.prepareCompleted += onPrepareCompleted. |
XOPlayer 支持普通视频和全景视频的播放,两者在渲染到 UI 时大不相同:
- 普通视频渲染到 Image 组件
- 全景视频渲染到全局 Skybox 或 Sphere
此逻辑应补充在 RenderTexture 创建后、视频播放前:
1 | // attaching texture to material |
- video2DMaterial 是一个预先定义的材质,Shader 采用 “Unlit/Texture”
- videoPanoramicMaterial 是一个预先定义的材质,Shader 采用 “Skybox/Panoramic”
全景视频的视角又分为 180° 和 360°,可以通过预先定义 2 个不同的材质,Image Type 分别设置为 180 Degrees 和 360 Degrees 实现;也可以只创建一个材质,脚本中动态切换 Image Type :
1 | if (mMode == PlayMode.kPanoramic180) // 180 degrees panoramic videos |
3. 其它
3.1 全景视频的视角旋转
PC 上通过鼠标拖拽驱动视角旋转。天空盒内,只需要将 Main Camera 按其自身 position 沿 X/Y 轴旋转即可:
1 | private float rotateSpeed = 2.0f; |
3.2 鼠标点击任意处显示/隐藏工具栏
因为绝大部分逻辑均在 VideoPlayer 组件的脚本 PlayerManager 内实现,所以第一反应是通过 PlayerManager 实现 IPointerDownHandler, IPointerUpHandler 接口即可。
结论是不可以。上文已经提及,VideoPlayer 不是 UI 组件,没有 Rect Transform,所以它其实是不可能接受鼠标事件的。
所以需要在根 Canvas 上添加脚本,并实现 IPointerDownHandler, IPointerUpHandler :
1 | public class PointerHandler : MonoBehaviour, IPointerDownHandler, IPointerUpHandler |
3.3 普通视频播放相关
普通视频的调用链是这样的:
VideoPlayer——RenderTexture——video2DMaterial——normalPlayer
其中 RenderTexture 是动态创建的,直接导致 normalPlayer.GetComponent<Image>().material
属性不能通过 Inspector 面板关联(指 video2DMaterial——normalPlayer
),甚至在 Start()
中赋值也不行。Image 画面不会随播放更新。没有找到相关资料支持,但是我反推的结论是:
1 | mVideoTexture = new RenderTexture((int)mPlayer.width, (int)mPlayer.height, 0, RenderTextureFormat.ARGB32); |
这三句的调用顺序是不能变动的。即需要先完备材质信息,才能将材质赋值给 Image (或其它 UI 组件 ?)。
停止播放时存在类似问题:
1 | mPlayer.Stop(); |
结合创建时的三句代码反推,结论是:再次播放时 VideoTexture 是重新创建的,但是 video2DMaterial 不是,所以 normalPlayer 跟踪不到这个间接变化。
得出一个不知道对错的结论:一个调用链上的对象,最好要么全部静态创建,要么全部动态创建。
4. Issues
- 基本功能缺失:快进、快退、循环
- 180° 全景视频的旋转角度是 360°
- 进度条 Slider 不能拖动(OnValueChanged 死循环)
- 全景视频变糊(存疑)