公告:九九网站目录为广大站长提供免费收录网站服务,会员可在线完成投稿无需添加友情链接。只收录内容正规合法的网站;快审网站软文10元

点击这里在线咨询客服
新站提交
  • 网站:8464
  • 待审:14
  • 小程序:9
  • 文章:3613
  • 会员:21118

 文章所有内容均来自官方官方文档,作者仅做一个总结归纳,加深自身记忆。有能力的同学推荐阅读原文

框架结构图

1. 简介

苹果目前提供两个框架用来处理音视频播放

1.AVFoundation

AVFoundation用于播放、处理音视频。可以通过结构图看到AVFoundation位于UIKit之下,很好理解AVFoundation并不提供用户界面,你可以自己自己构建用户界面来控制媒体的播放处理等功能。 但是苹果更推荐使用AVKit来构建用户界面

2. AVKit

AVKit构建在 AVFoundation之上,可以简单的理解使用AVKit能够及其方便的使用系统为你提供的音视频播放界面。使用AVKit构建的播放界面能够随着苹果系统的更新自动更新。

如何选择如果你想让你的App拥有视频播放能力,并且不想自己创建控制界面,可以使用AVPlayerViewController(由AVKit提供)快速完成功能如果你想对视频进行编辑等处理,你需要使用 AVFoundation并且自己构建用户界面来处理音视频。iOS9之前,你可以使用MPMoviePlayerViewController做一个简单的视频播放器,但是在iOS9这个类已经被弃用了。 取而代之的是AVPlayerViewController

2. 创建一个简单的视频播放App

1.新建工程, 命名为AVBasicPlayback,在Info.plist中添加

<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>devimages-cdn.apple.com</key> <dict> <key>NSExceptionRequiresForwardSecrecy</key> <false/> </dict> </dict> </dict>

目的是确保App能够从devimages-cdn.apple.com加载视频资源

2.在Appdelete中添加如下代码

