Swift 技术 视频播放器滚动条(源码)
一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络,希望未来技术之巅上有你们也有我。
整个的原理在视频中已经说的很清楚了,不懂的话直接看视频是很明朗的.
前言
2023.09.03自定义封装一个视频拖拽的滚动条.需要满足一下的需求:
1.能拖拽快进
2.能点击快进
3.能预加载进度
4.显示当前播放进度
5.显示节点,点击跳到对应的节点
思路
该拖拽进度条底部是用来显示预加载进度的,使用系统自带的UIProgressView控件.最外面能拖拽播放进度的使用系统自带的UISlider控件.节点通过for循环创建按键出来的节点.这样有一个问题,当播放到节点的位置的时候,节点会挡住拖拽进度条的位置.所以拖拽进度条的原点,就用一张透明的图片(相当于不要了),然后创建一个按键在最前面,用一张圆形图片,挡住原来UISlider控件的原点,跟随滑动,播放的个过程
可以看到上面的图片就是用一个自定义的按键挡住了原来UISlider控件的原点.
重点
下面说一下整个封装的几个关键地方.
点击滚动条
算出point值的公式:
let point = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)
全部代码
//
// PlayProgressView.swift
// SwiftDemol
//
// Created by 冯汉栩 on 2023/8/21.
//import UIKitclass PlayProgressView: UIView {// 预加载进度lazy var progress: UIProgressView = {let progress = UIProgressView()progress.progressTintColor = UIColor(red: 169.0/255.0, green: 169.0/255.0, blue: 169.0/255.0, alpha: 1.0)//深灰色progress.trackTintColor = UIColor(red: 228.0/255.0, green: 228.0/255.0, blue: 228.0/255.0, alpha: 1.0)//浅灰色return progress}()// 进度条 clearn_point slider_pointer_normallazy var slider: UISlider = { [weak self] inlet slider = UISlider()slider.minimumTrackTintColor = UIColor(red: 74.0/255.0, green: 155.0/255.0, blue: 234.0/255.0, alpha: 1.0)//蓝色slider.maximumTrackTintColor = UIColor.clear // 透明颜色slider.setThumbImage(UIImage(named: "slider_pointer_normal"), for: .normal)//clearn_point(透明) slider_pointer_normal(白色)slider.setThumbImage(UIImage(named: "slider_pointer_normal"), for: .highlighted)slider.setThumbImage(UIImage(named: "slider_pointer_normal"), for: .selected)let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sliderTapped(gestureRecognizer:)))slider.addGestureRecognizer(tapGestureRecognizer)return slider}()//自定义point点lazy private var draggableButton: UIButton = {let button = UIButton()button.frame = CGRect(x: 0, y: 0, width: 10, height: 20)//button.backgroundColor = .blue.withAlphaComponent(0.5)button.setImage(UIImage(named: "slider_pointer_normal"), for: .normal)button.tag = 111return button}()// 修改播放进度var changePointBlock:((_ point: Float) ->Void)?// point数组var pointList = [Float]()// 当前播放点的移动位置var pointX:Float = 0.0 {didSet {let newX = (self.frame.width - draggableButton.frame.width) * CGFloat(pointX)if newX.isNaN { return }draggableButton.frame = CGRect(x: newX, y: draggableButton.frame.origin.y, width: draggableButton.frame.width, height: draggableButton.frame.height)slider.value = pointX}}override init(frame: CGRect) {super.init(frame: frame)buildUI()}required init?(coder aDecoder: NSCoder) {fatalError("init(coder:) has not been implemented")}private func buildUI() {backgroundColor = .clear // .green.withAlphaComponent(0.5)addSubview(progress)progress.snp.makeConstraints { make inmake.centerY.equalToSuperview()make.left.equalToSuperview().offset(2+1)//2是跟随slider的2+偏移1make.right.equalToSuperview().offset(-2)make.height.equalTo(4)}addSubview(slider)slider.snp.makeConstraints { make inmake.centerY.equalToSuperview().offset(-1)make.left.equalToSuperview().offset(2)make.right.equalToSuperview().offset(-2)make.height.equalTo(20)}addSubview(draggableButton)let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))draggableButton.addGestureRecognizer(panGesture)}override func layoutSubviews() {super.layoutSubviews()//严重的bug是否创建过if pointList.isEmpty { return }//如果有节点就移除for subview in self.subviews {if subview is UISlider {for sliderSubView in subview.subviews {if sliderSubView is UIButton {sliderSubView.removeFromSuperview()}}}}//布局节点for i in 0..<pointList.count {let button = UIButton()slider.addSubview(button)button.tag = ibutton.setImage(UIImage(named: "point_none"), for: .normal)button.addTarget(self, action: #selector(pointNoneClick), for: .touchUpInside)button.snp.makeConstraints { make inmake.width.equalTo(16)make.height.equalTo(20)make.centerY.equalToSuperview().offset(1)make.left.equalToSuperview().offset(Float(progress.bounds.size.width)*pointList[i]-16*0.5)}}}// MARK: - slider -- 点击滚动条@objc private func sliderTapped(gestureRecognizer: UIGestureRecognizer) {let pointTapped: CGPoint = gestureRecognizer.location(in: self)let positionOfSlider: CGPoint = slider.frame.originlet widthOfSlider: CGFloat = slider.frame.size.widthlet point = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)//UI--修改进度条的值slider.setValue(Float(point), animated: false)//换算x的值let newX = (self.frame.width - draggableButton.frame.width) * CGFloat(point)//修改自定义point按键的framedraggableButton.frame = CGRect(x: newX, y: draggableButton.frame.origin.y, width: draggableButton.frame.width, height: draggableButton.frame.height)//闭包出去修改AVPlayer的值 0 -- 100changePointBlock?(Float(point))}// MARK: - 按键 -- 节点按键@objc private func pointNoneClick(button:UIButton) {//换算x的值let newX = (self.frame.width - draggableButton.frame.width) * CGFloat(pointList[button.tag])//修改自定义point按键的framedraggableButton.frame = CGRect(x: newX, y: draggableButton.frame.origin.y, width: draggableButton.frame.width, height: draggableButton.frame.height)//UI--修改进度条的值slider.setValue(Float(pointList[button.tag]), animated: false)//闭包出去修改AVPlayer的值 0 -- 100changePointBlock?(pointList[button.tag])}// MARK: - 自定义按键 -- 拖拽@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {let translation = gesture.translation(in: self)//translation.x 拖拽的值var newX = draggableButton.frame.origin.x + translation.xswitch gesture.state {case .changed://更新按钮的 x 坐标,限制在 view 的范围内newX = max(0, min(newX, self.frame.width - draggableButton.frame.width))//意思是:newX取值范围0 ~ self.frame.width - draggableButton.frame.width 之间//修改自定义point按键的framedraggableButton.frame = CGRect(x: newX, y: draggableButton.frame.origin.y, width: draggableButton.frame.width, height: draggableButton.frame.height)gesture.setTranslation(.zero, in: self)//修改进度//换算成进度0.0-1.0let progress = newX / (self.frame.width - draggableButton.frame.width)//UI--修改进度条的值slider.setValue(Float(progress), animated: false)//闭包出去修改AVPlayer的值 0 -- 100changePointBlock?(Float(progress))default:break}}}
使用
创建+返回值出来
约束
赋值给它
相关文章:

