(五)深入了解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.pswp.cn/pingmian/76280.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76280.shtml
英文地址,請注明出處:http://en.pswp.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二次開發教程專欄! 本專欄專為新手開發者精心策劃了一系列內容,旨…