
引言
在之前的博客中,我们已经实现了一个相对完整的播放器,具备了基本功能,如播放、暂停、播放进度显示和拖拽快进等。这为我们提供了一个坚实的基础。接下来,我们将进一步扩展播放器的功能,使其更具灵活性和实用性:
- 支持多音轨播放:允许用户根据需要选择不同语言的音轨
- 添加和切换字幕:为视频提供多语言字幕支持
- 倍速播放:提供快进、慢放等不同的播放速度选项
- 横竖屏切换:根据设备方向变化自动调整播放器布局,提升用户体验
通过这些进阶功能,我们的播放器将变得更加智能,能够适应更多的使用场景。
协议
我们需要定义个新的协议用来定义资源轨道处理相关的方法,包括音频轨道数据,字幕轨道数据。
Swift
import Foundation
import AVFoundation
protocol 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 获取视频中的所有音轨信息。
Swift
/// 获取资源的所有音轨
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.options
for option in options {
print("音轨语言:\(option.locale?.languageCode)")
}
audioSelectionOptions = options ?? []
self.group = group
}
这段代码会返回所有的音轨信息,这个音频组 (audioGroup) 中包含了所有可用的音轨选项,每一个选项是一个 AVMediaSelectionOption,代表一种语言或音轨编码方式。
切换音轨
当用户选择了不同的音轨时,我们需要更新 AVPlayerItem 的音轨设置。
播放控制器 PHPlayerController 需要遵循 PHTrackProtocol 协议,当用户切换不同的音频轨道时,在switchAudioSelectionOption(audioSelectionOption:group:) 方法内实现切换的操作。
Swift
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:) 方法来实现的。实际上,字幕轨道的处理方式也是一样的。
获取字幕
如果一个视频包含字幕轨道,我们可以使用同样的方式来获取字幕选择组:
Swift
/// 加载字幕选择组
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.options
for option in options {
print("字幕语言:\(option.locale?.languageCode)")
}
}
在 AVFoundation 中,legible 特指"可阅读"的轨道,通常用于字幕(Subtitles)或隐藏式字幕(Closed Captions)。接下来,我们就基于这个方式,来介绍如何实现字幕的显示与切换。
展示与切换字幕
当我们获取到字幕信息之后,可以构建一个选项列表,将所有可用的字幕展示给用户。
- option.displayName:用于展示给用户(例如"English")
- option.locale?.languageCode:语言代码(例如 "en"、"zh")
当用户选择了字幕之后,仍然是通过 AVPlayerItem 进行设置。
Swift
/// 更换字幕可以为空奥
/// - Parameter subtitleSelectionOption: 字幕信息
/// - Parameter group: 字幕组
func switchSubtitleSelectionOption(subtitleSelectionOption: AVMediaSelectionOption?,group: AVMediaSelectionGroup) {
print("PHPlayerController: switchSubtitleSelectionOption")
// 选择字幕
playerItem.select(subtitleSelectionOption, in: group)
}
倍速播放
除了音轨和字幕之外,播放器的播放速度控制也是一个比较常见的需求,尤其在教学视频或长内容消费场景中,支持 加快或减慢播放速度,能大幅提升用户体验。
在 AVPlayer 中,实现倍速播放非常简单,只需设置 rate 属性即可:
Swift
player.rate = 1.5 // 播放速度设置为 1.5 倍
协议方法
在 PHControlProtocol 协议中我们再增加一个关于设置倍速协议方法,用作在控制视图内直接设置视频播放器的播放倍速。
Swift
protocol PHControlProtocol:NSObjectProtocol {
...
/// 设置倍速
/// - Parameter rate: 倍速
func setRate(rate: Float)
}
UI
控制播放的速度,显然我们需要在 PHPlayerControlView 添加新的UI组件来实现这一功能。我们直接使用按钮,点击后显示播放的速度列表来供给用户选择。
Swift
/// 倍速按钮
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)
列表的具体代码如下:
Swift
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 + i
button.addTarget(self, action: #selector(rateButtonTapped(_:)), for: .touchUpInside)
self.addSubview(button)
button.snp.makeConstraints { make in
make.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 = rate
rateSelected?(rate)
}
}
- 默认速度有四个常用选项0.5、1.0、1.5、2.0。
- 根据选项来创建选项按钮。
- 当用户选择对应的速度时,通过闭包回调出去。
当用户点击倍速按钮时,开始创建并添加倍速的选项视图,具体代码如下:
Swift
/// 倍速按钮点击事件
@objc private func speedButtonTapped() {
guard let controlSuperView = self.superview else { return }
// 创建倍速选项视图
let rateListView = PHPlayerRateListView()
rateListView.layer.masksToBounds = true
rateListView.layer.cornerRadius = 8.0
rateListView.backgroundColor = .black.withAlphaComponent(0.5)
controlSuperView.addSubview(rateListView)
rateListView.snp.makeConstraints { make in
make.centerX.equalTo(speedButton)
make.bottom.equalTo(speedButton.snp.centerY)
make.width.equalTo(60.0)
}
rateListView.rateSelected = { [weak self] rate in
guard let self = self else { return }
// 设置倍速
self.delegate?.setRate(rate: rate)
// 设置倍速按钮标题
self.speedButton.setTitle("\(rate)x", for: .normal)
// 移除倍速选项视图
rateListView.removeFromSuperview()
}
}
- 我们选择将列表视图添加在 PHPlayerOverlayView 上面。
- 当用户修改播放速度时,首先通过delegate修改播放器的rate属性。
- 然后同步倍速按钮显示状态,同时移除当前列表。

