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

引言

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

设置标题

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

添加方法

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

protocol PHControlDelegate:NSObjectProtocol {
    
    var delegate:PHPlayerDelegate? { get set }
    /// 开始播放
    func playstart()
    
    /// 设置当前时间
    ///
    /// - Parameters:
    ///   - time: 当前时间
    ///   - duration: 总时间
    func setCuttentTime(time:TimeInterval,duration:TimeInterval)
    
    /// 设置视频标题
    ///
    /// - Parameters:
    ///   - title: 标题
    func setTitle(title:String)
    
    /// 设置字幕标题
    ///
    /// - Parameters:
    ///   - titles: 字幕标题数组
    func setSubtitle(titles:[String])
    
    /// 播放完成
    func playbackComplete()
}

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

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



加载commonMetadata数据

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

/// 准备播放
    private func prepareToPlay() {
        let keys = ["tracks","duration","commonMetadata"]
        guard let asset = asset else { return }
        playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: keys)
        guard let playerItem = playerItem else { return }
        player = AVPlayer(playerItem: playerItem)
        guard let player = player else { return }
        playerView = PHPlayerView(player: player)
        self.delegate = playerView?.controlView
        self.delegate?.delegate = self
        playerItem.addObserver(self, forKeyPath: status_keypath, context: &playerItemContext)
    }
    

获取标题数据

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

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &playerItemContext {
            guard let playerItem = playerItem else { return }
            guard let player = player else { return }
            if playerItem.status == .readyToPlay{
                playerItem.removeObserver(self, forKeyPath: status_keypath)
                player.play()
                let duration = playerItem.duration
                // 同步页面开始播放
                self.delegate?.playstart()
                // 同步时间
                self.delegate?.setCuttentTime(time: 0.0, duration: CMTimeGetSeconds(duration))
                // 设置标题
                let assetTitle = assertTitle()
                self.delegate?.setTitle(title: assetTitle)
                // 监听播放进度
                addPlayerItemTimeObserver()
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }

获取标题数据。

/// 获取
    func assertTitle() -> String {
        var title = ""
        guard let asset = asset else { return title }
        let status = asset.status(of: .commonMetadata)
        if case .loaded(let metatadaItems) = status {
            let titleItem = metatadaItems.first
            let itemStatus = titleItem?.status(of: .value)
            if case .loaded(let value) = itemStatus {
                if let itemTitle = value as? String {
                    title = itemTitle
                }
            }
        }
        return title
    }

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

设置字幕

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

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

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

加载availableMediaCharacteristicsWithMediaSelectionOptions数据

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

 /// 准备播放
    private func prepareToPlay() {
        let keys = ["tracks","duration","commonMetadata","availableMediaCharacteristicsWithMediaSelectionOptions"]
        guard let asset = asset else { return }
        playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: keys)
        guard let playerItem = playerItem else { return }
        player = AVPlayer(playerItem: playerItem)
        guard let player = player else { return }
        playerView = PHPlayerView(player: player)
        self.delegate = playerView?.controlView
        self.delegate?.delegate = self
        playerItem.addObserver(self, forKeyPath: status_keypath, context: &playerItemContext)
    }
    