Swift 技术 视频播放器滚动条(源码)
一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络࿰…...

PixelSNAIL论文代码学习(2)——门控残差网络的实现
文章目录 引言正文门控残差网络介绍门控残差网络具体实现代码使用pytorch实现 总结 引言 阅读了pixelSNAIL,很简短,就用了几页,介绍了网络结构,介绍了试验效果就没有了,具体论文学习链接 这段时间看他的代码,还是挺痛…...
WebGPU学习(9)---使用Pipeline Overridable Constants
使用Pipeline Overridable Constants WebGPU 的着色器语言是 WGSL,但与 GLSL 和 HLSL 不同,不支持 #ifdef 等宏。为了实现各种着色器变体,迄今为止,宏一直是着色器编程中非常重要的功能。那么应该如何处理没有宏的 WGSLÿ…...

javaweb入门版学生信息管理系统-增删改查+JSP+Jstl+El
dao public class StudentDao {QueryRunner queryRunner QueryRunnerUtils.getQueryRunner();//查询全部学生信息public List<Student> selectStudent(){String sql "select * from tb_student";List<Student> students null;try {students queryRunn…...

云原生Kubernetes:K8S概述
目录 一、理论 1.云原生 2.K8S 3.k8s集群架构与组件 二、总结 一、理论 1.云原生 (1)概念 云原生是一种基于容器、微服务和自动化运维的软件开发和部署方法。它可以使应用程序更加高效、可靠和可扩展,适用于各种不同的云平台。 如果…...

nmap的使用
目录 nmap简介 主要作用 nmap原理 namp使用 options nmap列举远程机器开放端口 普通扫描 扫描范围端口 对几个端口探测 对所有端口进行探测 指定协议探测端口 扫描对应协议的所有端口 端口状态 nmap识别目标机器上服务的指纹 服务指纹 识别目标机器服务信息 …...

Python爬虫-某网酒店数据
前言 本文是该专栏的第5篇,后面会持续分享python爬虫案例干货,记得关注。 本文以某网的酒店数据为例,实现根据目标城市获取酒店数据。具体思路和方法跟着笔者直接往下看正文详细内容。(附带完整代码) 正文 地址:aHR0cHM6Ly93d3cuYnRoaG90ZWxzLmNvbS9saXN0L3NoYW5naGFp …...
了解atoi和offsetof
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 目录 文章目录 一、简介 二、深度剖析 1.atoi 2.offsetof 三、应用场景 一、简介二、深度剖析 1.atoi2.offsetof三、应用场景 一、简介 在C语言中,有许多…...

命令行编译VS工程
先输入以下命令,因为命令出错了,就会弹出帮助,如下: "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe" /help 反正就是Microsoft Visual Studio 的安装路径。 帮助界面如下:…...
Linux防火墙命令
开启防火墙 systemctl start firewalld关闭防火墙 systemctl stop firewalld # 暂时关闭防火墙 systemctl disable firewalld # 永久关闭防火墙(禁用开机自启) systemctl enable firewalld # 永久开启防火墙(启用开机自启)重启防火墙 systemctl restart firewalld重载规则 …...

大数据平台数据脱敏是什么意思?有哪些方案?
大数据平台包含了海量多样化数据,所以保障大数据平台数据安全非常重要,数据脱敏就是手段之一。今天我们就来简单聊聊大数据平台数据脱敏是什么意思?有哪些方案? 大数据平台数据脱敏是什么意思? 大数据平台数据脱敏简…...
前后端分离不存在会话,sessionid不一致问题
目录 1.使用拦截器解决跨域的示例: 2.使用redis,不使用session 前后端不分离项目我们可以通过session存储数据,但是前后端分离时不存在会话,每次请求sessionid都会改变,当值我们储存的数据不能取出来。 1.使用拦截器…...
Python 3+ 安装及pip配置
Python 3 安装及pip安装升级 1. 安装Python依赖2. 在Linux服务器下载3. 创建python链接4. 配置pip 服务器环境:Linux CentOS 7 内核版本3.10 Python版本:3.10.6 由于CentOS 7默认安装python2.7,使用yum可以查到最新的python3版本为3.6.8&…...
StarRocks入门到熟练
1、部署 1.1、注意事项 需要根据业务需求设计严谨的集群架构,一般来说,需要注意以下几项: 1.1.1、FE数量及高可用 FE的Follower要求为奇数个,且并不建议部署太多,通常我们推荐部署1个或3个Follower。在三个Followe…...
Zabbix Api监控项值推送:zabbix_sender
用法示例: zabbix_sender [-v] -z server [-p port] [-I IP-address] [-t timeout] -s host -k key -o value其中: -z 即 --zabbix-server,Zabbix server的主机名或IP地址。如果主机由proxy监控,则应使用proxy的主机名或IP地址-…...
Shell脚本开发:printf和test命令的实际应用
目录 Shell printf 命令 打印简单文本 Shell test 命令 1、文件测试 2、字符串比较 3、整数比较 逻辑运算: Shell printf 命令 当你使用Shell中的printf命令时,它可以帮助你格式化和输出文本。 打印简单文本 这将简单地打印字符串"Hello, …...

React笔记(三)类组件(1)
一、组件的概念 使用组件方式进行编程,可以提高开发效率,提高组件的复用性、提高代码的可维护性和可扩展性 React定义组件的方式有两种 类组件:React16.8版本之前几乎React使用都是类组件 函数组件:React16.8之后,函数式组件使…...
Hugging Face实战-系列教程4:padding与attention_mask
🚩🚩🚩Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在notebook中进行 本篇文章配套的代码资源已经上传 上篇内容: Hugging Face实战-系列教程3:文本2分类 下篇内容: …...

睿趣科技:抖音开网店卖玩具怎么样
近年来,随着社交媒体平台的飞速发展,抖音作为一款短视频分享应用也迅速崭露头角。而在这个充满创业机遇的时代背景下,许多人开始探索在抖音平台上开设网店,尤其是卖玩具类商品,那么抖音开网店卖玩具究竟怎么样呢? 首先…...

简易虚拟培训系统-UI控件的应用4
目录 Slider组件的常用参数 示例-使用Slider控制主轴 示例-Slider控制溜板箱的移动 本文以操作面板为例,介绍使用Slider控件控制开关和速度。 Slider组件的常用参数 Slider组件下面包含了3个子节点,都是Image组件,负责Slider的背景、填充区…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...