iOS音视频播放指南(一)

article/2025/10/2 12:41:33

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中添加如下代码

importAVFoundation

funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{

// 1.初始化session

letaudioSession=AVAudioSession.sharedInstance()

do{

//2.设置category确保app的音频能够正常播放

tryaudioSession.setCategory(.playback)

}catch {

print("Setting category to AVAudioSessionCategoryPlayback failed.")

}

returntrue

}

\3. 调整ViewController中的代码。

importUIKit

importAVKit

importAVFoundation

classViewController:UIViewController{

letplayButton=UIButton(type:.system)

overridefuncviewDidLoad(){

super.viewDidLoad()

setupUI()

}

@objcfuncplayVideo(){

//1.这是一个HTTP Live Streaming流媒体链接,用于测试

guardleturl=URL(string:"https://devimages-cdn.apple.com/samplecode/avfoundationMedia/AVFoundationQueuePlayer_HLS2/master.m3u8")else{

return

}

//2. 创建AVPlayer

letplayer=AVPlayer(url:url)

//3. 创建AVPlayerViewController,并设置player

letcontroller=AVPlayerViewController()

controller.player=player

//4. 显示

present(controller,animated:true){

player.play()

}

}

funcsetupUI(){

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 有一些默认的设置:

  1. 默认不允许录音

  1. 如果手机为静音模式,app播放的任何声音都会被静音

  1. 当手机锁屏的时候,app的音频会被静音

  1. 当你的App播放音频,其他背景音会被静音(比如你正在后台播放音乐)

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

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

Introductiondeveloper.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007875

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

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


4.深入了解AVFoundation


4.1AVAsset

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

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

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

4.2创建AVAsset

//URL可以是本地资源也可以是网络资源

leturl:URL=""

letasset=AVAsset(url:url)

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

leturl:URL=// 网络URL资源

//不允许在蜂窝网络下加载

letoptions=[AVURLAssetAllowsCellularAccessKey:false]

letasset=AVURLAsset(url:url,options:options)

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

funcloadPlayable(){

//1. 加载本地视频

leturl=Bundle.main.url(forResource:"sample-mp4-file",withExtension:"mp4")!

letasset=AVAsset(url:url)

letplayableKey="playable"

//2 判断属性是否可用

letstatus=asset.statusOfValue(forKey:playableKey,error:nil)

ifstatus!=.loaded{

print("playable unloaded")

}

//千万不要直接读取,这样可能会造成线程堵塞

//asset.isPlayable

//3. 加载"playable"属性

asset.loadValuesAsynchronously(forKeys:[playableKey]){

varerror:NSError?=nil

letstatus=asset.statusOfValue(forKey:playableKey,error:&error)

switchstatus{

case.loaded:

print("loaded, playable:\(asset.isPlayable)")

case.failed:

print("failed")

case.cancelled:

print("cancelled")

default:break

}

}

}

4.3处理元数据

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

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

funcloadMetaData(){

//1. 加载本地视频

leturl=Bundle.main.url(forResource:"sample-mp4-file",withExtension:"mp4")!

letasset=AVAsset(url:url)

letformatsKey="availableMetadataFormats"

letcommonMetadataKey="commonMetadata"

//2. 加载属性

asset.loadValuesAsynchronously(forKeys:[formatsKey,commonMetadataKey]){

varerror:NSError?=nil

//3 获取Format-specific key spaces下的元数据

letformatsStatus=asset.statusOfValue(forKey:formatsKey,error:&error)

ifformatsStatus==.loaded{

forformatinasset.availableMetadataFormats{

letmetaData=asset.metadata(forFormat:format)

print(metaData)

}

}

//4 获取Common key space 下的元数据

letcommonStatus=asset.statusOfValue(forKey:commonMetadataKey,error:&error)

ifcommonStatus==.loaded{

letmetadata=asset.commonMetadata

print(metadata)

//5 通过Identifier获取AVMetadataItem

lettitleID:AVMetadataIdentifier=.commonIdentifierTitle

lettitleItems=AVMetadataItem.metadataItems(from:metadata,filteredByIdentifier:titleID)

ifletitem=titleItems.first{

//6 处理title item

print(item)

}

}

}

}

4.4视频播放

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

\1. AVPlayer

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

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

2.AVPlayerItem

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

3.AVKit 和 AVPlayerLayer

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

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

funcplayFlow() {

// 1 获取URL

leturl=Bundle.main.url(forResource:"sample-mp4-file",withExtension:"mp4")!

// 2 创建AVAsset对象

letasset=AVAsset(url:url)

// 3 创建AVPlayerItem对象

letplayerItem=AVPlayerItem(asset:asset)

// 4 创建AVPlayer

letplayer=AVPlayer(playerItem:playerItem)

// 5 关联

letcontroller=AVPlayerViewController()

controller.player=player

}

4.5监听播放状态

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

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

leturl:URL=Bundle.main.url(forResource:"sample-mp4-file",withExtension:"mp4")!

varasset:AVAsset!

varplayer:AVPlayer!

varplayerItem:AVPlayerItem!

// Key-value observing context

privatevarplayerItemContext=0

// 需要加载的属性

letrequiredAssetKeys=[

"playable",

"hasProtectedContent"

]

funcprepareToPlay(){

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

}

overridefuncobserveValue(forKeyPathkeyPath:String?,

ofobject:Any?,

change:[NSKeyValueChangeKey:Any]?,

context:UnsafeMutableRawPointer?){

// 只对playerItemContext进行处理

guardcontext==&playerItemContextelse{

super.observeValue(forKeyPath:keyPath,

of:object,

change:change,

context:context)

return

}

ifkeyPath==#keyPath(AVPlayerItem.status){

letstatus:AVPlayerItem.Status

ifletstatusNumber=change?[.newKey]as?NSNumber{

status=AVPlayerItem.Status(rawValue:statusNumber.intValue)!

}else{

status=.unknown

}

// Switch over status value

switchstatus{

case.readyToPlay:

print("加载完成")

case.failed:

print("加载失败")

case.unknown:

print("未知状态")

default:break

}

}

}

4.5基于时间对视频进行操作

首先我们了解一下CMTime

publicstructCMTime{

publicvarvalue:CMTimeValue

publicvartimescale:CMTimeScale

publicvarflags:CMTimeFlags

publicvarepoch:CMTimeEpoch

}

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

value/timescale = seconds

// 60/60 = 1秒

letoneSecond=CMTime(value:60,timescale:60)

// 1/4 = 0.25秒

letquarterSecond=CMTime(value:1,timescale:4)

// 441000/44100 = 10秒

lettenSeconds=CMTime(value:441000,timescale:44100)

// 90/30 = 3秒

letcursor=CMTime(value:90,timescale:30)

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

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

  1. 周期监测

比如你想根据视频播放的时间刷新界面上的时间显示

vartimeObserverToken:Any?

funcaddPeriodicTimeObserver(){

// 每半秒回调一次

lettimeScale=CMTimeScale(NSEC_PER_SEC)

lettime=CMTime(seconds:0.5,preferredTimescale:timeScale)

timeObserverToken=player.addPeriodicTimeObserver(forInterval:time,

queue:.main){

[weakself]timein

guardletself=selfelse{return}

//在这里进行你的业务逻辑

print("\(self.player.currentTime())")

}

}

funcremovePeriodicTimeObserver(){

iflettimeObserverToken=timeObserverToken{

player.removeTimeObserver(timeObserverToken)

self.timeObserverToken=nil

}

}

\2. 边界监测

比如你想在某个时间节点对视频进行处理

funcaddBoundaryTimeObserver(){

// 视频每播放1/4我们进行一次回调

letinterval=CMTimeMultiplyByFloat64(asset.duration,multiplier:0.25)

varcurrentTime=CMTime.zero

vartimes=[NSValue]()

// 添加时间节点

whilecurrentTime<asset.duration{

currentTime=currentTime+interval

times.append(NSValue(time:currentTime))

}

timeObserverToken=player.addBoundaryTimeObserver(forTimes:times,

queue:.main){

//在这里进行你的业务逻辑

print(self.player.currentTime())

}

}

funcremoveBoundaryTimeObserver(){

iflettimeObserverToken=timeObserverToken{

player.removeTimeObserver(timeObserverToken)

self.timeObserverToken=nil

}

}

调整视频播放时间

如果你想快速调整视频到某个时间点

funcseekToTime(){

// 2分钟

lettime=CMTime(value:120,timescale:1)

player.seek(to:time)

}

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

funcseekToTimeAccuracy(){

// 10秒的第一帧。 这里不用觉得CMTime计算的时间不对,视频的帧率由视频本身决定,preferredTimescale设置一个极大的值就可以了

letseekTime=CMTime(seconds:10,preferredTimescale:Int32(NSEC_PER_SEC))

// 设置tolerance 为CMTime.zero 不允许有误差

player.seek(to:seekTime,toleranceBefore:CMTime.zero,toleranceAfter:CMTime.zero)

}

原文https://zhuanlan.zhihu.com/p/335087516

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓


http://chatgpt.dhexx.cn/article/Ev7UtoJl.shtml

相关文章

iOS视频播放的基本方法

本文总结了iOS中最常见的视频播放方法&#xff0c;不同的方法都各具特点&#xff0c;我希望能够总结它们的不同&#xff0c;方便在开发中选择合适的技术方案。 Apple为我们提供了多种方法来实现视频播放&#xff0c;包括MPMoviePlayerController&#xff0c;MPMoviePlayerView…

【计算机系统1】4 Nim游戏

目录 目的与要求 内容与方法 步骤与过程 程序总体设计 核心数据结构及算法流程 核心代码 调试过程 界面展示子程序DISPLAY&#xff08;嵌套&#xff1a;球数展示子程序PUTBALL&#xff09; 游戏子程序GAME&#xff08;嵌套&#xff1a;单人每轮子程序PLAY&#xff09; 结论或体…

java nim游戏_LeetCode 292. Nim游戏

题目描述&#xff1a; 你和你的朋友&#xff0c;两个人一起玩 Nim游戏&#xff1a;桌子上有一堆石头&#xff0c;每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。 你们是聪明人&#xff0c;每一步都是最优解。 编写一个函数&#xff0c;来判断你…

Nim游戏、3的幂、4的幂

&#x1f345; Java学习路线&#xff1a;Java学习路线 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、CSDN哪吒公众号作者✌ 、Java架构师奋斗者&#x1f4aa; &#x1f345; 百日刷题计划&#xff1a;第 12 / 100 天。 &#x1f345; 扫描主页左侧二维码&a…

【数论】博弈论 —— nim游戏

知识点 一 . nim游戏的数学定义 Nim游戏是博弈论中最经典的模型&#xff0c;它又有着十分简单的规则和无比优美的结论 。 Nim游戏是组合游戏(Combinatorial Games)的一种&#xff0c;准确来说&#xff0c;属于“Impartial Combinatorial Games”&#xff08;以下简称ICG&#…

【模板题】几种常见的Nim游戏(博弈论)

一、AcWing 891. Nim游戏 【题目描述】 给定 n n n堆石子&#xff0c;两位玩家轮流操作&#xff0c;每次操作可以从任意一堆石子中拿走任意数量的石子&#xff08;可以拿完&#xff0c;但不能不拿&#xff09;&#xff0c;最后无法进行操作的人视为失败。 问如果两人都采用最优…

Kafka 为什么能那么快 | Kafka高效读写数据的原因

点击上方“服务端思维”&#xff0c;选择“设为星标” 回复”669“获取独家整理的精选资料集 回复”加群“加入全国服务端高端社群「后端圈」 无论 kafka 作为 MQ 也好&#xff0c;作为存储层也罢&#xff0c;无非就是两个功能&#xff08;好简单的样子&#xff09;&#xff0c…

【人人都懂密码学】一篇最易懂的Java密码学入门教程

密码与我们的生活息息相关&#xff0c;远到国家机密&#xff0c;近到个人账户&#xff0c;我们每天都在跟密码打交道&#xff1a; 那么&#xff0c;密码从何而来&#xff1f;生活中常见的加密是怎么实现的&#xff1f;怎么保证个人信息安全&#xff1f;本文将从这几方面进行浅谈…

Kafka必须掌握的核心技术--为什么吞吐量大、速度快?

点击上方“服务端思维”&#xff0c;选择“设为星标” 回复”669“获取独家整理的精选资料集 回复”加群“加入全国服务端高端社群「后端圈」 Kafka是大数据领域无处不在的消息中间件&#xff0c;目前广泛使用在企业内部的实时数据管道&#xff0c;并帮助企业构建自己的流计算应…

哪些软件问题也可导致硬盘录像机死机

硬盘录像机死机除了一些硬件上的问题之外&#xff0c;也有不少是由软件引起的。如&#xff1a; 1、病毒感染 病毒是计算机操作的大患&#xff0c;几乎人人恶之。病毒可以使计算机工作效率急剧下降&#xff0c;造成频繁死机、数据丢失、系统崩溃&#xff0c;甚至损坏主板、硬盘、…

导致硬盘录像机卡死的十大原因分析

硬盘录像机卡死除了一些技术上的问题之外&#xff0c;也有不少是由软件引起的。如&#xff1a; 1、病毒感染 病毒是硬盘录像机操作的大患&#xff0c;几乎人人恶之。病毒可以使硬盘录像机工作效率急剧下降&#xff0c;造成频繁死机、数据丢失、系统崩溃&#xff0c;甚至损坏主板…

谈项目管理和软件测试过程

谈项目管理和软件测试过程&#xff08;一&#xff09; 1. 软件测试在公司的组织保障是基础 1.1 研发部组织结构介绍 以华友公司研发部的组织结构为例&#xff0c;测试部门属于研发部副总裁直接管理&#xff0c;见如下结构图 公司研发部的组织结构图 …

NoSQL初探之人人都爱Redis:(1)Redis简介与简单安装

一、NoSQL的风生水起 1.1 后Web2.0时代的发展要求 随着互联网Web2.0网站的兴起&#xff0c;传统的关系数据库在应付Web2.0网站&#xff0c;特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心&#xff0c;暴露了很多难以克服的问题&#xff1a; &#xff08;1…

软件公司面试总结

文章目录 面试1面试2面试3面试4面试5面试6 面试1 1、你先做个简单的自我介绍吧 我叫张三&#xff0c;2015年在重庆邮电大学毕业。我读的专业是电子信息。工作已经快5年了。 我上家公司的主营业务是在柬埔寨做移动支付钱包。 最近做的一个项目是聚合支付的项目&#xff0c;主要…

腾讯的硬盘里,有互联网的昨天今天和明天

作者&#xff1a;史中 来源&#xff1a;浅黑科技&#xff08;qianheikeji&#xff09; 2018年1月1日&#xff0c;太阳照常升起。 世界上所有的时钟合谋&#xff0c;把最后一个90后推过了18岁的门槛。对于这些年轻的面孔来说&#xff0c;自由的风终于如期而至&#xff0c;只是其…

机械硬盘与SSD固态硬盘性能的深度

从7200转硬盘升级到10000转的迅猛龙&#xff0c;那叫量变。从10000转的迅猛龙升级到SSD&#xff0c;这个叫质变。2者的差距是有些地方相当大&#xff0c;而有些却很接近&#xff0c;主要是难比较。经常听到有人说&#xff1a;我买2个黑盘组RAID 0&#xff0c;传输率也有接近250…

sudo rm-rf引发的惨案——Linux硬盘的分区和挂载

前言 前不久&#xff0c;刚使用组里的一台服务器&#xff0c;这台服务器平时用的人不多&#xff0c; 没有严格的管理机制&#xff0c;大家都使用同一个用户名进行远程连接&#xff0c;人人都有sudo权限。我因为对Linux不是非常熟悉&#xff0c;使用管理员权限下执行了一个删除…

DVR-硬盘录像机

硬盘录像机&#xff08;Digital Video Recorder&#xff0c;简称DVR&#xff09;&#xff0c;即数字视频录像机&#xff0c;相对于传统的模拟视频录像机&#xff0c;采用硬盘录像&#xff0c;故常常被称为硬盘录像机&#xff0c;也被称为DVR。 它是一套进行图像存储处理的计算…

[转]80后研制出世界最快硬盘:传输速度每秒1.5GB

传输速度每秒1.5GB&#xff0c;仅需3秒就能传输一张DVD光盘的数据&#xff0c;是普通硬盘速度的15倍。一秒钟可以访问31万次&#xff0c;而普通硬盘仅可以访问16次。这些数据&#xff0c;描绘着一款固态硬盘的卓越性能。研发出这款世界传输速度最快、性能最好的固态硬盘的&…

我们测了30款移动硬盘,却如此尴尬

我最近在研究移动硬盘&#xff0c;如果你也感兴趣不妨来看看我这些天的研究成果吧。不卖关子&#xff0c;我们发现不同品牌SDD移动硬盘间存在着很大差异&#xff0c;如果你非常在意读写速度就需要慎重选择了&#xff1b;而不同品牌HDD移动硬盘间在速度上并不存在巨大差异&#…