前言

方案实现

原始需求

隐藏需求

方案制定

具体实现

总结

附录

前言

互联网内容已经逐渐从图文阅读往如今火热的短视频更迭,某种程度上短视频有着图文所没有的优势和不可替代性,降低了自我表达的门槛。近期迭代做了个短视频列表滚动自动播放的需求,上线了一段时间。觉得略有趣,简单分享下方案。

本文提供了 Demo ,将方案进行简化处理,只包含核心的功能实现。

本文的 Demo 附在文末

方案实现

介绍下整个方案的思考和实现的一些过程。

原始需求

简化如下:

自动播放条件

Wifi环境下,当视频中心位置从下往上越过屏幕的2/3位置,或从上往下越过屏幕的1/3位置时,视频开始自动播放;

停止播放条件

当视频中心位置离开可见区域时,视频自动停止播放。若下一个视频满足开始自动播放的条件,则上一个视频自动停止播放。

手动播放

点播放按钮可手动开始播放。开始播放某视频后,其他视频停止播放。

隐藏需求

原始需求背后需要考虑的其他情况:

页面进入/离开的处理

App进入后台/返回前台的处理

从一个视频列表跳转到另一个视频列表的处理

页面包含安全区的处理

列表滚动播放的性能问题

视频循环播放/静音功能

上拉加载更多/下拉刷新等动作触发的滚动的处理

列表 cell 的复用问题

方案制定

主要考虑了以下一些方面:

优先考虑的是性能问题,同一个列表中尽量只有一个视频控件

尽量降低方案的侵入性,目前有多个现有列表需要支持该功能,涉及到多个 controller 和多个 cell ,侵入性低也利于改动和维护

隐藏需求会影响到自动播放逻辑的调用时机

大致思路如下:

由一个类管理整个 App 的视频滚动播放的相关逻辑,包括视频组件。

由 controller 监控滚动,触发管理类进行处理。管理类计算当前符合自动播放的视频,播放并将视频组件嵌入 cell 中。

为了降低侵入性,采用协议实现管理类和 controller、管理类和 cell 间的通信。也利于改动逻辑时只改动到管理类,而不是牵涉各个调用处和 cell。

隐藏需求处理:

页面进入/离开的处理、从一个视频列表跳转到另一个视频列表的处理

在 controller 的生命周期中去调用管理类方法进行处理。离开时停止当前播放中的视频,进入时播放当前列表的视频。进入时判断数据源是否已经获取,已经获取则调用播放。另外在数据源获取后判断是否已经 Appear,是则调用播放。用变量标志避免两个逻辑重复调用。

App进入后台/返回前台的处理

App 进入后台/返回前台不会调用 controller 的生命周期,需要另外处理。

页面包含安全区的处理

在计算视频的相对位置时,将安全区考虑在内进行计算。

列表滚动播放的性能问题

测试 列表滚动停止时 、 实时滚动时 、 滚动降速到一定速度时 等情况下,调用自动播放逻辑的性能和体验,

视频循环播放/静音功能

视频播放完毕的事件需要通过监听 NSNotification.Name.AVPlayerItemDidPlayToEndTime 实现,放在管理类中进行处理。

上拉加载更多/下拉刷新等动作触发的滚动的处理

在这两种情况下,需要停止页面的逻辑调用,直到数据源返回成功或失败为止。

列表 cell 的复用问题

复用时需要重新布置frame、清空数据源等。

具体实现

定义管理类 VideoListAutoPlayManager

class VideoListAutoPlayManager {

private init() {

playerVC.player = AVPlayer()

playerVC.showsPlaybackControls = false

playerVC.view.backgroundColor = UIColor.clear

}

static let shared = VideoListAutoPlayManager()

private var playerVC: AVPlayerViewController = AVPlayerViewController()

private var preOffsetY: CGFloat = 0

private var currentPlayingView: VideoPlayable?

}

需要保存一些信息和状态,所以定义成单例。

AVPlayerViewController 自带控制条,需要隐藏。

视频播放时背景从黑色开始,会导致出现先看到封面,然后黑色,然后再播放视频的问题,设置为透明会让从封面到视频的过渡自然。

preOffsetY 记录当前滚动的 UIScrollView 的 contentOffset.y 。用于在多个视频满足自动播放时,通过判断滚动方向来决定选取哪个视频自动播放。

currentPlayingView 记录当前播放中的 cell。用于通知上一个播放的 cell 即将停止播放视频,方便 cell 处理另外的逻辑。

定义协议

protocol VideoPlayable: UIView {

var viewToContainVideo: UIView {get}

var urlToPlay: URL? {get}

func videoStatusChanged(changeTo isPlaying: Bool)

}

protocol VideoListPlayable: UIScrollView {

var visibleViews: [VideoPlayable] {get}

}

extension UITableView: VideoListPlayable {

var visibleViews: [VideoPlayable] {

let views: [VideoPlayable] = visibleCells.compactMap({ $0 as? VideoPlayable })

return views

}

}

