• 五.AV Foundation 视频播放 - 标题和字幕


    引言

    本篇博客主要介绍使用AV Foundation加载视频资源的时候,如何获取视频标题,获取字幕并让其显示到播放界面。

    设置标题

    资源标题的元数据内容,我们需要从资源的commonMetadata中获取,在加载AVPlayerItem的时候我们已经指定了需要加载commonMetadata数据,所以这里不需要做任何改动,可以在AVPlayerItem的status变为.readyToPlay的时候直接读取标题内容。

    添加方法

    首先我们需要在PHControlDelegate中添加两个代理方法,分别对应设置视频标题和设置设置字幕标题,并在PHControlView中实现。

    1. protocol PHControlDelegate:NSObjectProtocol {
    2. var delegate:PHPlayerDelegate? { get set }
    3. /// 开始播放
    4. func playstart()
    5. /// 设置当前时间
    6. ///
    7. /// - Parameters:
    8. /// - time: 当前时间
    9. /// - duration: 总时间
    10. func setCuttentTime(time:TimeInterval,duration:TimeInterval)
    11. /// 设置视频标题
    12. ///
    13. /// - Parameters:
    14. /// - title: 标题
    15. func setTitle(title:String)
    16. /// 设置字幕标题
    17. ///
    18. /// - Parameters:
    19. /// - titles: 字幕标题数组
    20. func setSubtitle(titles:[String])
    21. /// 播放完成
    22. func playbackComplete()
    23. }

    设置标题代理方法的实现。

    1. // 设置标题
    2. func setTitle(title: String) {
    3. titleLabel.text = title
    4. }



    加载commonMetadata数据

    首先来看一下创建AVPlayerItem的时候加载commonMetadata的实现。

    1. /// 准备播放
    2. private func prepareToPlay() {
    3. let keys = ["tracks","duration","commonMetadata"]
    4. guard let asset = asset else { return }
    5. playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: keys)
    6. guard let playerItem = playerItem else { return }
    7. player = AVPlayer(playerItem: playerItem)
    8. guard let player = player else { return }
    9. playerView = PHPlayerView(player: player)
    10. self.delegate = playerView?.controlView
    11. self.delegate?.delegate = self
    12. playerItem.addObserver(self, forKeyPath: status_keypath, context: &playerItemContext)
    13. }

    获取标题数据

    在视频准备好播放后,从commonMetadata中获取我们需要的视频标题数据。

    1. override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    2. if context == &playerItemContext {
    3. guard let playerItem = playerItem else { return }
    4. guard let player = player else { return }
    5. if playerItem.status == .readyToPlay{
    6. playerItem.removeObserver(self, forKeyPath: status_keypath)
    7. player.play()
    8. let duration = playerItem.duration
    9. // 同步页面开始播放
    10. self.delegate?.playstart()
    11. // 同步时间
    12. self.delegate?.setCuttentTime(time: 0.0, duration: CMTimeGetSeconds(duration))
    13. // 设置标题
    14. let assetTitle = assertTitle()
    15. self.delegate?.setTitle(title: assetTitle)
    16. // 监听播放进度
    17. addPlayerItemTimeObserver()
    18. }
    19. } else {
    20. super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    21. }
    22. }

    获取标题数据。

    1. /// 获取
    2. func assertTitle() -> String {
    3. var title = ""
    4. guard let asset = asset else { return title }
    5. let status = asset.status(of: .commonMetadata)
    6. if case .loaded(let metatadaItems) = status {
    7. let titleItem = metatadaItems.first
    8. let itemStatus = titleItem?.status(of: .value)
    9. if case .loaded(let value) = itemStatus {
    10. if let itemTitle = value as? String {
    11. title = itemTitle
    12. }
    13. }
    14. }
    15. return title
    16. }

    再次运行播放器会发现播放器的左上角已经显示出了视频的标题。

    设置字幕

    AV Foundation为显示字幕提供了非常可靠的方法,AVPlayerLayer会自动渲染这些元数据到页面上,并且还可以手动选择需要显示那种字幕。要完成这个功能需要用到AVMediaSelectionGrop和AVMediaSelectionOption这两个类。
     

    AVMediaSelectionOption标识AVAsset中的备用媒体呈现方式。比如备用音频、视频或者文本轨道。想要确定存在哪些备用轨道要用到一个名为availableMediaCharacteristicsWithMediaSelectionOptions属性。它会返回一个包含字符串的数组,这些字符串用于表示保存在资源中可用选项的媒体特征,包含AVMediaCharacteristicVisual(视频),AVMediaCharacteristicAudible(音频)、AVMediaCharacteristicLegible(字幕或隐藏式字幕)。

    请求可用媒体特性数据后,调用AVAsset的mediaSelectionGroupForMediaCharacteristic:方法,(iOS16后推荐使用loadMediaSelectionGroup(for mediaCharacteristic: AVMediaCharacteristic) 方法),为其传递要检索的选项的特定媒体特征。这个方法会返回一个AVMediaSelectionGroup,它作为一个或多个互斥的AVMediaSelectionOption实例的容器。

    加载availableMediaCharacteristicsWithMediaSelectionOptions数据

    下面就在视频准备开始播放的时候进行字幕数据的加载,在这之前呢和加载其它元数据一样需要在创建AVPlayerItem的时候指定所需加载的元数据。

    1. /// 准备播放
    2. private func prepareToPlay() {
    3. let keys = ["tracks","duration","commonMetadata","availableMediaCharacteristicsWithMediaSelectionOptions"]
    4. guard let asset = asset else { return }
    5. playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: keys)
    6. guard let playerItem = playerItem else { return }
    7. player = AVPlayer(playerItem: playerItem)
    8. guard let player = player else { return }
    9. playerView = PHPlayerView(player: player)
    10. self.delegate = playerView?.controlView
    11. self.delegate?.delegate = self
    12. playerItem.addObserver(self, forKeyPath: status_keypath, context: &playerItemContext)
    13. }

    获取字幕数据

    在监听到视频状态转换为.readyToPlay时开始调用方法处理字幕数据。

    1. override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    2. if context == &playerItemContext {
    3. guard let playerItem = playerItem else { return }
    4. guard let player = player else { return }
    5. if playerItem.status == .readyToPlay{
    6. playerItem.removeObserver(self, forKeyPath: status_keypath)
    7. player.play()
    8. let duration = playerItem.duration
    9. // 同步页面开始播放
    10. self.delegate?.playstart()
    11. // 同步时间
    12. self.delegate?.setCuttentTime(time: 0.0, duration: CMTimeGetSeconds(duration))
    13. // 设置标题
    14. let assetTitle = assertTitle()
    15. self.delegate?.setTitle(title: assetTitle)
    16. // 设置字幕
    17. let subtitles = loadMediaOptions()
    18. self.delegate?.setSubtitle(titles: subtitles)
    19. // 监听播放进度
    20. addPlayerItemTimeObserver()
    21. }
    22. } else {
    23. super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    24. }
    25. }

    加载字幕方法实现。

    1. /// 加载字幕
    2. func loadMediaOptions() -> [String] {
    3. var subtitles = [String]()
    4. guard let asset = asset else { return subtitles }
    5. let mc = AVMediaCharacteristic.legible
    6. asset.loadMediaSelectionGroup(for: mc) {[weak self] selectionGroup, error in
    7. guard let self = self, let selectionGroup = selectionGroup else { return }
    8. // 获取字幕选项
    9. for option in selectionGroup.options {
    10. let displayName = option.displayName
    11. subtitles.append(displayName)
    12. }
    13. }
    14. return subtitles
    15. }

    字幕选择页面实现

    获取到字幕数据后,我们借助delegate将其专递到了PHControlView中供使用,目前在控制页面还没有选择字幕的按钮,先来把它添加到播放器的右上角,并实现点击事件。

    实现代理方法

    1. // 设置字幕标题
    2. func setSubtitle(titles: [String]) {
    3. subtitles = titles
    4. }

    实现点击事件。

    1. // 显示字幕列表
    2. @objc func subtitleOnclick() {
    3. guard let subtitles = subtitles else { return }
    4. guard let delegate = delegate else { return }
    5. let alertViewController = UIAlertController(title: "选择字幕", message: nil, preferredStyle: .actionSheet)
    6. for subtitle in subtitles {
    7. let action = UIAlertAction(title: subtitle, style: .default) { action in
    8. delegate.selectedSubtitle(subtitle: subtitle)
    9. }
    10. alertViewController.addAction(action)
    11. }
    12. let cancel = UIAlertAction(title: "取消字幕", style: .cancel)
    13. alertViewController.addAction(cancel)
    14. self.window?.rootViewController?.present(alertViewController, animated: true)
    15. }

    显示字幕

    在播放控制器内,接收选择的字幕信息开始设置字幕。

    1. /// 指定字幕
    2. ///
    3. /// - Parameters:
    4. /// - subtitle: 字幕名称
    5. func selectedSubtitle(subtitle: String) {
    6. guard let asset = asset else { return }
    7. guard let playerItem = playerItem else { return }
    8. let mc = AVMediaCharacteristic.legible
    9. asset.loadMediaSelectionGroup(for: mc) {[weak self] selectionGroup, error in
    10. guard let self = self, let selectionGroup = selectionGroup else { return }
    11. var selected = false
    12. // 获取字幕选项
    13. for option in selectionGroup.options {
    14. if option.displayName == subtitle {
    15. playerItem.select(option, in: selectionGroup)
    16. selected = true
    17. break
    18. }
    19. }
    20. if selected == false {
    21. playerItem.select(nil, in: selectionGroup)
    22. }
    23. }
    24. }

    结语

    显示标题和字幕的功能就完成了,主要就是读取AVAsset资源中的元数据并同步到视频播放的过程中,播放器的整体用户体验有了进一步的提升。到目前为止我们在处理的都是播放过程中是逻辑,但关于视频播放结束的处理也同样重要,下一篇博客就专门来介绍关于视频资源播放结束逻辑的实现。

      

  • 相关阅读:
    指令寻址方式(重点记忆)
    sqlalchemy更新json 字段的部分字段
    Vue中使用组件
    php学习笔记
    人工智能时代的离散数学教学研究
    基于C++和QT实现的房贷计算器设计
    【附源码】计算机毕业设计java圆梦酒店管理系统设计与实现
    Android App links 链接打开app功能
    STM32F10x SPL V3.6.2 集成 FreeRTOS v202112
    P5960【模板】差分约束
  • 原文地址:https://blog.csdn.net/weixin_39339407/article/details/136175327