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

引言

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

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

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

协议

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

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)
    }

}
  1. 默认速度有四个常用选项0.5、1.0、1.5、2.0。
  2. 根据选项来创建选项按钮。
  3. 当用户选择对应的速度时,通过闭包回调出去。

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

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()
        }
    }
  1. 我们选择将列表视图添加在 PHPlayerOverlayView 上面。
  2. 当用户修改播放速度时,首先通过delegate修改播放器的rate属性。
  3. 然后同步倍速按钮显示状态,同时移除当前列表。

横竖屏切换(手动控制)

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

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 的功能,包括:

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

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

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

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

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

相关推荐
daqinzl2 个月前
FFmpeg+SDL实现简易视频播放器
ffmpeg·sdl·视频播放器
程序猿玖月柒3 个月前
常见的多媒体框架(FFmpeg GStreamer DirectShow AVFoundation OpenMax)
ffmpeg·音视频·gstreamer·openmax·directshow·avfoundation
SuperHeroWu74 个月前
【HarmonyOS】使用AVPlayer播放音乐,导致系统其它应用音乐播放暂停 - 播放音频焦点管理
华为·音视频·harmonyos·avplayer·音频焦点·播放·暂停
飘逸高铁侠4 个月前
使用Python和OpenAI Whisper为视频生成字幕
python·whisper·音视频·字幕
bxprog8 个月前
调用 Python 开源库,获取油管英文视频的手动或自动英文srt字幕,以及自动中文简体翻译srt字幕
python·音视频·油管·字幕
超爱找事8 个月前
视频发光字体特效怎么做 会声会影字体怎么淡化退出 视频剪辑制作教程
经验分享·音视频·知识图谱·字幕·会声会影·视频剪辑制作教程
loong_XL9 个月前
google、windows自带语音识别中英文等实时字幕使用
asr·字幕·实时字幕
EelBarb9 个月前
python源码分享:视频srt字幕文件生成
python·音视频·字幕
Mac分享吧10 个月前
IINA for Mac v1.3.5 音视频软件 安装教程(保姆级)
macos·音频播放·视频播放器·iina·音视频播放