AVFoundation 是可以用来播放和创建基于时间的视听媒体的几个framework之一。它提供了OC接口来处理基于时间的视听数据。比如,你可以使用AVFoundation来测试,创建,编辑或者重新编发媒体文件,你也可以从设备拿到输入流,并在实时捕捉和回放过程中操作视频。

下入显示了 iOS AVFoundation的架构:
下图显示了 OS X的媒体架构组成:
一般使用高层可用的抽象来执行特定的任务。
  • 如果想简单的播放movies,使用AVKit
  • iOS上,录制视频,当只需要对格式进行最小限度的控制时,可以使用UIKit
注意:仍然,在AVFoundation中有某些古老的数据结构(包括时间相关数据结构、携带和描述媒体数据的不透明对象)在 Core Media中声明。
简介 At a Glance
AVFoundation包含两个方面:和视频相关的api;音频相关api。更早的音频相关类提供处理音频的简单方式。
  • 使用AVAudioPlayer播放音频
  • 使用AVAudioRecorder录制音频
使用 AVAudioSession配置音频行为,在 Audio Session Programming Guide 提及。
AVFoundation 呈现和使用媒体
AVAsset时AVFoundation 用来呈现媒体的主要类。这个框架的设计主要是由这个需求来指导的。理解AVAsset的结构将会帮助你理解AVFoundation framework的原理。一个AVasset的实例是一片或者多片媒体数据组成的集合的一种聚合表达。Avasset提供关于整个集合的信息,比如 title,duration,natural presentation size等。AVasset  不纠结于特定数据格式。AVAsset 是那些用于从URL的媒体创建资产实例和创建新合成的superclass。
asset中每片独立的媒体数据有一个统一的类型,叫做 track。比如,一个 track 表达了音频部分,另一个track表达视频部分;在一个复杂的组合中可能有互相覆盖重合的视频和音频track。Assets可能还含有metadata。
AVFoundation 的一个重要的概念是——初始化一个asset或者一个track不代表它可以使用了。它可能需要一些时间来计算一个项目的duration(比如MP3,可能不含 summary 信息)。你请求一个值,通过callback(比如block)异步得到回应,而不是在值计算的时候阻塞当前线程。

播放 Playback

AVFoundation允许你精确的管理asset的播放。为了支持这个,AVFoundation 从asset自身分离了一个asset的presentation state。比如,这可以让你同时播放同一个asset的两个不同段并且绘制到两个不同的分辨率。asset的presentation state通过一个 player item 对象管理;一个 asset内的每个 track 的presentation state通过 player item track 对象管理。你可以使用 player item和player item tracks来设置被player呈现的item的可视部分的大小;设置音频混合参数和视频组合设置,以便在回放期间应用;或者在播放中禁用asset的某个部分。
你可以使用 一个 player object播放 player items,把player的output指向CoreAnimation layer。你可以使用 一个 player queue来序列化的安排一组player items的播放。
如下图:

Asset的读取,写入和重新编码

AVFoundation允许你用不同的方式创建新的asset的表达。你可以从新编码一个已知的asset,或者,在iOS 4.1以上中,你能操作asset的内容,存储结果为一个新asset。
你必须使用一个export session 来重新编码一个已知asset,编码为由某些定义好的常用的预先设置的格式。如果你需要在转换中进行更多控制,你可以使用 asset reader 和asset writer 对象协同工作,把asset从一种呈现转为另一种。使用这些对象,你可以选择你要的tracks来表达到输入文件,指定你自己的输出格式,或者在转换过程中修改asset。

缩略图 Thumbnails

为了创建视频表达的缩略图,你需要初始化一个 AVAssetImageGenerator。AVAssetImageGenerator 使用默认可用的视频tracks来生成图片。

编辑