extension UICollectionView: VideoListPlayable {

var visibleViews: [VideoPlayable] {

let views: [VideoPlayable] = visibleCells.compactMap({ $0 as? VideoPlayable })

return views

}

}

第一个协议,VideoPlayable,是存放视频的 cell 需要实现的。实现协议返回需要包含视频的 view ,需要播放的视频 URL,以及用于 VideoListAutoPlayManager 通知 cell 处理视频播放状态变化的调用方法。

第二个协议,VideoListPlayable,是滚动列表需要实现的。实现协议返回滚动列表当前可见的 cell,用于 VideoListPlayable 去判断哪些视频需要自动播放。

两个协议都遵循某个类,UIView 或 UIScrollView,是有些取巧,方便后面取 frame 等。也可以不遵循,然后在协议中返回需要的数据即可。

另外为 UITableView 和 UICollectionView 做了默认实现。

触发滚动播放的处理

func scrollViewDidScroll(_ scrollView: VideoListPlayable) {

let currentOffsetY = scrollView.contentOffset.y

let minY = scrollView.frame.height / 3

let maxY = minY * 2

// 获取在 scrollView 自动播放区域内的视频

let autoPlayableViews = scrollView.visibleViews.filter { view in

guard let relativeRect = relativeRect(view: view.viewToContainVideo, relativeTo: scrollView), view.urlToPlay != nil else {return false}

let containerCenterY = relativeRect.minY + relativeRect.height / 2

return (containerCenterY > minY && containerCenterY < maxY)

}

guard let first = autoPlayableViews.first else {

// 没有需要自动播放的视频

// 移除当前正在离开/已经离开屏幕的视频

removeCurrentVideoIfLeavingScreen(scrollView: scrollView)

preOffsetY = currentOffsetY

return

}

// 取出需要自动播放的视频

let viewToPlay: VideoPlayable = autoPlayableViews.reduce(first) { (result, view) in

let isScrollToUpper = currentOffsetY view.frame.maxY ? (isScrollToUpper ? view : result) : (isScrollToUpper ? result : view)

}

if let currentPlayingView = currentPlayingView, viewToPlay as UIView == currentPlayingView {

// 满足条件的视频正在播放中

preOffsetY = currentOffsetY

return

}

removeCurrentVideo(on: scrollView)

addPlayerView(to: viewToPlay, on: scrollView)

preOffsetY = currentOffsetY

}

VideoListAutoPlayManager 提供该方法用于 controller 需要进行视频自动播放处理时进行调用。

外部可以自行决定在什么时机,进行视频自动播放逻辑的触发,不需要是在 scrollViewDidScroll 的时机。

该方法主要逻辑是:

取出当前可见区域中,满足自动播放条件(func relativeRect(view: UIView, relativeTo scrollView: VideoListPlayable) -> CGRect?)的 cell,即相对位置为滚动列表的 1/3 至 2/3 的位置。

如果没有满足条件的,则判断当前是否有播放中的视频,且视频即将或已经离开屏幕,有则停止播放视频,并通知 cell。

如果有满足条件的视频,则根据滚动方向选取视频(列表向上滚动时,播放靠下的视频,反之则播放靠上的视频),移除上一个播放中的视频(通知对应的 cell),切换视频源并播放,通知最新播放的 cell。

手动播放的处理

func play(at videoView: VideoPlayable, on scrollView: VideoListPlayable) {

removeCurrentVideo(on: scrollView)

addPlayerView(to: videoView, on: scrollView)

}

即移除当前播放中的视频,并将当前手动指定播放的视频进行播放。

添加视频组件

func addPlayerView(to view: VideoPlayable, on scrollView: VideoListPlayable) {

guard let url = view.urlToPlay else {

return

}

let avItem = AVPlayerItem(url: url)

let avPlayer = AVPlayer(playerItem: avItem)

playerVC.player = avPlayer

avPlayer.isMuted = true

avPlayer.play()

view.videoStatusChanged(changeTo: true)

let containerView = view.viewToContainVideo

containerView.addSubview(playerVC.view)

playerVC.view.translatesAutoresizingMaskIntoConstraints = false

playerVC.view.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true

playerVC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true

playerVC.view.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true

playerVC.view.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true

currentPlayingView = view

}

通过协议获取包含视频的 view,将视频放入其中,通知 cell 进行状态变化处理。

隐藏需求的实现

基本按照上一个部分讲的思路实现,没有将这部分代码放到 Demo 中。

总结

目前的方案,cell只需实现协议,添加一个用于包含视频的 view 即可。这样降低了对原代码的侵入性、减少修改和维护的成本,可随时去除该自动播放的特性。另外隐藏需求实际花费的思考和时间会比原始需求多些,需要考虑很多细节。

