引言
本篇博客主要介绍使用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?.controlViewself.delegate?.delegate = selfplayerItem.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.firstlet 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?.controlViewself.delegate?.delegate = selfplayerItem.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.legibleasset.loadMediaSelectionGroup(for: mc) {[weak self] selectionGroup, error inguard let self = self, let selectionGroup = selectionGroup else { return }// 获取字幕选项for option in selectionGroup.options {let displayName = option.displayNamesubtitles.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 indelegate.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.legibleasset.loadMediaSelectionGroup(for: mc) {[weak self] selectionGroup, error inguard 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 = truebreak}}if selected == false {playerItem.select(nil, in: selectionGroup)}}}
结语
显示标题和字幕的功能就完成了,主要就是读取AVAsset资源中的元数据并同步到视频播放的过程中,播放器的整体用户体验有了进一步的提升。到目前为止我们在处理的都是播放过程中是逻辑,但关于视频播放结束的处理也同样重要,下一篇博客就专门来介绍关于视频资源播放结束逻辑的实现。