AVFoundation 使用 compositions 来从已有media 片中创建新asset(在一个或多个视频和音频tracks中)。你使用一个 mutable composition 来添加和移除一个tracks、调整他们的时序。你也可以设置相应的音量和音频轨道;设置不透明度和不透明度的视频轨道。一个 composition 是一个 内存中 media 片的集合。当你导出一个 composition 时,使用 export session让他折叠为一个文件。
还可以从media创建asset,比如从sample buffers或still images中用一个asset writer 来创建 asset。

定格和视频媒体捕获 Still and Video Media Capture

capture session负责从camera和麦克风记录输入。一个capture session协调从输入设备到输出的数据流,例如输出到一个电影文件。你可以为一个session配置多个输入和输出,甚至在session正在运行时都可以配置。你给session发送消息来启动和停止数据流。
另外,你可以使用一个 preview layer的实例来给用户显示camera正在录制的内容。

AVFoundation的并发编程

Block调用,kvo,消息处理(invocations of blocks, key-value observers, and notification handlers)这些从AVFoundation 来的callback,不能保证被放入任何特定的线程或者队列。相反,AVFoundation在执行其内部任务的线程或队列上调用这些处理程序。
就通知和线程而言,有两种一般的指导原则:
  • UI相关的通知在主线程触发
  • 需要创建或者指定一个队列queue的类或者方法将会在这个队列上返回通知。
除了这两个准则之外(还有一些例外,它们在参考文档中注明), 你不应该假定通知将在任何特定的线程上返回。
如果你正在编写一个多线程app,你可以使用 NSThread 方法 isMainThread 或者 [[NSThread currentThread] isEqual:<#A stored thread reference#>] 来测试调用线程是否时你期待执行任务的那个。你可以用performSelectorOnMainThread:withObject:waitUntilDone: and performSelector:onThread:withObject:waitUntilDone:modes: 方法重定向消息给合适的线程。也可以使用 dispatch_async 在合适的queue上跳到你的block,要么是UI任务的主队列,要么是用于并发操作的队列。 AVCam-iOS: Using AVFoundation to Capture Images and Movies sample code是所有AVFoundation功能的主要示例,并且可以参考AVFoundation的线程和队列使用示例。
有几个AVFoundation示例包括两个对于理解和实现摄像头捕捉功能至关重要的示例:
    AVCam-iOS: Using AVFoundation to Capture Images and Movies 是用于实现任何使用相机功能的程序的规范示例代码。它是一个完整的样本,有据可查,涵盖了大多数最佳实践。
   AVCamManual: Extending AVCam to Use Manual Capture API  是AVCam的演示app。它使用手动相机控件实现相机功能。它是一个完整的例子,有据可查,应被视为示范。
    RosyWriter 是一个演示实时帧处理的例子,特别是如何将过滤器应用于视频内容。这是一个非常常见的开发人员需求,这个例子涵盖了这个功能。
    AVLocationPlayer: Using AVFoundation Metadata Reading APIs 演示使用元数据API。
使用 Assets
Assets可以来自一个文件或者来自用户iPod library或者photo library里的媒体。当你创建一个 asset object时,所有你想要为这个asset item处理的信息不回立即可用。一旦你有了一个movie asset,你可以从里面解压 定格图片,或者转码为其他格式,或者裁切电影的内容。
创建一个 asset 对象
通过使用url来创建一个asset来表达你可以标识的任何资源,创建的对象时 AVURLAsset。从文件创建如下:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];

初始化Asset的选项

AVURLAsset初始化方法的第二个参数是一个 options 字典。字典中仅有的key是AVURLAssetPreferPreciseDurationAndTimingKey。对应的值是一个布尔值(包含在一个NSValue object中),它指示asset是否应该准备好表示一个精确的持续时间duration,并提供精确的随机访问时间。
得到asset的精确duration可能需要明显的处理消耗。所以,使用一个大略的duration经常是更好的操作而且对播放没有影响。因此:
  • 如果你仅仅想要播放asest,给字典传递 nil或者传递 key的值为NO(NSValue)都可以。
  • 如果你想把asset加入一个composition(AVMutableComposition),你需要精确的随机数据。所以需要给字典的key传入YES,如下:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @YES };
