Pinterest:Android系统上的视频管理

Pinterest通过在Android应用中添加适当的视频管理,在为用户提供更加流畅的视频体验的同时,尽可能的为开发人员提供易于使用的视频组件,简化其工作流程。 本文来自Pinterest工程博客。

文 / Grey Skold

译 / 屈健宁

原文

https://medium.com/pinterest-engineering/managing-videos-on-android-f59da9601d5f

2016年Pinterest安卓应用上发布的视频模块,其目标是使得应用能够提供无缝的视频体验。包括支持在每个屏幕上同时播放多个视频,并且通过滚动出屏幕自动暂停播放的方式来动态地控制视频的播放状态以及同时播放的视频数量。

很快我们发现其实需要应对的技术挑战有很多,例如:

  • 管理当前所有可用视频的播放状态

  • 了解视频在屏幕上的可见率

  • 为我们的开发人员提供易于使用的视频组件

随着工作的进行,我们逐渐调整视频架构来满足这些需求,下面我们将在最新的视频模块中深入探讨如何应对这些挑战。

视频管理

从更高的层次上来看,我们需要构建一个组件,这个组件需要感知屏幕上所有可用的视频实例(即视图)以及其相关的surfaces(即视频片段)。管理surfaces对于监控应用于surfaces的子对象的生命周期状态(即onStart()等)至关重要,并且避免在使用者层上添加过多代码来将最新状态更改应用到视图。

为了跟踪这些关键的生命周期事件,Android框架向我们提供了屏幕显示内容的当前状态以及视觉上影响我们应用程序的任何更改。我们监测的关键生命周期事件是UI附件调用(例如onAttachedToWindow())以及主机屏幕何时更改其显示状态(例如onPause()等)。

使用这些回调方法,我们尝试记录已提供有效视频URL的所有视频。这将为我们提供当前范围内可用的视频的初始列表。

在视频框架的第一个迭代中,我们依靠客户端代码本身调用这些调用,但是我们发现这是不可扩展的。因为它在构建视频功能时增加了更多的复杂性。取而代之的方法是,我们通过构建需要传入基础视频组件的方法,提取了在VideoManager之后注册视频的回调方法。从那里,VideoManager将在幕后进行适当的计算。由于它现在才可以“开箱即用”地工作,因此消除了观众对视频记录过程已经具有预定义知识的需求。

改进前

1 // FooBarFragment.class for FooBar feature 2 3 4 5 6 7 8 9 override fun onResume() { 10 11 12 13       14 15 16 17 super.onResume() 18 19 20 21       22 23 24 25 // Required by consumers to implement 26 27 28 29      videoView?.apply { 30 31 32 33           viewability = Viewability. 34 35 36 37 FullyVisible          onActivate()          onViewCompletelyVisible()     }}override fun onPause() { 38 39 40 41       42 43 44 45 // Required by consumers to implement 46 47 48 49      videoView?.apply { 50 51 52 53           viewability = Viewability. 54 55 56 57 NotVisible          onDeactivate()     }     super.onPause()} 58 59 60 61

改进后

1 // BaseFragment.class implemented by all screens 2 3 4 5 6 7 8 9 override fun onResume() { 10 11 12 13       14 15 16 17 super.onResume() 18 19 20 21      videoManager.onResume( 22 23 24 25 this) 26 27 28 29 } 30 31 32 33 34 35 36 37 override fun onPause() { 38 39 40 41      videoManager.onPause( 42 43 44 45 this) 46 47 48 49       50 51 52 53 super.onPause() 54 55 56 57 } 58 59 60 61 62 63 64 65 // VideoManager.class internally 66 67 68 69 70 71 72 73 override fun onResume(videoSurface: VideoViewSurface) { 74 75 76 77      videoSurface.videoViews.forEach { 78 79 80 81           registerVideo(it) 82 83 84 85      } 86 87 88 89 } 90 91 92 93 94 95 96 97 override fun onPause(videoSurface: VideoViewSurface) { 98 99 100 101      videoSurface.videoViews.forEach { 102 103 104 105           unregisterVideo(it) 106 107 108 109      } 110 111 112 113 } 114 115 116 117 118 119 120 121

保留这个视频列表让我们可以根据应用程序的当前可见性来动态地设置播放状态。同时这个方法还提供了基于在视频记录时传递的某些元数据属性动态更改之类其他功能的灵活性。

例如,我们可能希望所有视频广告都自动播放,但仅限于在同一片段上自动播放1个有机视频(即创作者生成的内容)。通过检查在单个视频上记录的元数据,我们可以将这些限制应用于UI层。

我们还提取了所有Pinterest特定的分析代码,用以来聚焦在视频管理器(管理和播放视频)功能上,同时让这个管理组件和应用程序之间保持独立。

计算可视性

可视性定义为在屏幕上显示的UI组件的可见区域的百分比。此度量对于我们了解当前显示给用户的内容至关重要。有了这些信息,我们就能为合作伙伴收集有关其内容参与度的信息。