获取字幕数据

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

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &playerItemContext {
            guard let playerItem = playerItem else { return }
            guard let player = player else { return }
            if playerItem.status == .readyToPlay{
                playerItem.removeObserver(self, forKeyPath: status_keypath)
                player.play()
                let duration = playerItem.duration
                // 同步页面开始播放
                self.delegate?.playstart()
                // 同步时间
                self.delegate?.setCuttentTime(time: 0.0, duration: CMTimeGetSeconds(duration))
                // 设置标题
                let assetTitle = assertTitle()
                self.delegate?.setTitle(title: assetTitle)
                // 设置字幕
                let subtitles = loadMediaOptions()
                self.delegate?.setSubtitle(titles: subtitles)
                // 监听播放进度
                addPlayerItemTimeObserver()
                
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }

加载字幕方法实现。

/// 加载字幕
    func loadMediaOptions() -> [String] {
        var subtitles = [String]()
        guard let asset = asset else { return subtitles }
        let mc = AVMediaCharacteristic.legible
        asset.loadMediaSelectionGroup(for: mc) {[weak self] selectionGroup, error in
            guard let self = self, let selectionGroup = selectionGroup else { return }
            // 获取字幕选项
            for option in selectionGroup.options {
                let displayName = option.displayName
                subtitles.append(displayName)
            }
        }
        return  subtitles
    }

字幕选择页面实现

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

实现代理方法

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

实现点击事件。

// 显示字幕列表
    @objc func subtitleOnclick() {
        guard let subtitles = subtitles else { return }
        guard let delegate = delegate else { return }
        let alertViewController = UIAlertController(title: "选择字幕", message: nil, preferredStyle: .actionSheet)
        for subtitle in subtitles {
            let action = UIAlertAction(title: subtitle, style: .default) { action in
                delegate.selectedSubtitle(subtitle: subtitle)
            }
            alertViewController.addAction(action)
        }
        let cancel = UIAlertAction(title: "取消字幕", style: .cancel)
        alertViewController.addAction(cancel)
        self.window?.rootViewController?.present(alertViewController, animated: true)
    }

显示字幕

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

/// 指定字幕
    ///
    /// - Parameters:
    ///   - subtitle: 字幕名称
    func selectedSubtitle(subtitle: String) {
        guard let asset = asset else { return }
        guard let playerItem = playerItem else { return }
        let mc = AVMediaCharacteristic.legible
        asset.loadMediaSelectionGroup(for: mc) {[weak self] selectionGroup, error in
            guard let self = self, let selectionGroup = selectionGroup else { return }
            var selected = false
            // 获取字幕选项
            for option in selectionGroup.options {
                if option.displayName == subtitle {
                    playerItem.select(option, in: selectionGroup)
                    selected = true
                    break
                }
            }
            if selected == false {
                playerItem.select(nil, in: selectionGroup)
            }
        }
    }

结语

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

  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/411017.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue2实现无感刷新token

🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 📘 引言: &#x1f4…

大概了解一下G1收集器

在上一篇文章中(链接:大概了解一下CMS收集器)我们提到,CMS是一种主要针对旧生代对象进行回收的收集器。与CMS不同,G1号称“全功能的垃圾收集器”,对初生代内存和旧生代内存均进行管理。鉴于此,这…

如何多环境切换?如何在微服务配置多环境?

问题本质: nacos配置中心的配置是如何被项目读取到的?(nacos的配置中心和项目是如何联系的?) 注意:nacos有配置管理和服务管理,别弄混。自动注册的是服务管理!!! 1. 如何注册到nacos服务管理中心…

如何使用视频号下载提取器提取视频,推荐2种方法使用!

视频号下载提取视频号视频,推荐大家2个方法​! 前者简单,后者较为复杂,不过都可以提取视频号视频,大家可根据实际情况来使用​。 视频号下载工具提取器​? 1:通过搜一搜的这款搜索引擎找到自己…

【前端素材】推荐优质后台管理系统Xmino Admin平台模板(附源码)

一、需求分析 后台管理系统通常是指一个用于管理网站、应用程序或系统的管理界面,通常由管理员使用。其功能和设计思路可以根据具体需求和系统复杂性有所不同,但一般包括以下几个方面的功能和设计考虑: 功能分析: 用户管理&…

vant安装教程(基于vue3)

1、先安装 npm i vant 如果不行安装这个 yarn add vant 2、在main.js中引入即可 import { createApp } from vue import App from ./App.vue import router from ./router import store from ./store import { Button } from vant; import vant/lib/index.css;createApp(App).…

qt-C++笔记之事件过滤器

qt-C笔记之事件过滤器 —— 杭州 2024-02-25 code review! 文章目录 qt-C笔记之事件过滤器一.使用事件过滤器和不使用事件过滤器对比1.1.使用事件过滤器1.2.不使用事件过滤器1.3.比较 二.Qt 中事件过滤器存在的意义三.为什么要重写QObject的eventFilter方法?使用QO…

土耳其商务团一行莅临优积科技考察交流

7月31日土耳其商务代表Emre Arif Parlak等一行三人莅临优积科技考察交流,公司CEO刘其东携团队成员热情接待并深入交流。 商务团首先参观了我司产品生产基地,详细了解了钢结构模块的生产加工工艺流程和质量控制体系。随后参观了我司模块化学校样板房、模块…

Jmeter系列(2)目录介绍

目录 Jmeter目录介绍bin目录docsextrasliblicensesprintable_docs Jmeter目录介绍 在学习Jmeter之前,需要先对工具的目录有些了解,也会方便后续的学习 bin目录 examplesCSV目录中有CSV样例jmeter.batwindow 启动文件jmeter.shMac/linux的启动文件jmete…

企业想要高效上云?如何实现?

进入数字化、智能化时代以后,企业数字化转型已成为企业发展的必然趋势。浪潮之中,越来越多的企业开始积极探索上云路径,云上创新已经成为企业加速数字化转型,提升竞争力的必经之路。 赞奇与华为携手共创云桌面SaaS产品—赞奇云工作…

医院管理系统小程序

**🍅点赞收藏关注 → 私信领取本源代码、数据库🍅 本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目希望你能有所收获,少走一些弯路。🍅关注我不迷路🍅**一 、设计说明 1.1 研究背景…

解决方案 || 在Windows中运行含有bash命令的开源代码仓库

在Windows中运行含有bash命令的开源代码仓库 文章目录 在Windows中运行含有bash命令的开源代码仓库问题分析解决方案使用Git Bash使用Windows Subsystem for Linux (WSL)使用Cygwin 结论 问题分析 在开源社区中,许多项目都是基于Unix-like系统(如Linux或…

EXCEL如何从另一个表查找匹配信息

目录 1.背景:我们有一个目标呈现表,想要从另一个表中查询得到信息,比如根据身份证id查询该id的名字、性别等个人基本信息,或者从另一个财务信息表查询该id的工资信息等; 2.基础方法:利用VLOOKUP函数根据单…

操作系统--设备管理

一、设备控制器 我们的电脑设备可以接非常多的输入输出设备,比如键盘、鼠标、显示器、网卡、硬盘、打印机、音响等等,每个设备的用法和功能都不同。为了屏蔽设备之间的差异,每个设备都有一个叫设备控制器(Device Control&#xf…

淘金优化算法GRO求解不闭合MD-MTSP,可以修改旅行商个数及起点(提供MATLAB代码)

一、淘金优化算法GRO 淘金优化算法(Gold rush optimizer,GRO)由Kamran Zolf于2023年提出,其灵感来自淘金热,模拟淘金者进行黄金勘探行为。淘金优化算法(Gold rush optimizer,GRO)提…

PostgreSQL 与MySQL 对比使用

一、前言 博主的系统既有 用到MySQL 也有用到PostgreSQL ,之所以用到这两种数据库,主要是现在都是国产替代,虽然说这两款数据库也不是国产的,但是相对开源,oracle是不让用了。所以现在使用比较多的就是这两个关系型数据…

Canvas动画之豌豆射手

🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 往期热门专栏回顾 专栏…

力扣1290. 二进制链表转整数

Problem: 1290. 二进制链表转整数 文章目录 题目描述思路复杂度Code 题目描述 思路 1.记录一个变量res初始化为0,指针p指向链表头; 2.循环每次res res * 2 p -> val;p p -> next;(充分利用二进制数的特性;其中利用指针先…

Spring的生命周期

文章目录 第一章 对象的生命周期1.1 什么是对象的生命周期1.2 为什么要学习对象的生命周期1.3 生命周期的 3 个阶段1.3.1 创建阶段1.3.2 初始化阶段1.3.3 销毁阶段 1.4 总结 第二章 后置处理Bean2.1 后置处理Bean的运⾏原理分析2.2 BeanPostProcessor的开发步骤 第一章 对象的生…

哪个牌子的电视盒子好用?2024超强电视盒子排名

最近很多朋友问我电视盒子的相关问题,就目前来说,电视盒子的地位依然是不可取代的。我近来要发布的测评内容是哪个牌子的电视盒子好用,耗时两周进行对比后整理了电视盒子排名,看看哪些电视盒子是最值得入手的吧。 NO.1——泰捷新品…