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

news2024/10/6 20:25:09

引言

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

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

处理时间

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 because
    								of 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 UIKit

let offset_x = 30.0
let play_width = 40.0

class 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 = .white
        self.addSubview(currentTimeLabel)
        currentTimeLabel.textAlignment = .left
        currentTimeLabel.textColor = .white
        currentTimeLabel.font = UIFont.systemFont(ofSize: 14.0)
        self.addSubview(totalTimeLabel)
        totalTimeLabel.textAlignment = .right
        totalTimeLabel.textColor = .white
        totalTimeLabel.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) / 3600
        let minutes = Int(timeInterval) / 60 % 60
        let seconds = Int(timeInterval) % 60
        return 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.isSelected
        guard 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 = true
        delegate.scrubbingDidStart()
    }
    
    // 进度条拖拽
    @objc func moveSlider() {
        
    }
    
    // 进度条拖拽完成
    @objc func endSlider() {
        guard let delegate = delegate else { return }
        playButton.isSelected = false
        delegate.scrubbedDidEnd(time: TimeInterval(sliderView.value))
    }

使用

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

import UIKit
import AVFoundation

class 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 = player
        self.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 = .black
        playerView.frame = view.bounds
        view.addSubview(playerView)
    }
}

结语

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

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

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

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

相关文章

Spring Cloud Gateway 网关路由

一、路由断言 路由断言就是判断路由转发的规则 二、路由过滤器 1. 路由过滤器可以实现对网关请求的处理&#xff0c;可以使用 Gateway 提供的&#xff0c;也可以自定义过滤器 2. 路由过滤器 GatewayFilter&#xff08;默认不生效&#xff0c;只有配置到路由后才会生效&#x…

【原创 附源码】Flutter安卓及iOS海外登录--Apple登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月12日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&…

【GameFramework框架内置模块】1、全局配置(Config)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a; https://blog.csdn.net/q7…

AtCoder Beginner Contest 340(A-G)

A - Arithmetic Progression (atcoder.jp) 1.思路&#xff1a;循环输出即可 2.代码&#xff1a; #include <bits/stdc.h> #define rep(i,z,n) for(int i z;i < n; i) #define per(i,n,z) for(int i n;i > z; i--) #define PII pair<int,int> #define fi f…

【51单片机】串口(江科大)

8.1串口通信 1.串口介绍 2.硬件电路 3.电平标准 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种: 电平标准是数据1和数据O的表达方式,是传输线缆中人为规定的电 压与数据的对应关系,串口常用的电平标准有如下…

Microsoft Excel 加载数据分析工具

Microsoft Excel 加载数据分析工具 1. 打开 Excel&#xff0c;文件 -> 选项2. 加载项 -> 转到…3. 分析工具库、分析工具库 - VBA4. 打开 Excel&#xff0c;数据 -> 数据分析References 1. 打开 Excel&#xff0c;文件 -> 选项 2. 加载项 -> 转到… ​​​ 3…

2013-2022年上市公司迪博内部控制指数、内部控制分项指数数据

2013-2022年上市公司迪博内部控制指数、分项指数数据 1、时间&#xff1a;2013-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、辖区、证监会行业、申万行业、内部控制指数、战略层级指数、经营层级指数、报告可靠指数、合法合规指数、资产安全…

蓝桥杯-X图形

问题描述 给定一个字母矩阵。一个 X 图形由中心点和由中心点向四个 45度斜线方向引出的直线段组成&#xff0c;四条线段的长度相同&#xff0c;而且四条线段上的字母和中心点的字母相同。 一个 X 图形可以使用三个整数 r,c,L 来描述&#xff0c;其中 r,c 表示中心点位于第 r 行…

Matplotlib核心:掌握Figure与Axes

详细介绍Figure和Axes&#xff08;基于Matplotlib&#xff09; &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333; 一、Figure&#xff08;图形&#xff09;&#x1f333;&#x1f341;1. 创建Figure&#x1f341;&#x1f341;2. 添加Axes&am…