AVURLAsset *anAssetToUseInAComposition = [[AVURLAsset alloc] initWithURL:url options:options];

访问用户的Assets

使用 asset的url来从 iPod/Photo library中访问Asset。
  • 创建 MPMediaQuery 实例在iPod中方位library,然后使用 MPMediaItemPropertyAssetURL 拿到他的url
  • 访问 Photos App的asset,使用 ALAssetLibrary(现在应该是PHAssetLibrary)了。
由于已经推荐使用PHAsset,所以实例代码略。

准备使用 一个 Asset

上文说过,初始化一个 asset或者track 不代表你想要为item处理的信息都是立刻可用的。可能需要时间来计算item的精确 duration。所以为了不阻塞当前线程,你应该使用 AVAsynchronousKeyValueLoading 协议来请求值并且通过一个定义的block来获取回调。AVAsset 和 AVAssetTrack都遵循这个协议,
使用 statusOfValueForKey:error: 来测试属性值是否加载了。当asset第一次加载,大部分或则全部分属性值都是 AVKeyValueStatusUnknown。使用 loadValuesAsynchronouslyForKeys:completionHandler: 来加载属性值。在完成的处理回调中,你的任何动作都基于属性状态。你应该随时准备处理该状态不完整时的操作,比如网络原因造成的network-based url不能被访问,或者加载操作被取消了。
代码如下:
从视频中拿到定格图片
使用AVAssetImageGenerator 对象来拿到定格图片(比如缩略图)。通过你的Asset来初始化一个 image Generator。初始化可能是成功的,即使asset在初始化时没有任何可视的track,所以如果需要你应该用tracksWithMediaCharacteristic:方法测试一下asset有没有任何视觉特征的track。如下:
AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
    AVAssetImageGenerator *imageGenerator =
      [AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
    // Implementation continues…
}
你可以配置image generator的几个方面,比如,分别用maximumSize and apertureMode指定生成图的最大大小和aperture mode(光圈模式)。然后,你可以在给定的时间生成单个图像,或者生成一系列图像。在生成所有图像之前,必须确保对图像生成器有一个强引用。

生成一个单独的图片

使用copyCGImageAtTime:actualTime:error:在一个指定时间生成单张图片。
AVFoundation可能不会在指定时间生成图片,所以你需要给第二个参数传入一个CMTime指针,包含图片准确生成的时间。代码如下:

生成一个序列的图片

给image generator发送 generateCGImagesAsynchronouslyForTimes:completionHandler: 消息来生成一个序列的图片。第一个参数是一个NSValue的数组对象,每个对象包含一个CMTime结构,指定了你想要的image的生成时间。第二个参数是图片生成后的callback block,此参数提供一个结果常数告知你操作成功还是取消,视情况有以下几项:
  • 生成的image
  • 请求image的时间和image实际生成的时间
  • 生成失败的error 描述
在你的block实现中,检查这个常数来判断图片是否创建。另外确保有一个图片生成器的强引用,知道创建图片完成。代码如下:
AVAsset *myAsset = <#An asset#>];
// Assume: @property (strong) AVAssetImageGenerator *imageGenerator;
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:myAsset];
Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 600);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 600);
CMTime end = CMTimeMakeWithSeconds(durationSeconds, 600);
NSArray *times = @[NSValue valueWithCMTime:kCMTimeZero],
[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird],
[NSValue valueWithCMTime:end]];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error) {
NSString *requestedTimeString = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, requestedTime));
NSString *actualTimeString = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, actualTime));
NSLog(@”Requested: %@; actual %@”, requestedTimeString, actualTimeString);
if (result == AVAssetImageGeneratorSucceeded) {
// Do something interesting with the image.
}
if (result == AVAssetImageGeneratorFailed) {
NSLog(@”Failed with error: %@”, [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled) {
NSLog(@”Canceled”);
}
}];
通过发送 cancelAllCGImageGeneration 来取消图片序列的生成。

