七.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…

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

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

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

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

垃圾分类网站|基于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 前启动&…

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 是一种显示系统上所有运行进…

中间件 | 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;我们经常会遇到#{}和${}两种不同的占位符语法。本文将详细解析#{}和${}的区别以及它们在…

Day30-Linux基础阶段总复习

Day30-Linux基础阶段总复习 1. 运维人员的三个核心职责&#xff08;了解&#xff09;2. 企业网站和应用的可用性的衡量标准&#xff08;重点&#xff09;2.1 高并发企业业务写入流程图2.2 中小型企业案例 3. Linux系统诞生发展过程中的关键代表人物4. 企业场景如何针对不同的业…

详解动态规划(算法村第十九关青铜挑战)

不同路径 62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finis…

HubSpot出海CRM智能化营销,创造无限商机

随着企业的全球化发展&#xff0c;营销的挑战也在不断增加。如何有效地管理多样化的市场、提升客户互动和参与度&#xff0c;成为了全球企业必须面对的重要问题。在这一挑战背景下&#xff0c;HubSpot出海CRM的营销自动化功能成为了企业的强大利器&#xff0c;为实施全球化营销…

Java多线程——synchronized、volatile 保障可见性

目录 引出synchronized、volatile 保障可见性Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Java多线程——synchronized、volatile 保障可见性 synchronized、volatile 保障可见性 原子性&#xff1a;在一次或者多次操作时…

【C++】Unordered_map Unordered_set

在C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;例如map、set等。它们在搜索数据时效率可达到O(logN)&#xff0c;但最糟糕的情况下搜索需要比较红黑树的高度次&#xff0c;若此时树中的节点非常之多&#xff0c;那么搜索效率就非常不理想。 最理…

python绘制趋势线

趋势线 趋势线是用来显示数据趋势或者预测未来发展方向的一种图形表示方法。在统计学和数据分析中&#xff0c;趋势线通常是通过拟合数据点来找到一条最符合数据整体趋势的直线、曲线或者其他形状。常见的趋势线拟合方法包括线性回归、多项式回归、指数平滑等。 趋势线在金融…

OpenHarmony教程指南—Ability的启动模式

介绍 本示例展示了在一个Stage模型中&#xff0c;实现standard、singleton、specified多种模式场景。 本实例参考开发指南 。 本实例需要使用aa工具 查看应用Ability 模式信息。 效果预览 使用说明 1、standard模式&#xff1a; 1&#xff09;进入首页&#xff0c;点击番茄…

贪心算法详解及机器人运动应用Demo

一、引言 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法。贪心算法在有最优子结构的问题中尤为有效。今天&#xff0c;我们将通过一个机器人运动的Demo来详细解析贪心算…