当前位置: 首页 > news >正文

(四)深入理解AVFoundation-播放:高度自定义视频播放器 UI


引言

在之前的博客中,我们已经介绍了如何实现一个简单的播放器,并通过监听资源和播放器的属性来提升播放体验。因此本篇博客将带你进一步自定义播放器 UI。通过构建自己的播放控制界面(如播放/暂停按钮、进度条、全屏切换等),我们能够提供更符合需求的播放体验,并且支持用户通过手势操作进行更精细的控制。

播放画面的视图

在正式介绍“播放/暂停按钮”之前,我们先来看一下播放器的画面是如何展示在视图上的。

我们通过自定义的 PHPlayerView,使用 AVPlayerLayer 来承载 AVPlayer 的视频内容。通过覆写 layerClass 属性,让 PHPlayerView 的底层图层类型变为 AVPlayerLayer,从而直接承接播放画面。

import UIKit
import AVFoundationclass PHPlayerView: UIView {override class var layerClass: AnyClass {return AVPlayerLayer.self}/// 设置播放器/// - Parameter player: 播放器func setPlayer(_ player: AVPlayer) {guard let layer = self.layer as? AVPlayerLayer else { return }layer.player = player}
}

当然了可以直接创建AVPlayerLayer添加到图层之上。有了这个播放画面视图之后,我们就可以继续搭建播放控制相关的 UI,比如播放/暂停按钮。

 协议

为了让项目结构清晰,我们定义了两个关键的协议,来解决播放控制器与播放控制视图组件耦合的问题。

PHPlayerProtocol

该协议为播放器的代理协议,协议内定义了播放器相关的内容发生变化时需要指定的方法,而需要监听变化的UI组件需要遵守该协议,并实现协议方法。目前协议方法包含了播放状态的改变以及播放进度的改变。具体代码如下:

