(五)深入了解AVFoundation-播放:多音轨、字幕、倍速播放与横竖屏切换

引言

在之前的博客中,我们已经实现了一个相对完整的播放器,具备了基本功能,如播放、暂停、播放进度显示和拖拽快进等。这为我们提供了一个坚实的基础。接下来,我们将进一步扩展播放器的功能,使其更具灵活性和实用性:

  • 支持多音轨播放:允许用户根据需要选择不同语言的音轨
  • 添加和切换字幕:为视频提供多语言字幕支持
  • 倍速播放:提供快进、慢放等不同的播放速度选项
  • 横竖屏切换:根据设备方向变化自动调整播放器布局,提升用户体验

通过这些进阶功能,我们的播放器将变得更加智能,能够适应更多的使用场景。

协议

我们需要定义个新的协议用来定义资源轨道处理相关的方法,包括音频轨道数据,字幕轨道数据。

import Foundation
import AVFoundationprotocol PHTrackProtocol: NSObjectProtocol {/// 更换音频轨道(Switch)/// - Parameter audioSelectionOption: 音频信息/// - Parameter group: 音频组func switchAudioSelectionOption(audioSelectionOption: AVMediaSelectionOption,group: AVMediaSelectionGroup)/// 更换字幕可以为空奥/// - Parameter subtitleSelectionOption: 字幕信息/// - Parameter group: 字幕组func switchSubtitleSelectionOption(subtitleSelectionOption: AVMediaSelectionOption?,group: AVMediaSelectionGroup)}

多音轨的的处理

视频资源本身可以包含多个音轨,音轨会嵌入到视频文件中,例如使用MP4、MKV或其它多音轨支持的格式。当我们使用AVPlayer 播放这些视频时,播放器会自动识别并提供所有可用的音轨。

获取音轨信息

我们可以通过AVAsset 获取视频中的所有音轨信息。

    /// 获取资源的所有音轨func loadAudioSelectionOptions() {guard let asset = asset else {print("PHPlayerTrackManager : Asset is nil")return}guard let group = asset.mediaSelectionGroup(forMediaCharacteristic: .audible) else {print("PHPlayerTrackManager : Group is nil")return}let options = group.optionsfor option in options {print("音轨语言:\(option.locale?.languageCode)")}audioSelectionOptions = options ?? []self.group = group}

这段代码会返回所有的音轨信息,这个音频组 (audioGroup) 中包含了所有可用的音轨选项,每一个选项是一个 AVMediaSelectionOption,代表一种语言或音轨编码方式。

切换音轨

当用户选择了不同的音轨时,我们需要更新 AVPlayerItem 的音轨设置。

播放控制器 PHPlayerController 需要遵循 PHTrackProtocol 协议,当用户切换不同的音频轨道时,在switchAudioSelectionOption(audioSelectionOption:group:) 方法内实现切换的操作。

extension PHPlayerController:PHTrackProtocol {//MARK: 资源轨道相关协议/// 更换音频轨道(Switch)/// - Parameter audioSelectionOption: 音频信息/// - Parameter group: 音频组func switchAudioSelectionOption(audioSelectionOption: AVMediaSelectionOption,group: AVMediaSelectionGroup) {print("PHPlayerController: switchAudioSelectionOption")// 选择音轨playerItem.select(audioSelectionOption, in: group)}
}

添加和切换字幕

在上一节中我们提到,AVPlayer 对于多语言音轨的支持,是通过 AVAsset 中的 mediaSelectionGroup(forMediaCharacteristic:) 方法来实现的。实际上,字幕轨道的处理方式也是一样的。

获取字幕

如果一个视频包含字幕轨道,我们可以使用同样的方式来获取字幕选择组:

    /// 加载字幕选择组func loadSubtitleSelectionOptions() {guard let asset = asset else {print("PHPlayerTrackManager : Asset is nil")return}guard let group = asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else {print("PHPlayerTrackManager : Group is nil")return}let options = group.optionsfor option in options {print("字幕语言:\(option.locale?.languageCode)")}}

在 AVFoundation 中,legible 特指“可阅读”的轨道,通常用于字幕(Subtitles)或隐藏式字幕(Closed Captions)。接下来,我们就基于这个方式,来介绍如何实现字幕的显示与切换。

展示与切换字幕

当我们获取到字幕信息之后,可以构建一个选项列表,将所有可用的字幕展示给用户。

  • option.displayName:用于展示给用户(例如“English”)
  • option.locale?.languageCode:语言代码(例如 "en"、"zh")

当用户选择了字幕之后,仍然是通过 AVPlayerItem 进行设置。

    /// 更换字幕可以为空奥/// - Parameter subtitleSelectionOption: 字幕信息/// - Parameter group: 字幕组func switchSubtitleSelectionOption(subtitleSelectionOption: AVMediaSelectionOption?,group: AVMediaSelectionGroup) {print("PHPlayerController: switchSubtitleSelectionOption")// 选择字幕playerItem.select(subtitleSelectionOption, in: group)}

倍速播放

除了音轨和字幕之外,播放器的播放速度控制也是一个比较常见的需求,尤其在教学视频或长内容消费场景中,支持 加快或减慢播放速度,能大幅提升用户体验。

在 AVPlayer 中,实现倍速播放非常简单,只需设置 rate 属性即可:

player.rate = 1.5 // 播放速度设置为 1.5 倍

协议方法

在 PHControlProtocol 协议中我们再增加一个关于设置倍速协议方法,用作在控制视图内直接设置视频播放器的播放倍速。

protocol PHControlProtocol:NSObjectProtocol {.../// 设置倍速/// - Parameter rate: 倍速func setRate(rate: Float)}

UI

控制播放的速度,显然我们需要在 PHPlayerControlView 添加新的UI组件来实现这一功能。我们直接使用按钮,点击后显示播放的速度列表来供给用户选择。

    /// 倍速按钮let speedButton = UIButton(type: .custom)// 倍速按钮self.addSubview(speedButton)speedButton.setTitleColor(.white, for: .normal)speedButton.titleLabel?.font = UIFont.systemFont(ofSize: 14)speedButton.setTitle("1x", for: .normal)

列表的具体代码如下:

class PHPlayerRateListView: UIView {/// 选项数据let rateOptions: [Float] = [0.5, 1.0, 1.5, 2.0]/// 选中倍速private var selectedRate: Float = 1.0/// 选中倍速回调var rateSelected: ((Float) -> Void)?override init(frame: CGRect) {super.init(frame: frame)addRateItems()}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}private func addRateItems() {for i in 0..<rateOptions.count {let rate = rateOptions[i]let button = UIButton(type: .custom)button.setTitle("\(rate)x", for: .normal)button.tag = 100 + ibutton.addTarget(self, action: #selector(rateButtonTapped(_:)), for: .touchUpInside)self.addSubview(button)button.snp.makeConstraints { make inmake.top.equalToSuperview().offset(Double(i) * 40.0)make.leading.trailing.equalToSuperview()make.height.equalTo(40.0)if i == rateOptions.count - 1 {make.bottom.equalToSuperview()}}}}/// 选项按钮点击事件/// - Parameter sender: 按钮@objc private func rateButtonTapped(_ sender: UIButton) {let rate = rateOptions[sender.tag - 100]selectedRate = raterateSelected?(rate)}}
  1. 默认速度有四个常用选项0.5、1.0、1.5、2.0。
  2. 根据选项来创建选项按钮。
  3. 当用户选择对应的速度时,通过闭包回调出去。

当用户点击倍速按钮时,开始创建并添加倍速的选项视图,具体代码如下:

    /// 倍速按钮点击事件@objc private func speedButtonTapped() {guard let controlSuperView = self.superview else { return }// 创建倍速选项视图let rateListView = PHPlayerRateListView()rateListView.layer.masksToBounds = truerateListView.layer.cornerRadius = 8.0rateListView.backgroundColor = .black.withAlphaComponent(0.5)controlSuperView.addSubview(rateListView)rateListView.snp.makeConstraints { make inmake.centerX.equalTo(speedButton)make.bottom.equalTo(speedButton.snp.centerY)make.width.equalTo(60.0)}rateListView.rateSelected = { [weak self] rate inguard let self = self else { return }// 设置倍速self.delegate?.setRate(rate: rate)// 设置倍速按钮标题self.speedButton.setTitle("\(rate)x", for: .normal)// 移除倍速选项视图rateListView.removeFromSuperview()}}
  1. 我们选择将列表视图添加在 PHPlayerOverlayView 上面。
  2. 当用户修改播放速度时,首先通过delegate修改播放器的rate属性。
  3. 然后同步倍速按钮显示状态,同时移除当前列表。

横竖屏切换(手动控制)

在视频播放的场景中,**全屏播放(横屏)**常常带来更沉浸的观看体验。而竖屏状态下则方便浏览其他界面信息。

UI

既然是对播放器的控制,那么横竖屏切换按钮就很自然的需要在 PHPlayerControlView 的视图中来添加,除了按钮之外我们还需要定义一个属性,来记录屏幕的横竖屏状态。

    /// 横竖屏切换按钮let rotateButton = UIButton(type: .custom)/// 是否是横屏private(set) var isLandscape: Bool = false// 横竖屏切换按钮self.addSubview(rotateButton)rotateButton.setImage(UIImage(named: "ph_player_rotate"), for: .normal)

实现切换

当切换按钮时,我们并不需要任何回调,直接获取 windowScene 执行切换的方法。

    /// 横竖屏切换按钮点击事件@objc private func rotateButtonTapped() {// 横竖屏切换switchOrientation(to: isLandscape ? .portrait : .landscapeRight)}// 控制方向private  func switchOrientation(to orientation: UIInterfaceOrientation) {if let windowScene = self.window?.windowScene {let geometryPreferences = UIWindowScene.GeometryPreferences.iOS(interfaceOrientations: orientation == .landscapeRight ? .landscapeRight : .portrait)isLandscape = !isLandscapewindowScene.requestGeometryUpdate(geometryPreferences) {[weak self] error inguard let self = self else { return }// 处理错误self.isLandscape = !self.isLandscapeprint("切换方向失败: \(error.localizedDescription)")}}}

但这里面会有两个细节需要注意:

  • 当我们创建 PHPlayerOverlayView 和  PHPlayerView 两个视图并添加到视图控制器时没有使用约束布局,因此需要在 viewDidLayoutSubviews() 方法中 重新设置frame。
    override func viewDidLayoutSubviews() {super.viewDidLayoutSubviews()// 设置播放器视图playerView.frame = self.view.bounds// 设置覆盖视图overlayView.frame = self.view.bounds}
  • 当我们横屏播放时,那么可以忽略底部的安全距离,因此需要调整 PHPlayerControlView 的约束。
    override func layoutSubviews() {super.layoutSubviews()// 如果是横屏controlView.snp.updateConstraints { make inmake.height.equalTo(125.0 + (controlView.isLandscape ? 0.0 : MW_BOTTOM_SAFE_HEIGHT))}}

结语

通过本篇博客,我们详细介绍了如何在 iOS 中扩展 AVPlayer 的功能,包括:

  1. • 多音轨切换,让用户能够自由选择不同的音频语言轨道;
  2. • 字幕管理,通过 AVPlayerItemLegibleOutput 实现动态字幕展示及切换;
  3. • 倍速播放,支持快进、慢放等播放速度的调整;
  4. • 横竖屏切换,通过自定义按钮实现视频播放器的方向控制。

这些功能的实现让我们的播放器更加智能和灵活,也提高了用户的观看体验。通过这些扩展,你可以构建出一个功能完备的本地视频播放器,满足更复杂的播放需求。

接下来,你可以根据需要进一步优化界面的响应能力、网络资源的支持,或是考虑实现更丰富的视频控制功能,如画中画、缩放、分屏等。未来还可以结合 iOS 的新特性,探索更多可能性,提升播放器的体验。

希望这篇博客对你有所帮助,也欢迎你在评论区分享自己的想法和问题!

感谢阅读,我们下次再见!

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

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

相关文章

3ds Max 2016的版本怎么处理 按键输入被主程序截断 C#winform窗体接受不到英文输入

3ds Max 2016的版本怎么处理 按键输入被主程序截断 C#winform窗体接受不到英文输入 如果窗体失去焦点应该取消 全局监听事件 解决方案&#xff1a;在窗体失去焦点时取消全局键盘钩子 为了确保 WinForms 窗体失去焦点时不再拦截键盘事件&#xff08;避免影响 3ds Max 或其他程…

华为手机或平板与电脑实现文件共享

1.手机或平板与电脑在同一个网络 2.打开手机或平板端&#xff0c;设置---更多连接----快分享或华为分享打开此功能-----开启共享至电脑 3.打开电脑&#xff0c;网络中就可看到手机端分享的用户名称 4. 登陆就可访问手机 5.常见问题 5.1 电脑未发现本机 5.2 修改了访问密码后再…

elemenPlus中,如何去掉el-input中 文本域 textarea自带的边框和角标

1、去掉角标 :deep(.el-textarea__inner) {resize: none !important; // 去除右下角图标 }2、去除边框&#xff0c;并自定义背景色 <el-inputref"textareaRef"v-model"tempContent":style"{--el-border-color: rgba(255,255,255,0.0),--el-input-…

xv6-labs-2024 lab2

lab-2 0. 前置 课程记录 操作系统的隔离性&#xff0c;举例说明就是&#xff0c;当我们的shell&#xff0c;或者qq挂掉了&#xff0c;我们不希望因为他&#xff0c;去影响其他的进程&#xff0c;所以在不同的应用程序之间&#xff0c;需要有隔离性&#xff0c;并且&#xff0…

MCU控制4G模组(标准AT命令),CatM的最大速率?

根据3GPP标准&#xff0c;Cat M1的上行峰值速率大约是1 Mbps&#xff0c;下行大约是1 Mbps。但实际速率会受到多种因素影响&#xff0c;比如网络条件、信号强度、模块配置等。 考虑使用AT命令时的开销。每次发送数据都需要通过AT命令&#xff0c;比如ATQISEND&#xff0c;会引…

JavaScript(JS进阶)

目录 00闭包 01函数进阶 02解构赋值 03通过forEach方法遍历数组 04深入对象 05内置构造函数 06原型 00闭包 <!-- 闭包 --><html><body><script>// 定义&#xff1a;闭包内层函数&#xff08;匿名函数&#xff09;外层函数的变量&#xff08;s&…

6.1es新特性解构赋值

解构赋值是 ES6&#xff08;ECMAScript 2015&#xff09;引入的语法&#xff0c;通过模式匹配从数组或对象中提取值并赋值给变量。&#xff1a; 功能实现 数组解构&#xff1a;按位置匹配值&#xff0c;如 let [a, b] [1, 2]。对象解构&#xff1a;按属性名匹配值&#xff0c;…

SpringBoot美容院管理系统设计与实现

基于SpringBoot的美容院管理系统免费源码&#xff0c;帮助您快速搭建高效、智能的美容院管理平台。该系统涵盖了管理员、技师、前台、普通用户及会员五大功能模块&#xff0c;以下是系统的核心功能与部署方式详细介绍。 ​功能模块 ​管理员功能 ​美容部位管理&#xff1a;支…

记一次某网络安全比赛三阶段webserver应急响应解题过程

0X01 任务说明 0X02 靶机介绍 Webserver&#xff08;Web服务器&#xff09;是一种软件或硬件设备&#xff0c;用于接收、处理并响应来自客户端&#xff08;如浏览器&#xff09;的HTTP请求&#xff0c;提供网页、图片、视频等静态或动态内容&#xff0c;是互联网基础设施的核心…

ChatGPT 4:引领 AI 创作新时代

文章目录 前言一、ChatGPT 4 的技术革新二、AI 文案创作&#xff1a;精准生成与个性化定制三、AI 绘画艺术&#xff1a;从文字到图像的神奇转化四、AI 视频制作&#xff1a;自动化剪辑与创意实现五、知识库与 ChatGPT 4 的深度融合六、全新的变革和机遇七、相关书籍推荐《ChatG…

HTTP请求-请求行

请求行&#xff08;方法&#xff0c;URL&#xff0c;版本号&#xff09; 方法&#xff1a; 描述了这次请求的目的。 常见方法&#xff1a; GET&#xff1a;从服务器拿一个东西过来&#xff08;读操作&#xff09; POST&#xff1a;往服务器放一个东西去&#xff08;写操作…

OSPF不规则区域和LSA

OSPF不规则区域 1.远离骨干的非骨干区域 R1-R4四台路由器能够正常学习到彼此路由&#xff0c;但是R5不行&#xff0c;因为R5是非法ABR 解决方法&#xff1a; 1使用Tunnel隧道将AR4连接到骨干区域 &#xff08;1&#xff09; 使用隧道解决不规则区域的问题 a.可能造成选路不…

【VS Code】开发C++跳转配置

C配置c_cpp_properties.json {"env": {"myIncludePath": ["${workspaceFolder}/src/include","${workspaceFolder}/src","${workspaceFolder}","/home/xxx/include/"],"myDefines": ["RELEASE&qu…

Spring AI应用:利用DeepSeek+嵌入模型+Milvus向量数据库实现检索增强生成--RAG应用(超详细)

Spring AI应用&#xff1a;利用DeepSeek嵌入模型Milvus向量数据库实现检索增强生成–RAG应用&#xff08;超详细&#xff09; 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术的快速发展为各行业带来了前所未有的机遇。其中&#xff0c;检索增强生成&…

Spring 的 IoC 和 DI 详解:从零开始理解与实践

Spring 的 IoC和 DI 详解&#xff1a;从零开始理解与实践 一、IoC&#xff08;控制反转&#xff09; 1、什么是 IoC&#xff1f; IoC 是一种设计思想&#xff0c;它的核心是将对象的创建和管理权从开发者手中转移到外部容器&#xff08;如 Spring 容器&#xff09;。通过这种…

JVM基础架构:内存模型×Class文件结构×核心原理剖析

&#x1f680;前言 “为什么你的Java程序总在半夜OOM崩溃&#xff1f;为什么某些代码性能突然下降&#xff1f;一切问题的答案都在JVM里&#xff01; 作为Java开发者&#xff0c;如果你&#xff1a; 对OutOfMemoryError束手无策看不懂GC日志里的神秘数字好奇.class文件如何变…

.DS_Store文件泄露、.git目录泄露、.svn目录泄露漏洞利用工具

&#x1f409;工具介绍 一款图形化的 .DS_Store文件泄露、.git目录泄露、.svn目录泄露漏洞利用工具。 &#x1f3af;使用 本工具使用Python3 PyQt5开发&#xff0c;在开始使用前&#xff0c;请确保已经安装了相关模块&#xff1a; pip3 install -r requirements.txt -i ht…

为何在 FastAPI 中需要允许跨域访问(CORS)?(Grok3 回答)

prompt: 你是一个文笔流畅、专业性极强的技术博客博主&#xff0c;你将结合具体的例子和实际代码解释写一篇为何后端选择fastapi框架时&#xff0c;需要允许跨域访问。 为何在 FastAPI 中需要允许跨域访问&#xff08;CORS&#xff09;&#xff1f; 在现代 Web 开发中&#xf…

JDK8前后日期(计算两个日期时间差-高考倒计时)

JDK8之前日期、时间 Date SimpleDateFormat Calender JDK8开始日期、时间 LocalDate/LocalTime/LocalDateTime ZoneId/ZoneDateTIme Instant-时间毫秒值 DateTimeFormatter Duration/Period

Gerapy二次开发:用户管理专栏主页面开发

用户管理专栏主页面开发 写在前面用户权限控制用户列表接口设计主页面开发前端account/Index.vuelangs/zh.jsstore.js后端Paginator概述基本用法代码示例属性与方法urls.pyviews.py运行效果总结欢迎加入Gerapy二次开发教程专栏! 本专栏专为新手开发者精心策划了一系列内容,旨…