引言
在之前的博客中,我們已經實現了一個相對完整的播放器,具備了基本功能,如播放、暫停、播放進度顯示和拖拽快進等。這為我們提供了一個堅實的基礎。接下來,我們將進一步擴展播放器的功能,使其更具靈活性和實用性:
- 支持多音軌播放:允許用戶根據需要選擇不同語言的音軌
- 添加和切換字幕:為視頻提供多語言字幕支持
- 倍速播放:提供快進、慢放等不同的播放速度選項
- 橫豎屏切換:根據設備方向變化自動調整播放器布局,提升用戶體驗
通過這些進階功能,我們的播放器將變得更加智能,能夠適應更多的使用場景。
協議
我們需要定義個新的協議用來定義資源軌道處理相關的方法,包括音頻軌道數據,字幕軌道數據。
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)}}
- 默認速度有四個常用選項0.5、1.0、1.5、2.0。
- 根據選項來創建選項按鈕。
- 當用戶選擇對應的速度時,通過閉包回調出去。
當用戶點擊倍速按鈕時,開始創建并添加倍速的選項視圖,具體代碼如下:
/// 倍速按鈕點擊事件@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()}}
- 我們選擇將列表視圖添加在?PHPlayerOverlayView 上面。
- 當用戶修改播放速度時,首先通過delegate修改播放器的rate屬性。
- 然后同步倍速按鈕顯示狀態,同時移除當前列表。
橫豎屏切換(手動控制)
在視頻播放的場景中,**全屏播放(橫屏)**常常帶來更沉浸的觀看體驗。而豎屏狀態下則方便瀏覽其他界面信息。
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 的功能,包括:
- ??多音軌切換,讓用戶能夠自由選擇不同的音頻語言軌道;
- ??字幕管理,通過?AVPlayerItemLegibleOutput?實現動態字幕展示及切換;
- ??倍速播放,支持快進、慢放等播放速度的調整;
- ??橫豎屏切換,通過自定義按鈕實現視頻播放器的方向控制。
這些功能的實現讓我們的播放器更加智能和靈活,也提高了用戶的觀看體驗。通過這些擴展,你可以構建出一個功能完備的本地視頻播放器,滿足更復雜的播放需求。
接下來,你可以根據需要進一步優化界面的響應能力、網絡資源的支持,或是考慮實現更豐富的視頻控制功能,如畫中畫、縮放、分屏等。未來還可以結合 iOS 的新特性,探索更多可能性,提升播放器的體驗。
希望這篇博客對你有所幫助,也歡迎你在評論區分享自己的想法和問題!
感謝閱讀,我們下次再見!