引言
在之前的博客中,我们已经实现了一个相对完整的播放器,具备了基本功能,如播放、暂停、播放进度显示和拖拽快进等。这为我们提供了一个坚实的基础。接下来,我们将进一步扩展播放器的功能,使其更具灵活性和实用性:
- 支持多音轨播放:允许用户根据需要选择不同语言的音轨
- 添加和切换字幕:为视频提供多语言字幕支持
- 倍速播放:提供快进、慢放等不同的播放速度选项
- 横竖屏切换:根据设备方向变化自动调整播放器布局,提升用户体验
通过这些进阶功能,我们的播放器将变得更加智能,能够适应更多的使用场景。
协议
我们需要定义个新的协议用来定义资源轨道处理相关的方法,包括音频轨道数据,字幕轨道数据。
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 的新特性,探索更多可能性,提升播放器的体验。
希望这篇博客对你有所帮助,也欢迎你在评论区分享自己的想法和问题!
感谢阅读,我们下次再见!