代码随想录刷题笔记 DAY 24 | 回溯算法理论基础 | 组合问题 No. 77

文章目录 Day 2401. 回溯算法理论基础1.1 什么是回溯法&#xff1f;1.2 为什么要使用回溯法&#xff1f;1.3 如何理解回溯法&#xff1f; 02. 组合问题&#xff08;No. 77&#xff09;2.1 题目2.2 笔记2.3 代码 Day 24 01. 回溯算法理论基础 1.1 什么是回溯法&#xff1f; &…

Kafka 入门笔记

课程地址 概述 定义 Kafka 是一个分布式的基于发布/订阅模式的消息队列&#xff08;MQ&#xff09; 发布/订阅&#xff1a;消息的发布者不会将消息直接发送给特定的订阅者&#xff0c;而是将发布的消息分为不同的类别&#xff0c;订阅者只接受感兴趣的消息 消息队列 消息队…

算法沉淀——位运算(leetcode真题剖析)

算法沉淀——位运算 常用位运算总结1.基础位运算2.确定一个数中第x位是0还是13.将一个数的第x位改成14.将一个数的第x位改成05.位图6.提取一个数最右边的17.删掉一个数最右边的18.异或运算9.基础例题 力扣题目讲解01.面试题 01.01. 判定字符是否唯一02.丢失的数字03.两整数之和…

Linux:搭建docker私有仓库(registry)

当我们内部需要存储镜像时候&#xff0c;官方提供了registry搭建好直接用&#xff0c;废话少说直接操作 1.下载安装docker 在 Linux 上安装 Docker Desktop |Docker 文档https://docs.docker.com/desktop/install/linux-install/安装 Docker 引擎 |Docker 文档https://docs.do…

lv15 驱动高级设备模型 1

之前的驱动操作称为硬编 一、起源 仅devfs&#xff08;dev目录&#xff09;&#xff0c;导致开发不方便以及一些功能难以支持&#xff1a; 热插拔&#xff08;如何插入一个设备然后找到设备的驱动应用到程序中&#xff09; 不支持一些针对所有设备的统一操作&#xff08;如电…

【51单片机】AT24C02(江科大、爱上半导体)

一、AT24C02 1.AT24C02介绍 AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息 存储介质:E2PROM 通讯接口:12C总线 容量:256字节 2.引脚即应用电路 本开发板AT24C02原理图 12C地址全接地,即全为0 WE接地,没有写使能 SCL接P21 S…

无人机飞行原理,多旋翼无人机飞行原理详解

多旋翼无人机升空飞行的首要条件是动力&#xff0c;有了动力才能驱动旋粪旋转&#xff0c;才能产生克服重力所必需的升力。使旋翼产生升力&#xff0c;进而推动多旋翼无人机升空飞行的一套设备装置称为动力装置&#xff0c;包括多旋翼无人机的发动机以及保证发动机正常工作所必…

Could not determine ref type of version: remote: Invalid credentials

在按照别人代码的README配置环境时&#xff0c;需要clone很多库&#xff0c;但是有一个库一直下不下来。 解决办法&#xff0c;直接上github搜索该库&#xff0c;选好相应的版本tag&#xff0c; git clone -b <branch_name> <link>下载即可。

计算机网络——04接入网和物理媒体

接入网和物理媒体 接入网络和物理媒体 怎样将端系统和边缘路由器连接&#xff1f; 住宅接入网络单位接入网络&#xff08;学校、公司&#xff09;无线接入网络 住宅接入&#xff1a;modem 将上网数据调制加载到音频信号上&#xff0c;在电话线上传输&#xff0c;在局端将其…

InternLM大模型实战-5.LMDeploy大模型量化部署实战

文章目录 前言笔记正文大模型部署背景部署挑战部署方案 LMDeploy框架量化推理引擎Turbomind推理服务api server 前言 本文是对于InternLM全链路开源体系系列课程的学习笔记 【LMDeploy 大模型量化部署实践】 https://www.bilibili.com/video/BV1iW4y1A77P/?share_sourcecopy_…