七.AV Foundation 视频播放 - 图片进度条

引言

播放器的功能功能已经十分完善了,接下来我们给它添加一些提升用户体验的功能。当前市面上的主流播放器几乎都有一个非常友善的功能,用户在退拽进度条的时候可以看见进度条所处进度的视频画面,这对于用户来说是一种直观而且便捷的体验。而对于 iOS 平台的应用程序而言,实现这样的功能也并不复杂。在本文中,我们将探讨如何在 iOS 应用程序中实现图片进度条,以便用户在拖动进度条时能够即时预览视频的画面。通过添加这样一个小但强大的功能,我们可以进一步提升用户与应用程序之间的互动性,使观看视频的体验更加流畅和愉快。

原理

AV Foundation框架为我们提供了一个名为AVAssetImageGenerator的类,专门用来从一个AVAsset资源中提取图片,它为我们提供了两个方法:

  • 获取一张图片

generateCGImageAsynchronously(for: <#T##CMTime#>, completionHandler: <#T##(CGImage?, CMTime, Error?) -> Void#>)

该方法用于异步生成给定时间点的CGImage对象,

  1. for: 这是一个CMTime类型的参数,表示要生成图像的时间点。CMTime是Core Media框架中用于表示时间的数据类型,可以理解为一个精确的时间值。

  2. completionHandler: 这是一个闭包类型的参数,用于在生成图像完成时进行回调。闭包接受三个参数:

    • CGImage?: 生成的图像对象,如果生成失败则为nil。
    • CMTime: 表示生成图像的时间点,这个时间点可能与传入的时间点不完全相同,会受到视频帧率等因素的影响。
    • Error?: 如果生成图像过程中出现错误,则会传递一个Error对象,否则为nil。

这个方法的使用场景通常是在需要从视频中获取某个时间点的图像时,可以异步调用该方法,并在完成后通过闭包获取生成的图像。由于图像生成是一个比较耗时的操作,因此使用异步方法可以避免阻塞主线程。

  • 获取一组图片

generateCGImagesAsynchronously(forTimes: <#T##[NSValue]#>, completionHandler: <#T##AVAssetImageGeneratorCompletionHandler##AVAssetImageGeneratorCompletionHandler##(CMTime, CGImage?, CMTime, AVAssetImageGenerator.Result, Error?) -> Void#>)

该方法用于异步生成给定时间点数组的CGImage对象数组,

  1. forTimes: 这是一个[NSValue]类型的参数,表示要生成图像的时间点数组。每个时间点都由一个CMTime对象封装在NSValue中。你可以传递一个包含多个时间点的数组,生成器会按顺序为每个时间点生成相应的图像。

  2. completionHandler: 这是一个闭包类型的参数,用于在生成图像完成时进行回调。闭包接受五个参数:

    • CMTime: 表示生成图像的时间点,这个时间点可能与传入的时间点不完全相同,会受到视频帧率等因素的影响。
    • CGImage?: 生成的图像对象,如果生成失败则为nil。
    • CMTime: 表示生成图像的实际时间点,与传入时间点相匹配。
    • AVAssetImageGenerator.Result: 表示生成图像的结果,是一个枚举类型,可能的取值有.success表示成功生成图像,.failed表示生成失败。
    • Error?: 如果生成图像过程中出现错误,则会传递一个Error对象,否则为nil。

这个方法的使用场景通常是在需要从视频中获取多个时间点的图像时,可以异步调用该方法,并在完成后通过闭包获取生成的图像数组。与单个时间点的方法相比,这个方法适用于批量生成图像的情况,能够提高效率。

实现

给现有播放器新增这样一个图片的可视进度条,在这里我们拆分成两部分,分别从数据处理、UI渲染来实现来这个整体的图片进度条功能。

数据处理:

我们需要绘制一个视频的进度条,用到的一定是多张图片,所以我们采用第二种方式来获取一组图片。同样我们还是需要在视频准备开始播放的时候调用新创建的方法generateThumbnails开始处理图片数据。

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {if context == &playerItemContext {guard let playerItem = playerItem else { return }guard let player = player else { return }if playerItem.status == .readyToPlay{playerItem.removeObserver(self, forKeyPath: status_keypath)player.play()let duration = playerItem.duration// 同步页面开始播放self.delegate?.playstart()// 同步时间self.delegate?.setCuttentTime(time: 0.0, duration: CMTimeGetSeconds(duration))// 设置标题let assetTitle = assertTitle()self.delegate?.setTitle(title: assetTitle)// 设置字幕let subtitles = loadMediaOptions()self.delegate?.setSubtitle(titles: subtitles)// 监听播放进度addPlayerItemTimeObserver()// 监听播放完成addItemEndObserverForPlayerItem()// 获取缩略图片generateThumbnails()}} else {super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)}}...
// 获取一组缩略图片func generateThumbnails() {}

generateThumbnails方法的实现

// 获取一组缩略图片func generateThumbnails() {//1.guard let asset = asset else { return }imageGenerator = AVAssetImageGenerator(asset: asset)guard let imageGenerator = imageGenerator else { return }imageGenerator.maximumSize = CGSize(width: 100.0, height: 0.0)//2.let status = asset.status(of: .duration)//定义增量var inscrement:CMTimeValue = 0var currentValue:CMTimeValue = CMTime.zero.valuevar duration:CMTime = CMTime.zeroif case .loaded(let r_duration) = status {duration = r_duration}inscrement = duration.value / pic_countvar times:[NSValue] = [NSValue]()while currentValue <= duration.value {let time = CMTime(value: currentValue, timescale: duration.timescale)times.append(NSValue(time: time))currentValue += inscrement}var thumbnails = [PHThumbnailModel]()var count = pic_count//3self.imageGenerator?.generateCGImagesAsynchronously(forTimes: times, completionHandler: {[weak self] requestedTime, imageRef, actualTime, result, error inif result == .succeeded,let cgImage = imageRef  {let image = UIImage(cgImage: cgImage)let thumbnail = PHThumbnailModel(time: actualTime, image: image)thumbnails.append(thumbnail)}count -= 1if count == 0 {guard let self = self else { return }DispatchQueue.main.async() {self.loadPicProgressView(thumbnails: thumbnails)}}})}

上面的方法内容较多,之前也没有提及过相关的内容,所以在这里单独做一下解释。该方法大概可以分为三个部分:

1.创建一个AVAssetImageGenerator并指定maximumSize属性。指定一个width值为100、height值为0的CGSize。这样可以确保生成的图片都遵循一定宽度,并且会根据视频的宽高比自动设置高度值。

2.计算出需要获取图片时间点的数组。从视频中均匀的获取20个时间点创建一个时间数组。

3.加载图片。调用AVAssetImageGenerator提供的方法获取一组图片资源,当获取的资源数量与我们指定的数量相等时,则认为资源加载完毕开始渲染。

其中我们将数据构建成了一个PHThumbnailModel的数据模型,里面存放了时间信息和图片信息。

UI渲染:

创建一个名为PHPicProgressView的类继承自UIView,内部只定义了一个属性buttons用来缓存已经创建的button,还有一个loadThumbnails方法用来接收传入的PHThumbnailModel数据。

import UIKitclass PHPicProgressView: UIView {/// 按钮缓存池var buttons = [UIButton]()/// 加载图片进度条////// - Parameters:///   - thumbnails: 缩略图资源数组func loadThumbnails(thumbnails:[PHThumbnailModel]) {for i in 0 ..< thumbnails.count {let thumbnail = thumbnails[i]var button:UIButton? = nilif i < buttons.count {button = buttons[i]} else {button = UIButton()self.addSubview(button!)buttons.append(button!)}button?.tag = 100 + ibutton?.setImage(thumbnail.image, for: .normal)
//            button?.addTarget(self, action: #selector(thumbnailOnclick), for: .touchUpInside)}}/// 缩略图点击
//    @objc func thumbnailOnclick(button:UIButton) {
//        
//    }override func layoutSubviews() {super.layoutSubviews()let button_width = self.bounds.width / CGFloat(buttons.count)let button_height = self.bounds.heightfor i in 0 ..< buttons.count {let button = buttons[i]button.frame = CGRect(x: 0.0 + CGFloat(i) * button_width, y: 0.0, width: button_width, height: button_height)}}
}

在PHControlView中添加PHPicProgressView,注意需要添加到进度条和当前时间标签的下层,避免出现遮挡。

import UIKitlet offset_x = 30.0
let play_width = 40.0class PHControlView: UIView,PHControlDelegate {..../// 图片进度条let picProgressView = PHPicProgressView()/// 图片高度var picHeight = 0.0override init(frame: CGRect) {super.init(frame: frame)setupView()setEvents()}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}func setupView() {...self.addSubview(picProgressView)picProgressView.isHidden = true...}...// 进度条开始拖拽@objc func startSlider() {guard let delegate = delegate else { return }playButton.isSelected = truedelegate.scrubbingDidStart()if picProgressView.isHidden {picProgressView.isHidden = false}}// 进度条拖拽完成@objc func endSlider() {guard let delegate = delegate else { return }playButton.isSelected = falsedelegate.scrubbedDidEnd(time: TimeInterval(sliderView.value))if !picProgressView.isHidden {picProgressView.isHidden = true}}/// 加载图片进度条func loadPicPogressView(thumbnails: [PHThumbnailModel]) {if let image = thumbnails.first?.image {let item_width = (self.bounds.size.width - offset_x*2)/10.0let item_height = item_width * image.size.height / image.size.widthpicHeight = item_heightlayoutSubviews()picProgressView.loadThumbnails(thumbnails: thumbnails)}}override func layoutSubviews() {super.layoutSubviews()....picProgressView.frame = CGRect(x: offset_x, y: CGRectGetMaxY(currentTimeLabel.frame) - 30.0, width: self.bounds.size.width - offset_x*2, height: picHeight)}}

在UI部分有两处需要说明的代码:

1.在picProgressView添加到父视图的时候,首先进行了隐藏处理,在进度条开始拖拽的时候设置为显示,结束拖拽的时候设置为隐藏。只在用户拖拽进度的过程中显示图片进度条。

2.loadPicPogressView方法,是在PHControlDelegate协议中声明的一个新的方法,目的是通知PHControlView开始加载图片进度条,并传入图片进度条相关数据。

图片进度条

结语

在本文中,我们探讨了如何在 iOS 应用程序中实现图片进度条的功能,以提升用户体验。通过使用 AVFoundation 框架中的 AVAssetImageGenerator 类,我们能够轻松地从视频中获取指定时间点的图像,并将其应用于进度条的展示中。这个小小的功能不仅使用户能够更直观地预览视频内容,还为应用程序增添了更多的交互性和便利性。

当然,除了本文介绍的方法外,还有许多其他的技术和功能可以进一步改进和丰富应用程序的视频播放体验。希望本文能够为您提供一些启发,并在您的开发工作中发挥一定的作用。感谢您的阅读!

如果您有任何问题、建议或想要分享您的经验,请随时在评论区留言,我们期待与您进一步的交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/728157.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode刷题---二叉树展开为链表

官方题解&#xff1a;LeetCode官方题解 解题思想&#xff1a; 当根节点不为空时&#xff0c;从二叉树根节点开始遍历 判断当前节点是否有左节点&#xff0c;如果不存在左节点&#xff0c;则当前节点向右移一位 如果存在左节点&#xff0c;创建辅助节点指向左节点&#xff0c;判…

【Python】新手入门(9):数值和序列

&#x1f40d;【Python】新手入门&#xff08;9&#xff09;&#xff1a;数值和序列 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&am…

百度智能云千帆大模型平台发布会定档3月21日,新模型已悄然上线

百度智能云官微日前宣布&#xff0c;百度智能云千帆产品发布会&#xff08;AI Cloud Day&#xff09;将于2024年3月21日在北京举行&#xff0c;届时将揭晓千帆ModelBuilder 和 AppBuilder 的最新产品进展&#xff0c;并发布系列新模型及开发工具组件。 记者在百度智能云官网上发…

O2O:Offline Meta-Reinforcement Learning with Online Self-Supervision

ICML 2022 paper Introduction 元强化学习(Meta RL)结合O2O。元RL需要学习一个探索策略收集数据&#xff0c;同时还需学习一个策略快速适应新任务。由于策略是在固定的离线数据集上进行元训练的&#xff0c;因此在适应探索策略收集的数据时&#xff0c;它可能表现得不可预测&…

97、我对 AI 模型调优的经验和认识

做 AI 算法调优一些年了,这些年中接触了不少模型,也做过不少在 ASIC 芯片进行模型加速的案例。 在接触的模型中,有一些模型有着非常奇怪的分支结构,有的还有奇怪的 tensor shape,还有的有这奇怪的自定义算法。但在模型优化时,为了将一个 AI 模型性能调到最优,也是无所不…

代码随想录三刷 day16 | 二叉树之104.二叉树的最大深度 559.n叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数

三刷day16 104.二叉树的最大深度559.n叉树的最大深度111.二叉树的最小深度222.完全二叉树的节点个数 104.二叉树的最大深度 题目链接 解题思路&#xff1a; 本题中根节点的高度就是最大深度 二叉树节点的深度&#xff1a; 指从根节点到该节点的最长简单路径边的条数或者节点数…

飞桨AI框架安装和使用示例

飞桨AI框架安装和使用示例 飞桨PaddlePaddle是非常流行的国产AI框架&#xff0c;让我们一起来动手实践吧&#xff01; 安装 飞桨安装参考页面&#xff1a;https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/pip/linux-pip.html 在这个安…

AttributeError: ‘SFTPClient‘ object has no attribute ‘exists‘问题解决

在使用paramiko库进行SFTP操作时&#xff0c;如果遇到AttributeError: SFTPClient object has no attribute exists错误&#xff0c;这意味着你尝试调用的.exists()方法并不直接存在于paramiko.SFTPClient对象中。 虽然SFTPClient类没有内置的.exists()方法&#xff0c;但你可…

【S32K3 MCAL配置】-1.2-GPIO配置及其应用-DIO之高低电平输入-按键(基于MCAL)

"><--返回「Autosar_MCAL高阶配置」专栏主页--> 目录(共11页精讲,基于评估板: NXP S32K312EVB-Q172,手把手教你S32K3从入门到精通) 实现的架构:基于MCAL层 前期准备工作:

垃圾分类网站|基于Springboot框架+java+MYSQL数据库的垃圾分类网站开发设计与实现(可运行源码+数据库+文档)

目录 1.摘 要 2.系统结构设计 3.系统顺序图设计 4.数据库设计 5.系统详细设计 用户前台功能模块 管理员功能模块 垃圾分类管理员功能模块 论文参考 文末获取源码 1.摘 要 本论文主要论述了如何使用JAVA语言开发一个垃圾分类网站 &#xff0c;本系统将严格按照软件开发…

「雷神加速器」pubg m登陆不上去、连接超时、无法进入游戏解决方法

Pubg Mobile(简称pubg m)作为人气吃鸡端游绝地求生PUBG的正版授权手游,完美保留了游戏中跳伞、跑毒、缩圈、捡装备的经典要素玩法,在手游玩家中有着极高的人气。近期,不少老玩家在登陆启动pubg m时出现登陆不上去、连接超时、无法进入游戏的情况,这里为大家整理汇总了几种常用解…

一款高输出电流 PWM 转换器

一、产品描述 TPS543x 是一款高输出电流 PWM 转换器&#xff0c;集成了低电阻、高侧 N 沟道 MOSFET。具有所列的特性的基板上还包括高性能电压误差放大器&#xff08;可在瞬态条件下提供高稳压精度&#xff09;、欠压锁定电路&#xff08;用于防止在输入电压达到 5.5V 前启动&…

【Oracle】oracle中sql给表新增字段并添加注释说明;mysql新增、修改字段

oracle中sql给表新增字段并添加注释说明 ALTER TABLE 表名 ADD 字段名 类型 COMMENT ON COLUMN 表面.字段名 IS ‘注释内容’ ALTER TABLE GROUP ADD T NUMBER(18) COMMENT ON COLUMN GROUP.T IS ‘ID’ mysql新增、修改字段、已有字段增加默认值 ALTER TABLE 表名 ADD COL…

ps aux | grep xxxx和ps ef | grep xxxx这两个命令有什么区别

命令实例 ps aux | grep nexus和ps ef | grep nexus ps aux | grep nexus 和 ps ef | grep nexus 这两个命令都用于在 Unix 或 Linux 系统中查找与 nexus 相关的进程&#xff0c;但它们在展示进程信息时有细微的区别。 ps aux | grep nexus: ps aux 是一种显示系统上所有运行进…

Java 获取元素坐标详情

获取整个页面的高度 int window_height driver.manage().window().getSize().height; 获取整个页面的宽度 int window_height driver.manage().window().getSize().width; 获取元素大小 高度 int height afghanistanIdItem.getSize().getHeight(); 获取元素大小 宽度 …

中间件 | Redis - [基本信息]

INDEX 1 常规用法2 QPS3 pipeline 1 常规用法 分布式锁 最常见用法&#xff0c;需要注意分布式锁的redis需要单点 分布式事务 分布式事务中&#xff0c;核心的技术难点其实是分布式事务这个事本身作为数据的持久化 2PC&#xff0c;比如 seata 的 AT 模式下&#xff0c;将 un…

HYBBS 表白墙网站PHP程序源码

安装教程 上传程序安装&#xff0c;然后设置账号密码&#xff0c;登陆后台切换模板手机PC都要换开启插件访问前台。 安装完成后如果不能正常访问就删除install安装文件夹 安装完成后右上角登录后点击头图进入后台 找到插件 安装表白墙配置插件 找到模板 将表白墙模板同时设…

Linux 开发工具 yum、git、gdb

目录 一、yum 1、软件包 2、rzsz 3、注意事项 4、查看软件包 5、安装软件 6、卸载软件 二、git操作 1、克隆三板斧 2、第一次使用会出现以下情况&#xff1a; 未配置用户名和邮箱&#xff1a; push后弹出提示 三、gdb使用 1、背景 2、使用方法 例一&#xff1a…

MyBatis的#{}和${}:安全与灵活并存的SQL之道

MyBatis的#{}和${}&#xff1a;安全与灵活并存的SQL之道 MyBatis是一款广泛使用的Java持久化框架&#xff0c;提供了强大的SQL映射和数据库操作功能。在编写MyBatis的SQL语句时&#xff0c;我们经常会遇到#{}和${}两种不同的占位符语法。本文将详细解析#{}和${}的区别以及它们在…

ASML或许正在考虑离开荷兰

《荷兰电讯报》报道称&#xff0c;鉴于全球最大晶圆制造设备生产商阿斯麦尔&#xff08;ASML&#xff09;可能向海外扩张&#xff0c;荷兰政府正在秘密制定计划确保该公司留在本国。这项代号为“贝多芬”的行动旨在解决ASML对于荷兰商业环境的顾虑&#xff0c;包括针对外籍员工…