横竖屏切换(手动控制)
在视频播放的场景中,**全屏播放(横屏)**常常带来更沉浸的观看体验。而竖屏状态下则方便浏览其他界面信息。
UI
既然是对播放器的控制,那么横竖屏切换按钮就很自然的需要在 PHPlayerControlView 的视图中来添加,除了按钮之外我们还需要定义一个属性,来记录屏幕的横竖屏状态。
Swift
/// 横竖屏切换按钮
let rotateButton = UIButton(type: .custom)
/// 是否是横屏
private(set) var isLandscape: Bool = false
// 横竖屏切换按钮
self.addSubview(rotateButton)
rotateButton.setImage(UIImage(named: "ph_player_rotate"), for: .normal)
实现切换
当切换按钮时,我们并不需要任何回调,直接获取 windowScene 执行切换的方法。
Swift
/// 横竖屏切换按钮点击事件
@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 = !isLandscape
windowScene.requestGeometryUpdate(geometryPreferences) {[weak self] error in
guard let self = self else { return }
// 处理错误
self.isLandscape = !self.isLandscape
print("切换方向失败: \(error.localizedDescription)")
}
}
}
但这里面会有两个细节需要注意:
- 当我们创建 PHPlayerOverlayView 和 PHPlayerView 两个视图并添加到视图控制器时没有使用约束布局,因此需要在 viewDidLayoutSubviews() 方法中 重新设置frame。
Swift
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// 设置播放器视图
playerView.frame = self.view.bounds
// 设置覆盖视图
overlayView.frame = self.view.bounds
}
- 当我们横屏播放时,那么可以忽略底部的安全距离,因此需要调整 PHPlayerControlView 的约束。
Swift
override func layoutSubviews() {
super.layoutSubviews()
// 如果是横屏
controlView.snp.updateConstraints { make in
make.height.equalTo(125.0 + (controlView.isLandscape ? 0.0 : MW_BOTTOM_SAFE_HEIGHT))
}
}

结语
通过本篇博客,我们详细介绍了如何在 iOS 中扩展 AVPlayer 的功能,包括:
- • 多音轨切换,让用户能够自由选择不同的音频语言轨道;
- • 字幕管理,通过 AVPlayerItemLegibleOutput 实现动态字幕展示及切换;
- • 倍速播放,支持快进、慢放等播放速度的调整;
- • 横竖屏切换,通过自定义按钮实现视频播放器的方向控制。
这些功能的实现让我们的播放器更加智能和灵活,也提高了用户的观看体验。通过这些扩展,你可以构建出一个功能完备的本地视频播放器,满足更复杂的播放需求。
接下来,你可以根据需要进一步优化界面的响应能力、网络资源的支持,或是考虑实现更丰富的视频控制功能,如画中画、缩放、分屏等。未来还可以结合 iOS 的新特性,探索更多可能性,提升播放器的体验。
希望这篇博客对你有所帮助,也欢迎你在评论区分享自己的想法和问题!
感谢阅读,我们下次再见!