三.AV Foundation 视频播放 - 播放控制

引言

前面的博客我们已经实现了视频的播放功能,但是作为一个完整的视频播放器仅仅有播放功能是不够的,暂停,快进,播放进度条,显示播放时间,显示视频标题和字幕都是必不可少的功能。

本篇博客我们就对视频的播放,暂停,快进等控制功能做一个详细的解读,为原来的播放器添加这些功能来提升用户的播放体验。

处理时间

AVPlayer和AVPlayerItem都是基于时间的对象,当我们想要调节它的播放时间的时候,需要先了解下AV Foundation框架中时间的呈现方式。

在日常开发中我们通常使用Int或者float或者double来标识时间,我们使用NSTimeInterval表示时间的时候其实也是使用的double,只是对它进行了typedef定义。不过在浮点型数据表示时间实际上会存在一些问题,因为浮点型数据的运算会导致不精确的情况,当这种不精确的时间不断地进行累加就会导致情况越发严重,会导致明显的时间偏移。

所以AV Foundation中使用一种可靠性更高的方式来表示时间CMTime。

CMTime属于Core Media框架,它使用分数的形式来表示时间,具体定义如下:

public struct CMTime {public init()public init(value: CMTimeValue, timescale: CMTimeScale, flags: CMTimeFlags, epoch: CMTimeEpoch)/**< The value of the CMTime. value/timescale = seconds */public var value: CMTimeValue/**< The timescale of the CMTime. value/timescale = seconds. */public var timescale: CMTimeScale/**< The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */public var flags: CMTimeFlags/**< Differentiates between equal timestamps that are actually different becauseof looping, multi-item sequencing, etc.Will be used during comparison: greater epochs happen after lesser ones.Additions/subtraction is only possible within a single epoch,however, since epoch length may be unknown/variable */public var epoch: CMTimeEpoch
}

这个结构最关键的两个值是value和timescale,value作为分子,timescale作为分母以分数形式来处理时间。

功能定义

了解了AV Foundation中的时间处理方式之后,接下来我们就开始为播放器定义一些播放,暂停,快进等基本功能。

首先创建一个名为PHPlayerDelegate协议,将播放器需要实现的功能定义到协议中。

protocol PHPlayerDelegate:NSObjectProtocol {/// 播放func play()///暂停func pause()/// 停止func stop()/// 开始拖拽func scrubbingDidStart()/// 拖拽过程func scrubbedToTime(time:TimeInterval)/// 停止拖拽func scrubbedDidEnd(time:TimeInterval)
}

使我们的播放控制器PHPlayerController遵循协议并实现协议方法。

1.播放:直接调用AVPlayer的同名方法。

/// 播放func play() {guard let player = player else { return }player.play()}

2.暂停:直接调用AVPlayer的同名方法。

/// 暂停func pause() {guard let player = player else { return }player.pause()}

3.停止:和pause相同我们使用设置rate为0的方式实现。

/// 停止func stop() {guard let player = player else { return }player.rate = 0.0}

4.开始拖拽:拖拽进度条时将播放器暂停播放。

/// 开始拖拽func scrubbingDidStart() {pause()}

5.拖拽过程:这个方法我们先空实现。

/// 指定播放时间////// - Parameters:///   - time: 指定播放时间func scrubbedToTime(time: TimeInterval) {}

6.停止拖拽:拖拽进度条完成后,首先调用cancelPendingSeeks方法清空上一个快进搜索,避免造成堆积,然后将播放器快进到指定播放位置。

    /// 结束拖拽////// - Parameters:///   - time: 指定播放时间func scrubbedDidEnd(time: TimeInterval) {guard let playerItem = playerItem else { return }guard let player = player else { return }playerItem.cancelPendingSeeks()player.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale: Int32(NSEC_PER_SEC)))}

控制UI组件

播放器的所有控制功能就都已经实现完成了,但是现在还没有对应的UI组件来调用这些功能,接下来我们就来实现一个比较常见的播放器控制UI,包括进度条,播放,暂停按钮等等,整体页面如下图所示,接下来我们就来实现一下吧。

播放页面

PHControlView页面实现