裁切和转码一个Movie

使用 AVAssetExportSession对象来转码一个 movie,或者裁切。工作流如下图,一个export session是一个controller对象管理着asset的异步导出。你使用目标asset来初始化session,export 预设集的名字表明了你要执行的导出选项。然后配置export session来指定输出URL和文件类型,其他设置比如metadata和output应该为网络优化等的设置是可选的。
使用 exportPresetsCompatibleWithAsset: 来检查给定的asset是否可以导出。如下:
AVAsset *anAsset = <#Get an asset#>;
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
    // Implementation continues.
}
通过提供一个output URL(必须为file url)来完成session的配置。AVAssetExportSession 可以从url的path扩展名推导出输出文件的类型。或者你直接使用outputFileType来设置。你也可以指定额外的属性,比如时间范围、输出文件长度的限制、导出的文件是否应该针对网络使用进行优化,以及一个视频组成。下面例子展示了 timeRange peoperty来裁切movie:
exportSession.outputURL = <#A file URL#>;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTime start = CMTimeMakeWithSeconds(1.0, 600);
CMTime duration = CMTimeMakeWithSeconds(3.0, 600);
CMTimeRange range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range;
调用 exportAsynchronouslyWithCompletionHandler:.  来创建新文件。当导出操作完成时 completion block会被调用。你的handler实现中,应该检查session的status值来决定导出是否成功。代码如下:
通过cancelExport 来取消导出 。
如果覆盖一个已存在的文件或者在app的沙盒外写入会导致失败,以下情况也会失败:
  • 来电话了
  • 你app在后太当时另一个app开始了播放
这些情况下,你应该通知用户失败,让用户来重新导出。
播放
使用AVPlayer对象来控制asset的播放。播放过程中使用AVPlayerItem实例管理asset的presentation state。使用AVPlayerItemTrack对象来管理独立的track的presentation state。使用AVPlayerLayer对象显示一个video。
播放 Assets
player就是一个用来管理asset播放的控制对象,比如启动和停止播放或者找到特定的时间。使用AVplayer播放单独asset。使用AVQueuePlayer(AVPlayer的子类)对象播放一队items。OS X上使用AVKit的AVplayerView类在view上播放内容。
player提供播放状态的信息,如果需要你可以用来更新用户界面。直接把player的输出指向一个特定的 Core Animation Layer(一个AVPlayerLayer或者AVSynchronizedLayer的实例)。
注意,多个Player Layer:你可以从单独的AVPlayer实例创建多个AVPlayerLayer,但是只有最近创建的layer会在屏幕显示视频内容。
你不直接给AVPlayer提供asset,尽管最后你想play asset。你提供的是AVPlayerItem。他管理了关联asset的presentation state。包含 player item tracks(AVPlayerItemTrack实例)来对asset的tracks响应。关系如下:
这种抽象意味着你可以同时使用不同Players来播放一个给定asset,但是每个player的绘制方式不同。下图显示一个可能性——两个不同player播放同一个asset,播放设置不同。使用 item tracks你可以在播放时禁用部分轨道(比如不播放声音)。
 你可以用已有asset初始化一个player item,也可以用URL初始化player item(AVPlayerItem为asset创建和配置)。 然而,与AVAsset一样,简单初始化一个player并不意味着它可以立即播放。  你可以观察(使用键值观察)item的status属性来确定是否以及何时准备播放。
处理不同类型的Asset
配置播放一个asset可能依赖于要播放的asset的类型。宽泛的说,有两种类型:基于文件的asset:你有的随机数据(本地文件,摄像头资源,或者媒体库);基于流的asset(HTTP Live Streaming)。
加载和播放文件asset,有以下步骤:
  • 创建AVURLAsset
  • 创建AVPlayerItem实例
  • 关联item到AVPlayer实例
  • 等待item的status  属性表明现在可以播放了(使用KVO观察,接收一个status变动通知)
