原文
https://medium.com/pinterest-engineering/managing-videos-on-android-f59da9601d5f
2016年Pinterest安卓应用上发布的视频模块,其目标是使得应用能够提供无缝的视频体验。包括支持在每个屏幕上同时播放多个视频,并且通过滚动出屏幕自动暂停播放的方式来动态地控制视频的播放状态以及同时播放的视频数量。
很快我们发现其实需要应对的技术挑战有很多,例如:
- 管理当前所有可用视频的播放状态
- 了解视频在屏幕上的可见率
- 为我们的开发人员提供易于使用的视频组件
- 随着工作的进行,我们逐渐调整视频架构来满足这些需求,下面我们将在最新的视频模块中深入探讨如何应对这些挑战。
视频管理
从更高的层次上来看,我们需要构建一个组件,这个组件需要感知屏幕上所有可用的视频实例(即视图)以及其相关的surfaces(即视频片段)。管理surfaces对于监控应用于surfaces的子对象的生命周期状态(即onStart()等)至关重要,并且避免在使用者层上添加过多代码来将最新状态更改应用到视图。
为了跟踪这些关键的生命周期事件,Android框架向我们提供了屏幕显示内容的当前状态以及视觉上影响我们应用程序的任何更改。我们监测的关键生命周期事件是UI附件调用(例如onAttachedToWindow())以及主机屏幕何时更改其显示状态(例如onPause()等)。
使用这些回调方法,我们尝试记录已提供有效视频URL的所有视频。这将为我们提供当前范围内可用的视频的初始列表。
在视频框架的第一个迭代中,我们依靠客户端代码本身调用这些调用,但是我们发现这是不可扩展的。因为它在构建视频功能时增加了更多的复杂性。取而代之的方法是,我们通过构建需要传入基础视频组件的方法,提取了在VideoManager之后注册视频的回调方法。从那里,VideoManager将在幕后进行适当的计算。由于它现在才可以“开箱即用”地工作,因此消除了观众对视频记录过程已经具有预定义知识的需求。
改进前
// FooBarFragment.class for FooBar feature
override fun onResume() {
super.onResume()
// Required by consumers to implement
videoView?.apply {
viewability = Viewability.FullyVisible
onActivate()
onViewCompletelyVisible()
}
}
override fun onPause() {
// Required by consumers to implement
videoView?.apply {
viewability = Viewability.NotVisible
onDeactivate()
}
super.onPause()
}
改进后
// BaseFragment.class implemented by all screens
override fun onResume() {
super.onResume()
videoManager.onResume(this)
}
override fun onPause() {
videoManager.onPause(this)
super.onPause()
}
// VideoManager.class internally
override fun onResume(videoSurface: VideoViewSurface) {
videoSurface.videoViews.forEach {
registerVideo(it)
}
}
override fun onPause(videoSurface: VideoViewSurface) {
videoSurface.videoViews.forEach {
unregisterVideo(it)
}
}
保留这个视频列表让我们可以根据应用程序的当前可见性来动态地设置播放状态。同时这个方法还提供了基于在视频记录时传递的某些元数据属性动态更改之类其他功能的灵活性。
例如,我们可能希望所有视频广告都自动播放,但仅限于在同一片段上自动播放1个有机视频(即创作者生成的内容)。通过检查在单个视频上记录的元数据,我们可以将这些限制应用于UI层。
我们还提取了所有Pinterest特定的分析代码,用以来聚焦在视频管理器(管理和播放视频)功能上,同时让这个管理组件和应用程序之间保持独立。
计算可视性
可视性定义为在屏幕上显示的UI组件的可见区域的百分比。此度量对于我们了解当前显示给用户的内容至关重要。有了这些信息,我们就能为合作伙伴收集有关其内容参与度的信息。
在常见情况下,由于VideoManager保留对所有活动视频的引用,因此我们可以跟踪视图的确切坐标(即getLocationInWindow())和设备的屏幕尺寸(以像素为单位)(请参见DisplayMetrics),以推断其在屏幕上的可见性。
我们还通过以下方式处理重叠的UI组件:
向消费者提供包括一系列``障碍物’'视图的选项,这些视图可能会覆盖我们的基础视频(例如工具栏,浮动按钮等)
显示弹出窗口的回调(即onWindowFocusChanged())屏幕滚动组件或UI组件不在屏幕上(请参阅RecyclerView监听器)
屏幕上显示视频表面时的其他回调(即onResume()等)
为开发人员打造的内容
虽然我们希望减少开发人员面临的视频管理复杂性,但是这其中最大的困难就是采用新的视频界面。因此,我们都抽象出了视频设置的复杂性以及Google PlayerView提供的UI组件的使用:
改进前
// FooBar video feature, requires custom FooBarVideoView.class of 100+ lines
object : FooBarVideoView(
context, // application context
analytics, // Analytics object
url, // video url
uid, // unique ID
false // isAd flag
) {
// configuration flag for custom setup (mute, autoplay, controller, etc.)
override val videoConfiguration = VideoConfiguration.FOO_BAR
}.apply {
shouldLoop = true
videoAspectRatio = aspectRatio
render(videoMetaData) // loads video, videoMetaData contains: url, isAd, uid
}
改进后
// Foobar video feature, no custom class required just set flags
PinterestVideoView(context).apply {
// Optional params for setup/customization
this.analytics = pinterestAnalytics
this.mute = false
this.autoPlay = true
this.alwaysAutoplay = true
this.alwaysPlay = true
this.showMute = true
this.looping = true
this.bufferingRule = SHOW_BUFFERING_ALWAYS
}.apply {
render(videoMetaData) // loads video, videoMetaData contains: url, isAd, uid
}
视频基础架构的另一个复杂性是实际的VideoManager体系结构本身。在我们的重写中,我们将大多数旧组件合并为仅支持正常运行的VideoManager的核心部分。
改进前
![](http://www.ctiforum.com/uploadfile/2019/0929/20190929093221594.jpg)
改进后
![](http://www.ctiforum.com/uploadfile/2019/0929/20190929093139781.jpg)
我们新的VideoManager体系结构为事件和组件之间的相互关系提供了清晰的层次结构。这不仅在纸面上看起来不错,而且仅重构一项就删除了约4,500行代码(不到原始实现大小的1/3)
展望
建立适当的“视频管理”是一个漫长而艰巨的过程,但是多年来,我们已经构建了一些真正经过改进的东西,以帮助简化我们的开发流程和Pinner体验。将来,我们希望开源我们的工作,以便其他开发人员可以为正在进行的处理动态视频回放做出贡献。我们将继续迭代我们的视频客户端架构,以应对新的挑战,以期为Pinners和开发人员提供令人愉悦的视频体验。来源:LiveVideoStack