在常见情况下,由于VideoManager保留对所有活动视频的引用,因此我们可以跟踪视图的确切坐标(即getLocationInWindow())和设备的屏幕尺寸(以像素为单位)(请参见DisplayMetrics),以推断其在屏幕上的可见性。

我们还通过以下方式处理重叠的UI组件:

  • 向消费者提供包括一系列``障碍物’'视图的选项,这些视图可能会覆盖我们的基础视频(例如工具栏,浮动按钮等)

  • 显示弹出窗口的回调(即onWindowFocusChanged())屏幕滚动组件或UI组件不在屏幕上(请参阅RecyclerView监听器)

  • 屏幕上显示视频表面时的其他回调(即onResume()等)

为开发人员打造的内容

虽然我们希望减少开发人员面临的视频管理复杂性,但是这其中最大的困难就是采用新的视频界面。因此,我们都抽象出了视频设置的复杂性以及Google PlayerView提供的UI组件的使用:

改进前

1 // FooBar video feature, requires custom FooBarVideoView.class of 100+ lines 2 3 4 5 object : FooBarVideoView( 6 7 8 9      context,      10 11 12 13 // application context 14 15 16 17      analytics,    18 19 20 21 // Analytics object 22 23 24 25      url,          26 27 28 29 // video url 30 31 32 33      uid,          34 35 36 37 // unique ID 38 39 40 41       42 43 44 45 false         46 47 48 49 // isAd flag 50 51 52 53 ) { 54 55 56 57       58 59 60 61 // configuration flag for custom setup (mute, autoplay, controller, etc.) 62 63 64 65      override val videoConfiguration = VideoConfiguration.FOO_BAR 66 67 68 69 }.apply { 70 71 72 73      shouldLoop =  74 75 76 77 true 78 79 80 81      videoAspectRatio =  82 83 84 85 aspectRatio     render(videoMetaData) // loads video, videoMetaData contains: url, isAd, uid} 86 87 88 89

改进后

1 // Foobar video feature, no custom class required just set flags 2 3 4 5 PinterestVideoView(context).apply { 6 7 8 9       10 11 12 13 // Optional params for setup/customization 14 15 16 17       18 19 20 21 this.analytics = pinterestAnalytics 22 23 24 25       26 27 28 29 this.mute =  30 31 32 33 false 34 35 36 37       38 39 40 41 this.autoPlay =  42 43 44 45 true 46 47 48 49       50 51 52 53 this.alwaysAutoplay =  54 55 56 57 true 58 59 60 61       62 63 64 65 this.alwaysPlay =  66 67 68 69 true 70 71 72 73       74 75 76 77 this.showMute =  78 79 80 81 true 82 83 84 85       86 87 88 89 this.looping =  90 91 92 93 true 94 95 96 97       98 99 100 101 this.bufferingRule = SHOW_BUFFERING_ALWAYS 102 103 104 105 }.apply { 106 107 108 109      render(videoMetaData)  110 111 112 113 // loads video, videoMetaData contains: url, isAd, uid 114 115 116 117 } 118 119 120 121 122 123 124 125

视频基础架构的另一个复杂性是实际的VideoManager体系结构本身。在我们的重写中,我们将大多数旧组件合并为仅支持正常运行的VideoManager的核心部分。

改进前

改进后

我们新的VideoManager体系结构为事件和组件之间的相互关系提供了清晰的层次结构。这不仅在纸面上看起来不错,而且仅重构一项就删除了约4,500行代码(不到原始实现大小的1/3)

展望

建立适当的“视频管理”是一个漫长而艰巨的过程,但是多年来,我们已经构建了一些真正经过改进的东西,以帮助简化我们的开发流程和Pinner体验。将来,我们希望开源我们的工作,以便其他开发人员可以为正在进行的处理动态视频回放做出贡献。我们将继续迭代我们的视频客户端架构,以应对新的挑战,以期为Pinners和开发人员提供令人愉悦的视频体验。

LiveVideoStackCon 2019深圳讲师招募

12月13-14日,LiveVideoStackCon首次来到深圳,将全球前沿多媒体技术实践与深圳本地产业结合,触发技术与商业灵感。欢迎将你的技术实践、踩坑与填坑经历、技术与商业创业的思考分享出来。请将个人资料和话题信息邮件到 speaker@livevideostack.com 或点击【阅读原文】了解成为LiveVideoStackCon讲师的权益与义务,我们会在48小时内回复。

LiveVideoStack 秋季招聘

LiveVideoStack正在招募编辑/记者/运营,与全球顶尖多媒体技术专家和LiveVideoStack年轻的伙伴一起,推动多媒体技术生态发展。同时,也欢迎你利用业余时间、远程参与内容生产。了解岗位信息请在BOSS直聘上搜索“LiveVideoStack”,或通过微信“Tony_Bao_”与主编包研交流。

本文分享自微信公众号 - LiveVideoStack(livevideostack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Android So动态加载 优雅实现与原理分析

背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我