Putting It All Together: Playing a Video File Using AVPlayerLayer 例子中实现。
创建和准备播放HTTP live steam。通过URL初始化一个 AVPlayerItem实例。(不能对http live strem中的媒体直接创建AVAsset实例),代码如下
NSURL *url = [NSURL URLWithString:@”<#Live stream URL#>];
// You may find a test stream at <http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8>.
self.playerItem = [AVPlayerItem playerItemWithURL:url];
[playerItem addObserver:self forKeyPath:@”status” options:0 context:&ItemStatusContext];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
当你关联了player item和 player时,播放已经准备好。此时,player item创建AVAsset和AVAssetTrack 实例,你可以用他们检查live stream的内容。
通过观察 player item的 duration属性来得到一个live stream的duration。这个值在item可以播放时会更新为正确的值。
注意:iOS 4.3以上才可以用duration属性。通用做法是使用status属性,值为AVPlayerItemStatusReadyToPlayduration可以用以下代码获得:
[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];
如果仅仅只想播放live stream,用以下简便方式使用URL创建Player:
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@”status” options:0 context:&PlayerStatusContext];
对assets和item上来说,初始化player不代表播放是准备好的。应该观察player的status属性,当变为AVPlayerStatusReadyToPlay 时才可以。也可以观察currentItem属性来访问通过steam创建的player item。
如果你不知道你有何种类型的URL,有以下几步:
1.尝试用URL初始化一个AVURLAsset,然后加载他的tracks 键。如果成功,就可以为asset创建player item
2.如果第一步失败,直接通过url创建 AVPlayerItem。观察player的status来决定何时可以播放
如果上面的都成功了,你最终会得到一个player item然后你可以将它与一个player关联。
播放一个item
使用 play 方法:
– (IBAction)play:sender {
[player play];
}
除了简单的播放之外,你还可以管理播放的各个方面,比如rate和playhead的位置。你也可以监控player的play state,此时同步用户界面到asset的presentation state。

改变 Playback Rate

通过rate属性:
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
值为1,代表 使用自然速率播放,0.0 代表暂停播放,可以使用 pause 方法。
支持倒放的item,可以给rate指定负值。通过 canPlayReverse 判断是否支持倒放。可以就可以支持0,-1的取值范围。如果 canPlayFastReverse  为真,那么可以去值小于 -1.

查找,重定位播放头 Seeking—Repositioning the Playhead

使用 seekToTime: 移动播放头倒特定的时间:
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn];
seekToTime:方法,被设计成性能大于精度的。如果你需要精确移动playhead,使用seekToTime:toleranceBefore:toleranceAfter:来替代,如下:
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
使用零误差可能需要框架对大量数据进行解码。以下情况,应该只使用零。例如,编写一个需要精确控制的复杂的媒体编辑的应用程序。
播放完成后,播放器 header被设置倒item的结束位置,在调用 play 方法会没有作用。从item注册一个AVPlayerItemDidPlayToEndTimeNotification 通知勒吧playhead定位回开始。在 通知的回调方法里,调用 seekToTime:方法,指定参数为kCMTimeZero。如下:
// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#The player item#>];
– (void)playerItemDidReachEnd:(NSNotification *)notification {
[player seekToTime:kCMTimeZero];
}
播放多个items
使用AVQueuePlayer 对象在队列中播放一组items。 AVQueuePlayer类时一个AVplayer的子类。以下代码初始化:
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
使用 play方法播放,和AVPlayer对象一样。queue一个接一个的播放,如果想要跳过下一个,可以发送 advanceToNextItem消息。
通过 insertItem:afterItem:, removeItem:, and removeAllItems. 来操作queue。使用 canInsertItem:afterItem:.来判断item可不可以被插入到queue。如果一个新item插入queue,第二个参数传入nil。代码如下:
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
监控播放
你可以监控player的presentation state,还有播放的player item的方方面面。这在状态变化不是由你直接控制时特别有用。比如:
  • 如果用户使用多任务切换到不同的app,一个 player的rate会降到0.0
  • 如果你正在播放远程媒体,player item的loadedTimeRanges and seekableTimeRanges属性会在更多数据可用时改变。这俩属性告诉你那部分player item的timeline是可用的。
  • 一个 player 的currentItem当 一个play而 item为http live stream 创建时改变。
  • 一个 player item的 tracks 属性可能在播放一个 http live stream时改变。如果stream为内容提供了不同的编码时,这种情况可能发生;如果 player 切换到不同编码,tracks会改变。
  • 一个player或者player item的status可能在播放 失败时改变。