android短视频列表自动播放,手把手教你实现视频列表滚动自动播放-短视频列表滚动播放实战...相关推荐

  1. python 视频下载进度_手把手教你用Python打造一款批量下载视频并能可视化显示下载器...

    3.直接上代码和注释. #解析页面def parser: ab=[] rep=requests.get('http://v.u00.cn:93/iappce.htm#sp',timeout=5,hea ...

  2. 手把手教你爬取PC端『某酷视频』个人历史播放数据,并可视化展示

    大家好,我是阿辰,今天手把手教你爬取PC端『某酷视频』个人历史播放数据,并可视化展示 上次有粉丝说,那个是ios手机,安卓手机现在需要root权限才可以安装证书,那么今天就不以手机为例,以电脑PC端为 ...

  3. 【嵌入式Android开发学习攻略】手把手教你循序渐进的学习

    [嵌入式Android开发学习攻略]手把手教你循序渐进的学习 学习嵌入式主要有以下几个方面: C语言:C是必须学的,它是最基础的 操作系统:Linux.Android   目前Linux是主流,这个一 ...

  4. 怎么把分钟转化成秒_怎么利用抖音短视频引流,手把手教你如何玩转抖音流量...

    现在抖音的影响力越来越大,用户也是越来越多:一个流量这么聚集的地方,营销者们怎么可能会放弃这么个好的流量池?各行各业纷纷出动,只为在这块"肥地"分的一杯羹. 2018年,抖音APP ...

  5. 手把手教你用Python打造一款批量下载视频并能可视化显示下载进度的下载器

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 桃之夭夭,灼灼其华. /1 前言/ ...

  6. Android图文并茂如何存储,图文并茂,手把手教您如何将笔记本/iphone手机,安卓系统手机传屏到投影/电视...

    原标题:图文并茂,手把手教您如何将笔记本/iphone手机,安卓系统手机传屏到投影/电视 图文并茂,手把手教您如何将笔记本/iphone手机,安卓系统手机传屏到投影/电视 最近很多用户问,我们这套无线 ...

  7. 手把手教你不用客户端也能轻松下载音乐视频

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 宝剑锋从磨砺出,梅花香自苦寒来. ...

  8. 手把手教你使用Python网络爬虫获取B站视频选集内容(附源码)

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 位卑未敢忘忧国,事定犹须待阖棺. ...

  9. 怎么裁剪视频时长?手把手教你裁剪

    现在的网络非常方便,我们可以很轻松的在网上找到各种视频进行网课的学习.不过有些网课重点的内容可能不多而且又分散,我们很难做到高效的学习.其实我们可以通过视频裁剪,将需要的视频内容裁剪下来,这样子就方便 ...

  10. 如何设置计算机自动连接宽带,宽带自动连接设置,教您电脑怎么设置宽带自动连接...

    最近有电脑用户反映,每次电脑开机之后,都要手动点击宽带连接才能连网,觉得这样很麻烦.其实,我们是可以为自己的电脑设置开机自动连接宽带的.可以简单而有效的提高工作学习效率,那电脑怎么设置宽带自动连接?下 ...

最新文章

  1. 人脸验证 DeepID 算法实践
  2. 汤家凤高等数学2020年强化笔记-第三模块-积分学
  3. 文巾解题 184. 部门工资最高的员工
  4. Java对【JSON数据的解析】--官方解析法
  5. [蓝桥杯2019初赛]完全二叉树的权值-完全二叉树的性质
  6. Bootstrap学习笔记
  7. 输出该链表中倒数第k个结点
  8. 将信息从个人计算机传递到中央,上载-常识-工控百科-工控家
  9. Quartz简答介绍
  10. unity中的web player与webGL
  11. Graphics.DrawLine 方法
  12. 期货大作手风云录 --简记
  13. Perl笔记:07、以正则表达式进行匹…
  14. Leetcode 714
  15. 国外著名博客 Gizmodo 博主 Brian Lam 访谈录
  16. LDO芯片CMO3236 系列低功耗
  17. tiny4412移植uboot-2019-01(三)
  18. Vim文本编辑器及文本处理常用操作
  19. 计算机表格要学些什么,几个Excel表格的制作方法
  20. h5 vr效果_H5案例|通过VR展示的那些烧脑游戏

热门文章

  1. 学术诚信的重要性_坚守学术道德,弘扬学术诚信
  2. pt100热电阻计算公式C语言,牛顿法在Pt100铂热电阻温度计算中的应用特性分析
  3. OA是什么?OA系统,OA,OA解决方案有哪些,OA有什么功能,OA有什么作用
  4. 51单片机-波特率的计算定时器初始值的计算
  5. java脚手架_Java代码重构
  6. golang读取EXIF orientation标记
  7. 几个网站收录提交入口,让自己的博客被搜索引擎收录(最新版)
  8. input输入框对伪类(after,before)支持情况
  9. ROS学习(一)Ros 中使用kinect
  10. IDEA中Debugger的使用