import AVFoundation func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 1.初始化session let audioSession = AVAudioSession.sharedInstance() do { //2.设置category确保app的音频能够正常播放 try audioSession.setCategory(.playback) } catch { print("Setting category to AVAudioSessionCategoryPlayback failed.") } return true }

3. 调整ViewController中的代码。

import UIKit import AVKit import AVFoundation class ViewController: UIViewController { let playButton = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() setupUI() } @objc func playVideo() { //1.这是一个HTTP Live Streaming流媒体链接,用于测试 guard let url = URL(string: "https://devimages-cdn.apple.com/samplecode/avfoundationMedia/AVFoundationQueuePlayer_HLS2/master.m3u8") else { return } //2. 创建AVPlayer let player = AVPlayer(url: url) //3. 创建AVPlayerViewController,并设置player let controller = AVPlayerViewController() controller.player = player //4. 显示 present(controller, animated: true) { player.play() } } func setupUI() { playButton.setTitle("Play Video", for: .normal) playButton.addTarget(self, action: #selector(playVideo), for: .touchUpInside) view.addSubview(playButton) playButton.translatesAutoresizingMaskIntoConstraints = false playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true playButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true } }

4. 运行App,点击播放。 恭喜你,已经成功完成了一个简单的视频播放App。

如果你视频加载不出来,检查一下第一步是否完成。另外还可以在网上寻找一些HLS测试的URL或者本地资源对代码中URL的进行替换

3. 音频设置

上一步我们在AppDelegate中使用到了AVAudioSession类

AVAudioSession是App和操作系统的音频中介。我们使用这个类进行一些音频设置,无需进行复杂的配置, 系统就能为用户提供良好的音频体验

AVAudioSession 有一些默认的设置:

默认不允许录音如果手机为静音模式,app播放的任何声音都会被静音当手机锁屏的时候,app的音频会被静音当你的App播放音频,其他背景音会被静音(比如你正在后台播放音乐)

如果想要调整默认设置,我们就需要配置AVAudioSession,在上一步中我们设置了.playback。

设置为.playback 能够使我们app具有后台音频播放的能力(需要配置),当你的App开始播放音频的时候系统会停止播放其他App的音频。想要了解更多可以仔细阅读下面这个链接

接下来我们设置允许后台音频播放

配置完成后App已经能够进行后台音频播放了。

深入了解AVFoundation

1. AVAsset

我们使用AVFoundation主要目的就是对音视频进行处理, AVAsset就是这些媒体数据的抽象模型。 我们可以用本地的视频,例如一个mp4格式的视频, 或则通过一个HLS流媒体连接来构建一个AVAsset。 使用AVAsset能够使得我们不用关心视频的格式,编解码这些复杂的工作。可以简单的理解AVAsset提供给我们一套统一的接口用于处理不同格式的视频。

AVAsset中包含了多个AVAssetTrack。 AVAssetTrack是媒体流的抽象,例如音轨、视频轨道、字幕轨道。

在大部分情况下,你只会对轨道的一部分进行处理,而不是处理整个轨道,所以AVAsset提供一些方法让你获取 AVAssetTrack的子集

2. 创建AVAsset

//URL可以是本地资源也可以是网络资源 let url: URL = "" let asset = AVAsset(url: url)

实际上AVAsset是一个抽象类,你实际创建的是一个AVURLAsset实例。 你也可以通过AVURLAsset直接创建实例, 你还可以进行一些设置,比如我们不想在使用蜂窝网络的时候加载视频。

let url: URL = // 网络URL资源 //不允许在蜂窝网络下加载 let options = [AVURLAssetAllowsCellularAccessKey: false] let asset = AVURLAsset(url: url, options: options)

新建 AVAsset 的时候系统不会自动加载数据,直到需要对其进行操作(播放,导出等)。在新建AVAsset后,不要直接读取它的属性,这可能会造成阻塞。 比如你想知道一个视频是否可以播放,需要调用loadValuesAsynchronously异步加载playable

func loadPlayable() { //1. 加载本地视频 let url = Bundle.main.url(forResource: "sample-mp4-file", withExtension: "mp4")! let asset = AVAsset(url: url) let playableKey = "playable" //2 判断属性是否可用 let status = asset.statusOfValue(forKey: playableKey, error: nil) if status != .loaded { print("playable unloaded") } //千万不要直接读取,这样可能会造成线程堵塞 //asset.isPlayable //3. 加载"playable"属性 asset.loadValuesAsynchronously(forKeys: [playableKey]) { var error: NSError? = nil let status = asset.statusOfValue(forKey: playableKey, error: &error) switch status { case .loaded: print("loaded, playable:\(asset.isPlayable)") case .failed: print("failed") case .cancelled: print("cancelled") default:break } } }

3. 处理元数据

AVAsset是媒体对象的抽象类 , AVMetadataItem是元数据的抽象类,提供了媒体文件关联的一些元数据,例如电影的标题或专辑的插图。你可以通过AVMetadataItem获取这些信息

AVFoundation把这些元数据分别关联到Format-specific key spaces 和Common key space. 你可以获取并处理这些数据。

func loadMetaData() { //1. 加载本地视频 let url = Bundle.main.url(forResource: "sample-mp4-file", withExtension: "mp4")! let asset = AVAsset(url: url) let formatsKey = "availableMetadataFormats" let commonMetadataKey = "commonMetadata" //2. 加载属性 asset.loadValuesAsynchronously(forKeys: [formatsKey, commonMetadataKey]) { var error: NSError? = nil //3 获取Format-specific key spaces下的元数据 let formatsStatus = asset.statusOfValue(forKey: formatsKey, error: &error) if formatsStatus == .loaded { for format in asset.availableMetadataFormats { let metaData = asset.metadata(forFormat: format) print(metaData) } } //4 获取Common key space 下的元数据 let commonStatus = asset.statusOfValue(forKey: commonMetadataKey, error: &error) if commonStatus == .loaded { let metadata = asset.commonMetadata print(metadata) //5 通过Identifier获取AVMetadataItem let titleID: AVMetadataIdentifier = .commonIdentifierTitle let titleItems = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: titleID) if let item = titleItems.first { //6 处理title item print(item) } } } }

4. 视频播放

前面我们对AVAsset已经有了一个了解,它代表了一个媒体资源。如果你想播放视频,你还需要用到其他对象。

1. AVPlayer

AVPlayer 媒体播放的核心类,用它对媒体对象进行管理。

AVPlayer一次只能播放一个视频,你可以使用AVQueuePlayer (AVPlayer的子类)来创建播放队列,播放多个视频

2.AVPlayerItem

AVAsset只是对媒体对象的静态建模。当你播放它们的时候, 媒体对象会有播放时间等状态,所以你需要使用AVPlayerItem 对这些数据进行建模。 你可以使用AVPlayerItem控制播放时间等。

3.AVKit 和 AVPlayerLayer

AVPlayer和AVPlayerItem不负责视频的显示。

你可以使用AVKit下的AVPlayerController或者AVPlayerLayer来显示显示视频。 AVPlayerController 自带播放界面, AVPlayerLayer需要你自己创建控件控制视频的播放。

func playFlow() { // 1 获取URL let url = Bundle.main.url(forResource: "sample-mp4-file", withExtension: "mp4")! // 2 创建AVAsset对象 let asset = AVAsset(url: url) // 3 创建AVPlayerItem对象 let playerItem = AVPlayerItem(asset: asset) // 4 创建AVPlayer let player = AVPlayer(playerItem: playerItem) // 5 关联 let controller = AVPlayerViewController() controller.player = player }

5. 监听播放状态

AVPlayer 以及AVPlayerItem 的属性状态经常会发生变化。我们使用KVO来进行监听并进行业务处理

其中AVPlayerItem 的status是一个非常重要的属性,我们可以通过这个属性判断视频是否可以进行播放

let url: URL = Bundle.main.url(forResource: "sample-mp4-file", withExtension: "mp4")! var asset: AVAsset! var player: AVPlayer! var playerItem: AVPlayerItem! // Key-value observing context private var playerItemContext = 0 // 需要加载的属性 let requiredAssetKeys = [ "playable", "hasProtectedContent" ] func prepareToPlay() { // 1.创建AVAsset asset = AVAsset(url: url) // 2.创建AVPlayerItem,并且在readyToPlay状态之前加载所有需要的属性 playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: requiredAssetKeys) // 3.KVO playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: &playerItemContext) // 4.创建AVPlayer player = AVPlayer(playerItem: playerItem) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { // 只对playerItemContext进行处理 guard context == &playerItemContext else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } if keyPath == #keyPath(AVPlayerItem.status) { let status: AVPlayerItem.Status if let statusNumber = change?[.newKey] as? NSNumber { status = AVPlayerItem.Status(rawValue: statusNumber.intValue)! } else { status = .unknown } // Switch over status value switch status { case .readyToPlay: print("加载完成") case .failed: print("加载失败") case .unknown: print("未知状态") default:break } } }

5. 基于时间对视频进行操作

首先我们了解一下CMTime

public struct CMTime { public var value: CMTimeValue public var timescale: CMTimeScale public var flags: CMTimeFlags public var epoch: CMTimeEpoch }

其中比较重要的是value和timescale 。当我们构建一个CMTime的时候,我们需要知道视频的帧率。 计算CMTime的时候,我们只要把 value作为分子和timescale作为分母,就能计算出时长。

value/timescale = seconds
// 60/60 = 1秒 let oneSecond = CMTime(value: 60, timescale: 60) // 1/4 = 0.25秒 let quarterSecond = CMTime(value: 1, timescale: 4) // 441000/44100 = 10秒 let tenSeconds = CMTime(value: 441000, timescale: 44100) // 90/30 = 3秒 let cursor = CMTime(value: 90, timescale: 30)

如果你想对视频的播放时间进监听,首先肯定想到的是KVO,但KVO不不适合用来对时间进行监听,因为时间是连续变化的(想象一下KVO一直回调)。

官方推荐了2种方式对时间进行监听

周期监测比如你想根据视频播放的时间刷新界面上的时间显示
var timeObserverToken: Any? func addPeriodicTimeObserver() { // 每半秒回调一次 let timeScale = CMTimeScale(NSEC_PER_SEC) let time = CMTime(seconds: 0.5, preferredTimescale: timeScale) timeObserverToken = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] time in guard let self = self else { return } //在这里进行你的业务逻辑 print("\(self.player.currentTime())") } } func removePeriodicTimeObserver() { if let timeObserverToken = timeObserverToken { player.removeTimeObserver(timeObserverToken) self.timeObserverToken = nil } }

2. 边界监测

比如你想在某个时间节点对视频进行处理
func addBoundaryTimeObserver() { // 视频每播放1/4我们进行一次回调 let interval = CMTimeMultiplyByFloat64(asset.duration, multiplier: 0.25) var currentTime = CMTime.zero var times = [NSValue]() // 添加时间节点 while currentTime < asset.duration { currentTime = currentTime + interval times.append(NSValue(time:currentTime)) } timeObserverToken = player.addBoundaryTimeObserver(forTimes: times, queue: .main) { //在这里进行你的业务逻辑 print(self.player.currentTime()) } } func removeBoundaryTimeObserver() { if let timeObserverToken = timeObserverToken { player.removeTimeObserver(timeObserverToken) self.timeObserverToken = nil } }

调整视频播放时间

如果你想快速调整视频到某个时间点
func seekToTime() { // 2分钟 let time = CMTime(value: 120, timescale: 1) player.seek(to: time) }

如果你想非常精确的调整视频到某个时间节点,使用seekToTime:toleranceBefore:toleranceAfter: 方法。

func seekToTimeAccuracy() { // 10秒的第一帧。 这里不用觉得CMTime计算的时间不对,视频的帧率由视频本身决定,preferredTimescale设置一个极大的值就可以了 let seekTime = CMTime(seconds: 10, preferredTimescale: Int32(NSEC_PER_SEC)) // 设置tolerance 为CMTime.zero 不允许有误差 player.seek(to: seekTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) }

第2,3篇

Github项目链接

分享到:

  admin

注册时间:

网站:0 个   小程序:3 个  文章:0 篇

  • 464

    网站

  • 9

    小程序

  • 3613

    文章

  • 118

    会员

赶快注册账号,推广您的网站吧!
热门网站
最新入驻小程序

跳一跳2022-08-22

跳一跳是微信开发的一款小游戏,有

数独大挑战2018-06-03

数独一种数学游戏,玩家需要根据9

答题星2018-06-03

您可以通过答题星轻松地创建试卷

全阶人生考试2018-06-03

各种考试题,题库,初中,高中,大学四六

运动步数有氧达人2018-06-03

记录运动步数,积累氧气值。还可偷

每日养生app2018-06-03

每日养生,天天健康