使用KVO监控这些属性变化。
重要:你应该在主线程注册和移除对通知的注册。这回避免可能的在其他线程产生变动时接收部分通知。AVFoundation 在主线程调用observeValueForKeyPath:ofObject:change:context: ,即使变动操作在其他线程产生。

响应一个状态变化

当player或者player item的状态变化时,会发射一个KVO的变动通知。如果一个对象由于某种原因不能播放(比如,媒体服务重置),会相应改变状态为AVPlayerStatusFailed or AVPlayerItemStatusFailed。这是,对象的error属性会生成不能播放的原因。
AVFoundation 不指定通知的发送线程。更新UI时,必须确定代码在主线程调用。下面代码用 di
spath_async来举例:

追踪视觉显示的已准备好的时机

当layer有用户可见的内容是,观察AVPlayerLAyer的 readyForDisplay属性来获得通知。某些特殊情况,只有当用户需要查看并执行转换时,才可以将AVPlayerLAyer插入到layer tree中。

追踪时间

使用 addPeriodicTimeObserverForInterval:queue:usingBlock: or addBoundaryTimeObserverForTimes:queue:usingBlock:. 方法在AVPlayer对象中追踪playhead的位置变化。这个操作可以用时间流逝信合和时间剩余信息更新UI,或者执行UI同步。
addPeriodicTimeObserverForInterval:queue:usingBlock:,您所提供的block在您指定的时间间隔内被调用,还有时间跳跃,以及回放开始或停止时调用。
 addBoundaryTimeObserverForTimes:queue:usingBlock:,传入一个NSValue的CTTime数组。当数组内时间发生时调用。
两个方法都返回一个不透明类型的观察者对象。你必须在player调用block时一直强引用这个对象。你也需要注意让这个方法的调用对象执行 removeTimObserver方法。
AVFoundation 不保证这俩方法会在每个时间间隔或者时间范围内调用你的block。如果前一个block没有完成,AVFoundation 不回调用另一个。因此你必须确定,你在block中所做的工作不会对系统造成太大的负担。
// Assume a property: @property (strong) id playerObserver;
Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = @[[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird]];
self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
    NSString *timeDescription = (NSString *)
      CFBridgingRelease(CMTimeCopyDescription(NULL, [self.player       currentTime]));
    NSLog(@”Passed a boundary at %@”, timeDescription);
}];

到达Item的结束位置

当layer item完成播放时,会得到一个AVPlayerItemDidPlayToEndTimeNotification通知。
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>]
下面是一个完整实例:Playing a Video File Using AVPlayerLayer
示例将会展示一下几点:
  • 配置视图以在AVPlayerLayer图层上使用
  • 创建AVPlayer对象
  • 为基于文件的asset创建一个AVPlayerItem对象,并使用KVO来观察其状态
  • 通过启用按钮来响应该item准备播放
  • 播放该项目,然后将播放器的头部恢复到开头
以下为实例代码,不在翻译。

The Player View