import Foundationprotocol PHPlayerProtocol:NSObjectProtocol {/// 播放状态发生变化/// - Parameter status: 播放状态func playerStatusDidChange(status: PHPlayerStatus)/// 播放进度/// - Parameters:///  - Parameter currentTime: 当前时间///  - Parameter totalTime: 总时间func playerDidProgress(currentTime: TimeInterval, totalTime: TimeInterval)}

PHControlProtocol

该协议为播放UI组件的代理协议,协议内定义了播放UI组件对播放控制器操作的方法,而播放控制器需要遵循该协议,并实现这些方法。目前协议内包含了播放、暂停、快进到指定时间三个方法。具体代码如下:

import Foundationprotocol PHControlProtocol:NSObjectProtocol {/// 播放func play()/// 暂停func pause()/// 指定位置播放/// - Parameter time: 时间func seekTo(time: TimeInterval)}extension PHControlProtocol {func play() {}func pause() {}func seekTo(time: TimeInterval) { }
}

自定义播放控制 UI

我们将除播放视图以外的部分,分成两个部分:

  1. PHPlayerInfoView:视频信息和返回按钮。
  2. PHPlayerControlView:视频的自定义播放组件UI。

而这两部分,我们选择一个专门的视图 PHPlayerOverlayView 用来承载,与播放画面的视图完全隔离。

整个结构如下图所示:

代码如下:

import UIKitclass PHPlayerOverlayView: UIView, PHPlayerProtocol {/// 视频信息let videoInfoView = PHPlayerInfoView()/// 控制视图let controlView = PHPlayerControlView()override init(frame: CGRect) {super.init(frame: frame)setupView()setLayout()}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}private func setupView() {// 视频信息self.addSubview(videoInfoView)// 添加控制视图self.addSubview(controlView)}private func setLayout() {// 视频信息videoInfoView.snp.makeConstraints { make inmake.leading.trailing.equalToSuperview()make.top.equalToSuperview()make.height.equalTo(MW_NAVIGATIONBAR_HEIGHT)}// 控制视图controlView.snp.makeConstraints { make inmake.bottom.equalToSuperview()make.leading.trailing.equalToSuperview()make.height.equalTo(125.0 + MW_BOTTOM_SAFE_HEIGHT)}}....
}

我们先以主要功能为主,依次来介绍实现 “播放/暂停按钮” 、“进度条(UISlider)”、“当前时间/总时间显示”、“播放完成后的重播/状态提示”。

播放/暂停按钮

在控制播放功能时,我们通过自定义 UIButton 来实现对 AVPlayer 的播放与暂停控制。同时结合播放器的播放状态回调,动态更新按钮的图标,切换播放/暂停的视觉状态。

具体的控制 UI 我们集中封装在PHPlayerControlView中,该类负责承载播放控制相关的所有交互组件,例如播放/暂停按钮、进度条、时间标签等。

class PHPlayerControlView: UIView {..../// 播放按钮let playButton = UIButton(type: .custom).../// 代理weak var delegate: PHControlProtocol?override init(frame: CGRect) {super.init(frame: frame)setupView()setLayout()setEvent()}private func setupView() {...// 播放、暂停按钮self.addSubview(playButton)playButton.setImage(UIImage(named: "ph_player_play"), for: .normal)playButton.setImage(UIImage(named: "ph_player_pause"), for: .selected)...}private func setLayout() {...// 播放按钮playButton.snp.makeConstraints { make inmake.leading.equalTo(currentTimeLabel)make.top.equalTo(currentTimeLabel.snp.bottom).offset(8.0)make.width.height.equalTo(40.0)}...}private func setEvent() {// 播放、暂停playButton.addTarget(self, action: #selector(playButtonTapped), for: .touchUpInside)...}/// 播放、暂停按钮点击事件@objc private func playButtonTapped() {if playButton.isSelected {delegate?.pause()} else {delegate?.play()}}
  1. 点按钮的点击事件发生时,判断当前按钮状态。
  2. 如果是播放状态则代理执行暂停操作。
  3. 如果是暂停状态则代理执行播放的方法。

而状态的同步,则是通过 playerStatusDidChange 方法进行同步,我们使 PHPlayerOverlayView 遵循了 PHPlayerProtocol 这个协议,然后在 PHPlayerControlView 定义了同名方法。

class PHPlayerOverlayView: UIView, PHPlayerProtocol {..../// 播放状态改变/// - Parameter status: 播放状态func playerStatusDidChange(status: PHPlayerStatus) {// 同步控制视图self.controlView.playerStatusDidChange(status: status)}/// 播放进度/// - Parameters:/// - Parameter currentTime: 当前时间/// - Parameter totalTime: 总时间func playerDidProgress(currentTime: TimeInterval, totalTime: TimeInterval) {// 同步控制视图self.controlView.playerDidProgress(currentTime: currentTime, totalTime: totalTime)}}

该方法在 PHPlayerControlView 中的实现如下:

    /// 播放状态改变/// - Parameter status: 播放状态func playerStatusDidChange(status: PHPlayerStatus) {if status == .playing {playButton.isSelected = true// 如果是暂停、完成、失败} else if status == .paused || status == .completed || status == .failed {playButton.isSelected = false}}
  1. 会根据播放器的状态来修改播放按钮的状态。

进度条(UISlider)

播放器的进度条通常承担多个功能,除了用来实时显示当前播放进度之外,也支持用户拖拽以跳转到任意时间点,还可以同步展示缓冲进度。

在本篇中,我们将聚焦于最新核心的播放控制功能——进度显示与拖拽操作。

创建UISlider时,我们可以进行灵活的自定义,比如圆点的颜色,圆点的图片。当前进度的颜色,以及默认的轨道颜色等等。

    /// sliderlet slider = UISlider()// sliderself.addSubview(slider)slider.minimumValue = 0slider.maximumValue = 1slider.setThumbImage(UIImage(named: "ph_player_slider_thumb"), for: .normal)

在进行进度同步时,我们通过实现 playerDidProgress(currentTime:totalTime:) 方法来更新 UISlider 的 minimumValue、maximumValue以及value属性。

    /// 播放进度/// - Parameters:/// - Parameter currentTime: 当前时间/// - Parameter totalTime: 总时间func playerDidProgress(currentTime: TimeInterval, totalTime: TimeInterval) {guard totalTime > 0 else { return }...// 设置sliderslider.minimumValue = 0slider.maximumValue = Float(totalTime)slider.value = Float(currentTime)}
  1. 根据播放控制器传递过来的当前时间和总时间来更新UISlider的进度值。

为 UISlider 添加开始拖拽、拖拽、结束拖拽三个方法,并在三个方法内通过 delegate ,让播放控制执行对应的暂停、快进、播放操作。

    private func setEvent() {...// 开始拖拽slider.addTarget(self, action: #selector(sliderTouchDown), for: .touchDown)// 拖拽slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)// 结束拖拽slider.addTarget(self, action: #selector(sliderTouchUpInside), for: .touchUpInside)}/// slider开始拖拽@objc private func sliderTouchDown() {// 暂停delegate?.pause()}/// slider拖拽@objc private func sliderValueChanged() {...delegate?.seekTo(time: currentTime)}/// slider结束拖拽@objc private func sliderTouchUpInside() {...// 拖拽结束,开始播放delegate?.seekTo(time: currentTime)delegate?.play()}
  1. 当拖拽开始时执行暂停操作,边拖拽边播放的现象会很奇怪。
  2. 在拖拽的过程中播放器始终处于暂停状态,但会快进到指定时间点。
  3. 当拖拽结束后,在指定的时间点开始播放。

当前时间/总时间显示

这两组UI元素就比较简单了,因为他们不涉及任何交互,只是单方面的用来显示播放器的播放时间状态,而我们需要同步的其实有两个地方。

  1. 播放器正常播放的进度回调。
  2. 发生拖拽时,拖拽过程中当前时间的变化。
    /// 当前时间let currentTimeLabel = UILabel()/// 总时间let totalTimeLabel = UILabel()// 当前时间self.addSubview(currentTimeLabel)currentTimeLabel.textColor = .whitecurrentTimeLabel.font = UIFont.systemFont(ofSize: 14)currentTimeLabel.text = "00:00"// 总时间self.addSubview(totalTimeLabel)totalTimeLabel.textColor = .whitetotalTimeLabel.font = UIFont.systemFont(ofSize: 14)totalTimeLabel.text = "00:00"

在播放进度发生变化的回调中处理时间显示:

    /// 播放进度/// - Parameters:/// - Parameter currentTime: 当前时间/// - Parameter totalTime: 总时间func playerDidProgress(currentTime: TimeInterval, totalTime: TimeInterval) {guard totalTime > 0 else { return }// 设置当前时间currentTimeLabel.text = currentTime.toHMSTimeString()// 设置总时间totalTimeLabel.text = totalTime.toHMSTimeString()...}

在拖拽过程中对时间的显示处理:

    /// slider拖拽@objc private func sliderValueChanged() {// 设置当前时间let currentTime = TimeInterval(slider.value)currentTimeLabel.text = currentTime.toHMSTimeString()...}

其中 toHMSTimeString() 方法是我们为 TimeInterval 添加的扩展方法,用来将时间转换为时分秒格式的字符串。

extension TimeInterval {/// 转成时:分:秒  没有时则是分:秒/// - Returns: 时:分:秒func toHMSTimeString() -> String {let totalSeconds = Int(self)let hours = totalSeconds / 3600let minutes = (totalSeconds % 3600) / 60let seconds = totalSeconds % 60if hours > 0 {return String(format: "%02d:%02d:%02d", hours, minutes, seconds)} else {return String(format: "%02d:%02d", minutes, seconds)}}
}

播放完成后的重播/状态提示

在视频播放完成之后呢,也会调用 playerStatusDidChange(status:) 方法并且参数值为 .completed。我们可以根据业务需要,来执行对应的操作。

    //MARK:  播放器状态相关方法/// 播放状态改变/// - Parameter status: 播放状态func playerStatusDidChange(status: PHPlayerStatus) {if status == .playing {playButton.isSelected = true// 如果是暂停、完成、失败} else if status == .paused || status == .completed || status == .failed {playButton.isSelected = false} else if status == .completed {playCompleted()}}/// 播放完成private func playCompleted() {// 设置播放按钮为未选中状态playButton.isSelected = false// 设置当前时间为0currentTimeLabel.text = "00:00"// 设置slider为0slider.value = 0// 设置总时间为0delegate?.seekTo(time: 0)}
  1. 播放完成后首先修改按钮状态。
  2. 设置当前时间为00:00。
  3. 设置当前进度为0.0。
  4. 设置播放器回到0.0的位置。

结语

在本篇博客中,我们围绕 AVPlayer 播放控制 UI 的实现展开,介绍了如何构建播放画面的视图 PHPlayerView,以及如何在定义的 PHPlayerControlView 中实现播放/暂停按钮、进度条和时间显示等关键组件。

通过这些 UI 控件的封装,我们不仅提升了播放器的可交互性,也为后序更多功能扩展打下了基础。

下面的博客我们会进入 AVPlayer 的进阶功能,包含音轨、字幕、倍速等等。

相关文章:

(四)深入理解AVFoundation-播放:高度自定义视频播放器 UI

引言 在之前的博客中,我们已经介绍了如何实现一个简单的播放器,并通过监听资源和播放器的属性来提升播放体验。因此本篇博客将带你进一步自定义播放器 UI。通过构建自己的播放控制界面(如播放/暂停按钮、进度条、全屏切换等)&…...

sqli-labs靶场 less6

文章目录 sqli-labs靶场less 6 报错注入 sqli-labs靶场 每道题都从以下模板讲解,并且每个步骤都有图片,清晰明了,便于复盘。 sql注入的基本步骤 注入点注入类型 字符型:判断闭合方式 (‘、"、’、“”&#xf…...

数据库架构全解析:MyCat、MHA、ProxySQL 的原理、功能与实例

前言 : 在分布式数据库架构中,分库分表、高可用性(HA)和查询优化是核心需求。本文将深入解析三款主流工具:MyCat(分布式数据库中间件)、MHA(MySQL高可用方案)、ProxySQL…...

【hadoop】Hive数据仓库安装部署

一、MySQL的安装与配置 换源: 最下面附加部分 1、在master上直接使用yum命令在线安装MySQL数据库: sudo yum install mysql-server 途中会询问是否继续,输入Y并按回车。 2、启动MySQL服务: sudo service mysqld start 3、设…...

Unity Addressables资源生命周期自动化监控技术详解

一、Addressables资源生命周期管理痛点 1. 常见资源泄漏场景 泄漏类型典型表现检测难度隐式引用泄漏脚本持有AssetReference未释放高异步操作未处理AsyncOperationHandle未释放中循环依赖泄漏资源相互引用无法释放极高事件订阅泄漏未取消事件监听导致对象保留高 2. 传统管理…...

Linux网络编程——深入理解TCP的可靠性、滑动窗口、流量控制、拥塞控制

目录 一、前言 二、流量控制 三、TCP的滑动窗口 1、原理 2、机制 3、数据重发 Ⅰ、只是确认应答包(ACK)丢了 Ⅱ、发送数据包丢失 4、缓冲区结构 四、TCP的拥塞控制 1、慢启动 2、拥塞避免 3、快速重传 4、快速恢复 五、延迟应答 六、捎带应答 七、再谈TCP的面…...

Manifold-IJ 2022.1.21 版本解析:IntelliJ IDEA 的 Java 增强插件指南

Manifold-IJ-2022.1.21 可能是 IntelliJ IDEA 的一个插件或相关版本,特别是与 Manifold 这个增强 Java 开发体验的框架相关的组件。 很多时候没有网络环境,而又需要这个插件。 Manifold-IJ 2022.1.21下载:https://pan.quark.cn/s/ad907344c…...

linux内核

一 初识linux内核 1.1操作系统和内核简介 操作系统的精确定义并没有一个统一的标准,这里我认为操作系统是指整个系统负责完成最基本功能和系统管理的那些部分 这些部分包括内核,设备驱动程序,启动引导程序,基本的文件管理工具和…...

基于CNN-LSTM-GRU的深度Q网络(Deep Q-Network,DQN)求解移动机器人路径规划,MATLAB代码

一、深度Q网络(Deep Q-Network,DQN)介绍 1、背景与动机 深度Q网络(DQN)是深度强化学习领域的里程碑算法,由DeepMind于2013年提出。它首次在 Atari 2600 游戏上实现了超越人类的表现,解决了传统…...

C++23新特性:显式对象形参与显式对象成员函数

文章目录 一、背景与动机二、语法与基本使用三、优势与应用场景(一)简化代码(二)提升模板编程灵活性(三)与Lambda表达式结合 四、限制与注意事项五、总结 C23标准引入了一项重要的语言特性——显式对象形参…...

leetcode_242. 有效的字母异位词_java

242. 有效的字母异位词https://leetcode.cn/problems/valid-anagram/ 1、题目 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词(字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次…...

【Docker基础】容器技术详解:生命周期、命令与实战案例

文章目录 一、什么是容器?二、为什么需要容器三、容器的生命周期容器状态容器OOM容器异常退出容器异常退出容器暂停 四、容器命令命令清单详细介绍 五、容器操作案例容器的状态迁移容器批量操作容器交互模式attached 模式detached 模式interactive 模式 容器 与 宿主…...

电子电气架构 --- 为配备区域计算的下一代电子/电气(E/E)架构

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…...

python基础:位置互换

n int(input()) for _ in range(n):line input().strip()line list(line)for i in range(1,len(line)1):if i%2 0:line[i-2], line[i-1] line[i-1],line[i-2] print(.join(line))以下分不同数据类型说明 Python 实现奇偶互换的方法: 字符串的奇偶位互换 若字…...

51单片机Day03---让一个LED灯闪烁

目录 1.研究原理图: 2.一些小知识(重定义的使用): (1)在单片机中,unsigned int 常用于以下场景: (2)unsigned char: 3.思路构造:…...

城电科技 | 从概念到落地:如何打造真正的智慧零碳园区?

在科技飞速发展的当下,智慧零碳园区成为了引领未来发展的重要范式。那么,究竟什么是智慧零碳园区呢? 智慧零碳园区,是借助前沿信息技术,把物联网、云计算、大数据等技术深度融入园区管理及产业运营,以此达…...

oracle常见问题处理集锦

oracle常见问题处理集锦 oracle常见问题处理集锦ORA:28000 the count is locked oracle常见问题处理集锦 ORA:28000 the count is locked ORA-28000: 账户已被锁定 这个错误表示你尝试登录的 Oracle 数据库用户账户已被锁定,常见原因包括: 多次密码输错…...

Java-JDBC入门程序、预编译SQL

一. JDBC JDBC:Java DataBase Connectivity 就是使用Java语言操作关系型数据库的一套API 本质:sun公司官方定义一套操作所有关系型数据库的规范,即接口;各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使…...

【SQL】基于多源SQL 去重方法对比 -- 精华版

【SQL】基于SQL 去重方法对比 -- 精华版 一、引言二、基于SQL去重方法完整对比1. MySQL去重方法及优劣势1.1 ​DISTINCT关键字1.2 GROUP BY子句1.3 UNION系列操作1.4 子查询 自关联 2. Hive去重方法及优劣势2.1 DISTINCT关键字2.2 ​GROUP BY子句2.3 ​ROW_NUMBER窗口函数2.4 …...

list的使用以及模拟实现

本章目标 1.list的使用 2.list的模拟实现 1.list的使用 在stl中list是一个链表,并且是一个双向带头循环链表,这种结构的链表是最优结构. 因为它的实现上也是一块线性空间,它的使用上是与string和vector类似的.但相对的因为底层物理结构上它并不像vector是线性连续的,它并没有…...

java继承练习

//创建父类public class Employee {private String id;private String name;private double salary;public Employee() {}public Employee(String id, String name, double salary) {this.id id;this.name name;this.salary salary;}public String getId() {return id;}pu…...

猫咪如厕检测与分类识别系统系列【一】 功能需求分析及猫咪分类特征提取

开发背景 家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的…...

sparkcore编程算子

今天是Spark Core编程算子 Value类型算子 1. map 将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。Map算子是分区内一个数据一个数据的执行,类似于串行操作。 特点: - 主要目的将数据源中的数据进行…...

【EI会议】第三届机器人与软件工程前沿国际会议(FRSE 2025)

第三届机器人与软件工程前沿国际会议(FRSE 2025)将于2025年8月8日-10日在中国张家界召开。会议由清华大学自动化系主办,长沙理工大学、湖南科技大学、吉首大学、AC学术中心协办。 一、会议信息 大会官网:www.icfrse.org 会议时…...

机器人系统仿真--1.机器人模型URDF

添加机器人地盘...

具身机器人中AI(DEEPSEEK)与PLC、驱动器协同发展研究:突破数据困境与指令精确控制(3)

具身机器人中AI(DEEPSEEK)与PLC、驱动器协同发展研究:突破数据困境与指令精确控制(1)-CSDN博客 具身机器人中AI(DEEPSEEK)与PLC、驱动器协同发展研究:突破数据困境与指令精确控制&a…...

+++++背到厌倦。持续更新

Spring IoC 的工作流程: 读取 BeanDefinition: Spring 容器启动时,会读取 Bean 的配置信息 (例如 XML 配置文件、注解或 Java 代码),并将这些配置信息转换为 BeanDefinition 对象。创建 Bean 实例: 根据 BeanDefinition 中的信息,Spring 容器…...

修改 docker 工作目录

一、停掉 containerd、cri-docker、docker systemctl stop containerd systemctl stop cri-docker systemctl stop docker 二、拷贝 docker 工作目录下的所有文件到新路径 rsync -aP /var/lib/docker/ /docker/data/ 三、daemon.json 添加新工作目录路径 {"registry-…...

51c嵌入式~继电器~合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/13775821 一、继电器应用细节 继电器的应用,相信大家都知道,在电路中只要给它供电、断电也就可以工作了。本文讨论它的应用细节。 现在流行的接法 图中,继电器的线圈经过Q1作为开关&am…...

舵机:机器人领域的“关节革命者”

机器人的技术,每一个细微的进步都可能引领一场行业变革。而在这场变革中,舵机作为机器人关节的核心部件,正悄然上演着一场革命性的应用风暴。从简单的关节运动到复杂的姿态控制,舵机以其卓越的性能和无限的可能,重新定…...

飞书集成衡石ChatBot实战:如何10分钟搭建一个业务数据问答机器人?

让数据查询像聊天一样简单 在快节奏的业务环境中,数据查询的实时性和便捷性至关重要。传统BI工具需要复杂的操作,而衡石ChatBot结合飞书,让业务人员只需在聊天窗口提问,就能立刻获取数据反馈,真正实现“零门槛”数据分…...

高并发环境下超发现象的详细分析,包含场景示例、影响分析及解决方案(悲观锁、乐观锁、分布式锁)

以下是针对高并发环境下超发现象的详细分析,包含场景示例、影响分析及解决方案: 高并发下的超发详解 1. 超发现象定义 超发(Over-issuance)指在并发操作中,系统实际发放的资源(如商品库存)超过…...

Git 分支整合策略:Cherry-pick、Merge、Rebase 三者之间对比

Git 分支整合策略详解:Cherry-pick、Merge、Rebase 在日常的 Git 多分支协作开发中,代码合并是常见操作。Git 中主要提供以下三种方式来合并或迁移分支的提交: Cherry-pick:精确挑选部分提交复制到当前分支;Merge&am…...

嵌入式八股---计算机网络篇

前言 这块主要是结合着LWIP去理解计算机网络中常见的面试题 OSI四层/五层/七层模型 OSI分层(7层):物理层、数据链路层、网络层、传输层、会话层(http)、表示层(加密)、应用层。 TCP/IP分层(4层):网络接口层…...

使用 3D Layout 和 Icepak 进行 PCB、DCIR 和热分析

在本教程中,您将学习如何使用 3D Layout 执行 DCIR,然后使用功率损耗数据执行热分析。热分析将使用电子桌面 Icepak 进行。SIwave 及其嵌入式 icepak 可用于执行相同的分析,但有一个例外。电子桌面 Icepak 是一款功能齐全的 3D 工具。用户可以…...

UE5 Windows游戏窗口置顶

参考资料:UE5 UE4 项目设置全局置顶_ue4运行设置置顶-CSDN博客 修改完build.cs后,关掉重新生成解决方案。(不然可能编译报错,在这卡了半个小时) 不知道怎么用C的,可以用这个 Topmost - Keep Editor/Game w…...

【Linux】进程管理

一、程序与进程区别 1.程序: 存放在磁盘文件可执行文件(静态存在) 特点 静态性:程序是静态的,它只是一组指令的集合,在未被执行时,不会占用计算机的运行资源,也不会产生任何实际的…...

Android Studio PNG转SVG方法总结

在 Android Studio 中,将 PNG 位图转换为 SVG 矢量图并非直接内置的功能,但你可以通过以下步骤实现目标: 方法 1:使用在线转换工具 访问在线转换网站 推荐工具: CloudConvert Vector Magic OnlineConvertFree 上传…...

第6篇:Linux程序访问控制FPGA端LEDR<四>

Q:如何设计.c程序代码控制FPGA端外设LEDR动态显示? A:我们来设计程序实现简易计数器:将上一期点亮LEDR的程序代码*LEDR_ptr 0x2aa 改为 *LEDR_ptr *LEDR_ptr 1,读取LEDR端口的data寄存器,将寄存器值递增…...

DP扰码模块verilog仿真

在DisplayPort 1.4协议中,为了减少EMI,在8B/10B编码之前,需进行扰码Scramble。扰码用到了16-bit LFSR,表达式如下。 LFSR每移位8个bit后,用最高有效 8 位以相反的位顺序与一个字节数据进行异或从而实现数据加扰/解扰。…...

协作焊接机器人的应用场景

协作焊接机器人凭借其灵活性、安全性和高效性,在多个领域有着广泛的应用场景,以下是一些主要的方面: 汽车制造 车身焊接:在汽车车身生产线上,协作焊接机器人可与工人协同工作,完成车身各部件的焊接任务。例…...

深入解析计算机操作系统的底层架构与核心模块功能

深入解析计算机操作系统的底层架构与核心模块功能 一、操作系统底层架构总览 操作系统处于计算机系统的核心地位,是计算机硬件与用户之间的关键纽带,承担着资源管理者的重要角色。它负责统筹管理计算机的各类资源,如CPU、内存、存储设备以及…...

Elasticsearch 官网阅读学习笔记01

Elasticsearch 官网阅读学习笔记01 什么是 Elasticsearch? Elasticsearch 是位于 Elastic Stack 核心的分布式搜索和分析引擎。Elasticsearch 可为所有类型的数据提供近乎实时的搜索和分析。无论您拥有的是结构化或非结构化文本、数值数据还是地理空间数据 Elastic…...

玩转Docker | 使用Docker搭建Van-Nav导航站

玩转Docker | 使用Docker搭建Van-Nav导航站 前言一、Van-Nav介绍van-nav 简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署Van-Nav服务下载镜像创建容器检查容器状态检查服务端口安全设置四、访问Van-Nav应用访问Van-Nav首页登录后台管理五、添…...

若依 前后端部署

后端:直接把代码从gitee上拉去到本地目录 (https://gitee.com/y_project/RuoYi-Vue ) 注意下redis连接时password改auth 后端启动成功 前端:运行前首先确保安装了node环境,随后执行: !!一定要用管理员权限…...

笔记:头文件与静态库的使用及组织方式

笔记:头文件与静态库的使用及组织方式 1. 头文件的作用 接口声明:提供函数、类、变量等标识符的声明,供其他模块调用。编译依赖:编译器需要头文件来验证函数调用和类型匹配。避免重复定义:通过包含保护(如…...

PostgreSQL-常用命令

PostgreSQL 提供了丰富的命令行工具和 SQL 命令,用于管理和操作数据库。以下是一些常用的命令和操作: 1. 数据库管理 创建数据库 CREATE DATABASE dbname; 删除数据库 DROP DATABASE dbname; 列出所有数据库 \l SELECT datname FROM pg_database;…...

如何在 Postman(测试工具) 中实现 Cookie 持久化并保持同一会话

在开发基于 Spring Boot 的 Web 应用时,使用 Session 存储验证码等敏感信息是常见的做法。然而,在调试接口时,你可能会遇到这样一个问题:第一次请求接口时存入的验证码在第二次请求时无法获取,原因往往是两个请求所使用…...

粘性定位(position:sticky)——微信小程序学习笔记

1. 简介 CSS 中的粘性定位(Sticky positioning)是一种特殊的定位方式,它可以使元素在滚动时保持在视窗的特定位置,类似于相对定位(relative),但当页面滚动到元素的位置时,它会表现得…...

谷歌浏览器极速安装指南

目录 📋 准备工作 步骤一:访问官网 🌐 步骤二:获取安装包 ⬇️ 步骤三:一键安装 🖱️ 步骤四:首次启动设置 ⚙️ 步骤五:开始探索! 🌟 💬 …...