import UIKitlet offset_x = 30.0
let play_width = 40.0class PHControlView: UIView,PHControlDelegate {weak var delegate: PHPlayerDelegate?/// 返回按钮let backButton = PHBackButton()///标题let titleLabel = UILabel()/// 当前时间let currentTimeLabel = UILabel()/// 总时间let totalTimeLabel = UILabel()/// 进度条let sliderView = UISlider()/// 播放暂停按钮let playButton = PHPlayerButton()override init(frame: CGRect) {super.init(frame: frame)setupView()setEvents()}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}func setupView() {self.addSubview(backButton)self.addSubview(titleLabel)titleLabel.font = UIFont.systemFont(ofSize: 14.0)titleLabel.textColor = .whiteself.addSubview(currentTimeLabel)currentTimeLabel.textAlignment = .leftcurrentTimeLabel.textColor = .whitecurrentTimeLabel.font = UIFont.systemFont(ofSize: 14.0)self.addSubview(totalTimeLabel)totalTimeLabel.textAlignment = .righttotalTimeLabel.textColor = .whitetotalTimeLabel.font = UIFont.systemFont(ofSize: 14.0)self.addSubview(sliderView)self.addSubview(playButton)currentTimeLabel.text = "00:00:00"totalTimeLabel.text = "00:00:00"titleLabel.text = "视频1"}override func layoutSubviews() {super.layoutSubviews()backButton.frame = CGRect(x: offset_x, y: 20.0, width: 30.0, height: 30.0)titleLabel.frame = CGRect(x: CGRectGetMaxX(backButton.frame) + 25.0, y: 20.0, width: 100.0, height: 30.0)currentTimeLabel.frame = CGRect(x: offset_x, y: self.bounds.size.height - 50.0 - 30.0, width: 120.0, height: 15.0)totalTimeLabel.frame = CGRect(x: self.bounds.size.width - 120.0 - offset_x, y: CGRectGetMinY(currentTimeLabel.frame), width: 120.0, height: 15.0)sliderView.frame = CGRect(x: offset_x, y: CGRectGetMaxY(currentTimeLabel.frame) + 12.0, width: self.bounds.size.width - offset_x*2, height: 4.0)playButton.frame = CGRect(x: offset_x, y: CGRectGetMaxY(sliderView.frame), width: play_width, height: play_width)}func timeString(from timeInterval: TimeInterval) -> String {let hours = Int(timeInterval) / 3600let minutes = Int(timeInterval) / 60 % 60let seconds = Int(timeInterval) % 60return String(format: "%02d:%02d:%02d", hours, minutes, seconds)}}

代码的篇幅比较长,但都是一些UI相关的内容,不需要过多的解释。我们把重点放到播放按钮playButton和进度条sliderView上面。

playButton:播放按钮添加点击事件和实现,让delegate去调用对应的播放和暂停方法。

func setEvents() {playButton.addTarget(self, action: #selector(playeOnclick), for: .touchUpInside)}
 // 播放按钮点击@objc func playeOnclick(button:UIButton) {button.isSelected = !button.isSelectedguard let delegate = delegate else { return }if button.isSelected {delegate.pause()} else {delegate.play()}}

sliderView:为进度条添加拖拽事件和实现,让delegate同步播放器的状态

func setEvents() {playButton.addTarget(self, action: #selector(playeOnclick), for: .touchUpInside)sliderView.addTarget(self, action: #selector(startSlider), for: .touchDown)sliderView.addTarget(self, action: #selector(moveSlider), for: .valueChanged)sliderView.addTarget(self, action: #selector(endSlider), for: .touchUpInside)}
// 进度条开始拖拽@objc func startSlider() {guard let delegate = delegate else { return }playButton.isSelected = truedelegate.scrubbingDidStart()}// 进度条拖拽@objc func moveSlider() {}// 进度条拖拽完成@objc func endSlider() {guard let delegate = delegate else { return }playButton.isSelected = falsedelegate.scrubbedDidEnd(time: TimeInterval(sliderView.value))}

使用

将PHControlView添加到PHPlayerView之上,用来控制播放器的播放,暂停等操作。

import UIKit
import AVFoundationclass PHPlayerView: UIView {/// 控制图层let controlView = PHControlView()/// 重写layerClass方法,override class var layerClass: AnyClass{get {return AVPlayerLayer.self}}/// 重写init方法////// - Parameters:///   - player: 播放器init(player:AVPlayer) {super.init(frame: CGRectZero)guard let playerLayer = self.layer as? AVPlayerLayer else { return }playerLayer.player = playerself.addSubview(controlView)}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}override func layoutSubviews() {super.layoutSubviews()controlView.frame = self.bounds}
}

在ViewController中使用播放器

class ViewController: UIViewController {/// 播放控制器var playerController:PHPlayerController?override func viewDidLoad() {super.viewDidLoad()guard let url = Bundle.main.url(forResource: "hubblecast", withExtension: "m4v")  else { return }playerController = PHPlayerController(url: url)guard let playerView = playerController?.view else { return }playerView.backgroundColor = .blackplayerView.frame = view.boundsview.addSubview(playerView)}
}

结语

一个带有基础功能功能的播放器就已经完成了,目前播放器就拥有了自动播放,暂停,播放,拖拽进度的功能。但是进度显示,播放时间的显示,视频标题等等其它元数据信息显然还没有完成同步。在代码中我们也可以注意到PHControlView遵循了一个PHControlDelegate协议,它就是播放控制器用来同步controlView视图元数据信息的代理,下一篇博客我们将详细的介绍关于播放进度,播放状态,以及其它元数据信息同步的问题。

项目地址:PHPlayer: 视频播放器

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

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

相关文章

【Tauri】(2):使用Tauri应用开发,使用开源的Chatgpt-web应用做前端,使用rust 的candle做后端,本地运行小模型桌面应用

视频演示地址 https://www.bilibili.com/video/BV17j421X7Zc/ 【Tauri】&#xff08;2&#xff09;&#xff1a;使用Tauri应用开发&#xff0c;使用开源的Chatgpt-web应用做前端&#xff0c;使用rust 的candle做后端&#xff0c;本地运行小模型桌面应用 1&#xff0c;做一个免…

【MATLAB】使用梯度提升树在回归预测任务中进行特征选择(深度学习的数据集处理)

1.梯度提升树在神经网络的应用 使用梯度提升树进行特征选择的好处在于可以得到特征的重要性分数&#xff0c;从而识别出对目标变量预测最具影响力的特征。这有助于简化模型并提高其泛化能力&#xff0c;减少过拟合的风险&#xff0c;并且可以加快模型训练和推理速度。此外&…

【第三十五节】idea项目的创建以及setting和Project Structure的设置

项目创建 Project Structure的设置 点击file ~ Project Structure 进入 进入view/Appearance 选中Toolbar 就会出现状态栏

13 年后,我如何用 Go 编写 HTTP 服务(译)

原文&#xff1a;Mat Ryer - 2024.02.09 大约六年前&#xff0c;我写了一篇博客文章&#xff0c;概述了我是如何用 Go 编写 HTTP 服务的&#xff0c;现在我再次告诉你&#xff0c;我是如何写 HTTP 服务的。 那篇原始的文章引发了一些热烈的讨论&#xff0c;这些讨论影响了我今…

第9讲用户信息修改实现

用户信息修改实现 后端修改用户昵称&#xff1a; /*** 更新用户昵称* param wxUserInfo* param token* return*/ RequestMapping("/updateNickName") public R updateNickName(RequestBody WxUserInfo wxUserInfo,RequestHeader String token){if(StringUtil.isNot…

奶茶点餐|奶茶店自助点餐系统|基于微信小程序的饮品点单系统的设计与实现(源码+数据库+文档)

奶茶店自助点餐系统目录 目录 基于微信小程序的饮品点单系统的设计与实现 一、前言 二、系统功能设计 三、系统实现 1、商品信息管理 2、商品评价管理 3、商品订单管理 4、用户管理 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 …

STM32 + ESP8266,连接阿里云 上报/订阅数据

&#xff08;文章正在编辑中&#xff0c;一点点地截图操作过程&#xff0c;估计要拖拉两三天&#xff09; 一、烧录MQTT固件 ESP8266出厂时&#xff0c;默认是AT固件。连接阿里云&#xff0c;需要使用MQTT固件。 1、独立EPS8266模块的烧录方法 2、魔女开发板&#xff0c;板载…

ClickHouse--03--数据类型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 数据类型1. Int2.FloattoFloat32(...) 用来将字符串转换成 Float32 类型的函数toFloat64(...) 用来将字符串转换成 Float64 类型的函数 3.DecimaltoDecimal32(value…

蓝桥杯——第 5 场 小白入门赛(c++详解!!!)

文章目录 1 十二生肖基本思路&#xff1a; 2 欢迎参加福建省大学生程序设计竞赛基本思路&#xff1a;代码&#xff1a; 3 匹配二元组的数量基本思路&#xff1a;代码: 4 元素交换基本思路&#xff1a;代码&#xff1a; 5 下棋的贝贝基本思路&#xff1a;代码&#xff1a; 6 方程…

推荐在线图像处理程序源码

对于喜爱图像编辑的朋友们来说&#xff0c;Photoshop无疑是处理照片的利器。然而&#xff0c;传统的Photoshop软件不仅需要下载安装&#xff0c;还对电脑配置有一定的要求&#xff0c;这无疑增加了使用的门槛。 现在&#xff0c;我们为您带来一款革命性的在线PS修图工具——基…

大话设计模式——1.模板方法模式(Template Method Pattern)

定义&#xff1a;定义一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 例子&#xff1a;比较重大的考试往往有A、B两套试卷&#xff0c;其中一套出现问题可以立马更换另一套。 定义基…

java Servlet 云平台教学系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc

一、源码特点 JSP 云平台教学系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助 系统采用serlvet dao bean&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/S模式开发。开发 环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据…

使用 IDEA 开发一个简单易用的 SDK

目录 一、什么是 SDK 二、为什么要开发 SDK 三、开发 SDK 的详细步骤 四、导入 SDK 进行测试 附&#xff1a;ConfigurationProperties 注解的介绍及使用 一、什么是 SDK 1. 定义&#xff1a;软件开发工具包 Software Development Kit 2. 用于开发特定软件或应用程序的工…

[JavaWeb玩耍日记]Maven的安装与使用

目录 一.作用 二.安装 三.使用 2.对项目使用compile命令进行编译,看看新的文件会在哪里产生&#xff1f; 3.需要认识的命令 4.Maven对项目执行不同命令的生命周期特点&#xff1f; 5.如何导入工程外的Maven&#xff1f; 6.如何直观地查看Maven导入了哪些工程或哪些jar包…

Hive SQL编译成MapReduce任务的过程

一、 Hive 底层执行架构 1.1 Hive底层架构 1 &#xff09;用户接口&#xff1a; Client CLI &#xff08; command-line interface &#xff09;、 JDBC/ODBC(jdbc 访问 hive) 、 WEBUI &#xff08;浏览器访问 hive &#xff09; 2 &#xff09;元数据&#xff1a; Metas…

WordPress修改所有用户名并发送邮件通知的插件Easy Username Updater

前面跟大家介绍了『如何修改WordPress后台管理员用户名&#xff1f;推荐2种简单方法』一文&#xff0c;但是对于有很多用户的站长来说&#xff0c;操作有点复杂&#xff0c;而且无法发邮件通知对方&#xff0c;所以今天boke112百科向大家推荐一款可以直接在WordPress后台修改所…

HarmonyOS 开发学习笔记

HarmonyOS 开发学习笔记 一、开发准备1.1、了解ArkTs语言1.2、TypeScript语法1.2.1、变量声明1.2.2、条件控制1.2.3、函数1.2.4、类和接口1.2.5、模块开发 1.3、快速入门 二、ArkUI组件2.1、Image组件2.2、Text文本显示组件2.3、TextInput文本输入框组件2.4、Button按钮组件2.5…

【JS逆向三】逆向某某网站的sign参数,并模拟生成仅供学习

逆向日期&#xff1a;2024.02.06 使用工具&#xff1a;Node.js 类型&#xff1a;webpack 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 可使用AES进行解密处理&#xff08;直接解密即可&#xff09;&#xff1a;AES加解密工具 1、打开某某…

MySQL-运维

一、日志 1.错误日志 错误日志是MySQL中最重要的日志之一&#xff0c;它记录了当mysql启动和停止时&#xff0c;以及服务器在运行过程中发生任何严重错误时的相关性息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&#xf…

线性判别分析(LDA)

一、说明 LDA 是一种监督降维和分类技术。其主要目的是查找最能分隔数据集中两个或多个类的特征的线性组合。LDA 的主要目标是找到一个较低维度的子空间&#xff0c;该子空间可以最大限度地区分不同类别&#xff0c;同时保留与歧视相关的信息。 LDA 是受监督的&#xff0c;这意…