To play the visual component of an asset, you need a view containing an AVPlayerLayer layer to which the output of an AVPlayer object can be directed. You can create a simple subclass of UIView to accommodate this:

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface PlayerView : UIView
@property (nonatomic) AVPlayer *player;
@end
@implementation PlayerView
+ (Class)layerClass {
return [AVPlayerLayer class];
}
– (AVPlayer*)player {
return [(AVPlayerLayer *)[self layer] player];
}
– (void)setPlayer:(AVPlayer *)player {
[(AVPlayerLayer *)[self layer] setPlayer:player];
}
@end

A Simple View Controller

Assume you have a simple view controller, declared as follows:

@class PlayerView;
@interface PlayerViewController : UIViewController
@property (nonatomic) AVPlayer *player;
@property (nonatomic) AVPlayerItem *playerItem;
@property (nonatomic, weak) IBOutlet PlayerView *playerView;
@property (nonatomic, weak) IBOutlet UIButton *playButton;
– (IBAction)loadAssetFromFile:sender;
– (IBAction)play:sender;
– (void)syncUI;
@end

The syncUI method synchronizes the button’s state with the player’s state:

– (void)syncUI {
if ((self.player.currentItem != nil) &&
([self.player.currentItem status] == AVPlayerItemStatusReadyToPlay)) {
self.playButton.enabled = YES;
}
else {
self.playButton.enabled = NO;
}
}

You can invoke syncUI in the view controller’s viewDidLoad method to ensure a consistent user interface when the view is first displayed.

– (void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}

The other properties and methods are described in the remaining sections.

Creating the Asset

You create an asset from a URL using AVURLAsset. (The following example assumes your project contains a suitable video resource.)

– (IBAction)loadAssetFromFile:sender {
NSURL *fileURL = [[NSBundle mainBundle]
URLForResource:<#@”VideoFileName”#> withExtension:<#@”extension”#>];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSString *tracksKey = @”tracks”;
[asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:
^{
// The completion block goes here.
}];
}

In the completion block, you create an instance of AVPlayerItem for the asset and set it as the player for the player view. As with creating the asset, simply creating the player item does not mean it’s ready to use. To determine when it’s ready to play, you can observe the item’s status property. You should configure this observing before associating the player item instance with the player itself.

You trigger the player item’s preparation to play when you associate it with the player.

// Define this constant for the key-value observation context.
static const NSString *ItemStatusContext;
// Completion handler block.
dispatch_async(dispatch_get_main_queue(),
^{
NSError *error;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
if (status == AVKeyValueStatusLoaded) {
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
// ensure that this is done before the playerItem is associated with the player
[self.playerItem addObserver:self forKeyPath:@”status”
options:NSKeyValueObservingOptionInitial context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
[self.playerView setPlayer:self.player];
}
else {
// You should deal with the error appropriately.
NSLog(@”The asset’s tracks were not loaded:\n%@”, [error localizedDescription]);
}
});

Responding to the Player Item’s Status Change

When the player item’s status changes, the view controller receives a key-value observing change notification. AV Foundation does not specify what thread that the notification is sent on. If you want to update the user interface, you must make sure that any relevant code is invoked on the main thread. This example uses dispatch_async to queue a message on the main thread to synchronize the user interface.

– (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == &ItemStatusContext) {
dispatch_async(dispatch_get_main_queue(),
^{
[self syncUI];
});
return;
}
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
return;
}

Playing the Item

Playing the item involves sending a play message to the player.

– (IBAction)play:sender {
[player play];
}

The item is played only once. After playback, the player’s head is set to the end of the item, and further invocations of the play method will have no effect. To position the playhead back at the beginning of the item, you can register to receive an AVPlayerItemDidPlayToEndTimeNotification from the item. In the notification’s callback method, invoke seekToTime: with the argument kCMTimeZero.

// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.player currentItem]];
– (void)playerItemDidReachEnd:(NSNotification *)notification {
[self.player seekToTime:kCMTimeZero];
}
上部完。

留下评论

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.