AVFoundation.framework学习(1)

概述

AVFoundation是您可以用来播放和创建基于时间的视听媒体的几个框架之一。它提供了一个Objective-C接口,用于在基于时间的视听数据的详细级别上工作。例如,您可以使用它来检查,创建,编辑或重新编码媒体文件。您还可以在实时捕获和回放期间从设备获取输入流并操纵视频。

AVFoundation stack

AVFoundation是一个低水平的api,我们要是需求简单,我们应该用高水平的api

注意,我们在AVFoundation中的一些数据结构是在CoreMedia框架中声明的。

AVfoundation框架分成两部分-音频和视频。

如果我们想配置一些音频的相关信息,用AVAudioSession

media的代表和使用

AVfoundation 关键类是AVAsset。了解其结构有助于我们了解框架的工作原理。AVAsset对象是一个集合体,包含是一个或多个metia数据。他能提供整个metia的信息,例如:标题,持续时间,大小等。AVAssets与特定数据的格式没有关系。AVAssets是个超类,子类是通过url创建的assets实例或者是创建新的组件。

在asset中的每个单独的metia数据都是统一中类型,叫做track(轨道)。简单的例子:一个track(轨道)代表音频组件,另一个track(轨道)代表视频组件。在复杂的组合中,可能存在多个重叠的音频或者视频track(轨道)。

asset和track的关系

在AVFoundation中的一个重要概念是初始化asset或者track(轨道),但是初始化不一定意味着可以使用。这中间可能需要时间来计算每个item的时间。当我们需要一些信息的时候,我们获取的值是异步通过我们定义的block传给我们的,不应该阻塞当前线程。

播放

AVFoundation 允许我们以复杂的方式对asset进行播放。为了支持这一点,AVFoundation将asset的标示状态和 asset本身是区分开的。例如,这允许我们在不同的分辨率下同时播放同一个asset的两个不同片段。asset的状态是被player item对象管理。而在asset中的track的state是被 player item track的对象管理。

track 和 asset 的分别被自己的player管理

我们用player播放 一个player item对象,可以直接将player 输出到
到coreAnimation 的层上。我们也可以使用队列排程播放player item 集合中元素。

读写和重新编码asset

AVFoundation有几种方式创建新的asset对象。我们可以简单重新编码现在的asset或者在ios4.1以及更高版本中,我们可以修改asset并保存其结果。

我们可以用少量常用的预设定义的格式对asset进行重新编码来获取一个session,这个session是可以使用的。如果我们需要更多的控制,那么我们能通过asset reader和asset writer对象将asset转换到领一种形式。(这里我也不知道怎么说,看下面吧。)

缩略图

如果要创建缩略图图像,我们应该初始化AVAssetImageGenerator对象用于asset对象。AVAssetImageGenerator对象使用默认的视频track来生成图像。

编辑

AVFoundation使用composition 来将一个或者多个track创建一个新的asset。
我们可以使用可变的composition来增加或者删除track或者调整其时间顺序。我们也可以设置相对音量或者ramping。一个composition是存储在内存中的metia的集合。当我们用session到户一个composition的时候,composition将折叠成一个文件。
我们也可以用带有buffer的metia或者静态图创建asset

拍照或者录视频

照相或者麦克风的录制是被 capture session对象管理的。
一个 capture session对象管理输入设备到输出设备的数据流。
不管 capture session是否再工作,我们都可以为单个 capture session配置多个输入和输出。我们需要发消息告诉 capture session是否启动和停止数据流。

这里,我们可以用preview layer 对象来展示给用户相机正在录制的内容。

AVFoundation并发编程

AVfoundation的block,键值观察,通知等的回调不是在特定的线程或者队列上进行的。相反,AVFoundation是在执行内容任务的线程或者队列上调用这些处理程序的。

就通知和线程有两个准则:

  • 与UI有关的通知都发生在主线程上
  • 如果需要我们指定或者创建的队列的类或者方法将返回该队列上的通知。

Asset 的使用

这个类上面已经说了很重要,接下来我们看看这个asset如何使用

assets 可以用用户的ipod库或者照片库中的文件或media。当我们获取asset时候,我们可能无法立即获取检索项的信息。一旦我们拥有一个电影asset,我们就可以从中获取静态图片,将其专门为其他格式或者修剪内容。

创建一个asset对象

我们可以用URL创建一个asset。用url我们应该用其子类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 初始化的第二个参数是个字典。这个字典中只有一个key,是AVURLAssetPreferPreciseDurationAndTimingKey,对应的value是布尔值,这个字段指示asset是否应准备好提供精确的持续时间并按照时间提供精确的随机访问。

AVURLAssetPreferPreciseDurationAndTimingKey设置为yes,那么asset对数据解析就更详细。应该是这么理解的

获取asset的准确持续时间可能需要大量的处理开销。使用近似持续时间通常情况下可以减少开销,也可以满足播放功能。

  • 如果我们只用asset播放资源,我们给第二个参数传递nil就可以了,也可以传递字典不过key AVURLAssetPreferPreciseDurationAndTimingKey对应的值是NO。
  • 如果想要将asset添加到AVMutableComposition中,这时候需要精确的随机访问。我们应该将AVURLAssetPreferPreciseDurationAndTimingKey值设置为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];

访问用户的asset

放问用户的照片库或者ipod库,我们需要获取一个代表这些库资源的一个url。

  • 访问ipod库,我们应该 MPMediaQuery实例,然后用MPMediaItemPropertyAssetURL获取一个asset url资源.
    具体怎么使用可参考官方文档
  • 访问照片库,我们应该用ALAssetsLibrary.不过在ios9以后,我们应该用PHPhotoLibrary库来替代ALAssetsLibrary。

这里只是举例获取一个视频资源的asset
这里没有采用最新的框架,而是用的老的ALAssetsLibrary

 ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
        // Within the group enumeration block, filter to enumerate just videos.
        [group setAssetsFilter:[ALAssetsFilter allVideos]];
        if (group.numberOfAssets==0) {
            return ;
        }
        // For this example, we're only interested in the first item.
        [group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:0]
                                options:0
                             usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
                                 
                                 // The end of the enumeration is signaled by asset == nil.
                                 if (alAsset) {
                                     ALAssetRepresentation *representation = [alAsset defaultRepresentation];
                                     NSURL *url = [representation url];
                                     AVAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
                                     // Do something interesting with the AV asset.
                                 }
                             }];
    }
                         failureBlock: ^(NSError *error) {
                             // Typically you should handle an error more gracefully than this.
                             NSLog(@"No groups");
                         }];
    
从视频中获取静态图片

我们要是想获取静态图像,进行播放。我们应该使用AVAssetImageGenerator对象。我们用asset初始化AVAssetImageGenerator对象。asset在初始化的时候可能没有可视的 track,但是AVAssetImageGenerator也可能初始化成功。因此,我们在使用AVAssetImageGenerator的时候应该用tracksWithMediaCharacteristic函数测试asset是否具有可视 的track。

AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
    AVAssetImageGenerator *imageGenerator =
        [AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
    // Implementation continues...
}

我们可以配置AVAssetImageGenerator的很多方面。例如,我们可以指定生成图片的最大尺寸,光圈模式。我们也可以在指定时间生成单个图像,或者一系列图像。但是我们在生成图像之前一定保证对象不能被释放掉了。

准备使用asset

初始化asset或者track,那不意味我们可以检索所有的信息。这之间可能需要一些时间来计算项目的持续时间。我们应该调用AVAsynchronousKeyValueLoading协议来询问值,然后通过block回调来处理回复信息。我们不应该在计算的时候阻塞线程。
我们可以通过调用statusOfValueForKey 来判断属性是否已经加载。首次加载资源时,大部分属性的值都是AVKeyValueStatusUnknown。要为一个或者多个属性加载值,调用loadValuesAsynchronouslyForKeys:completionHandler方法。在完成处理的过程中,我们可以根据属性的状态采取适当的操作。我们应该随时监测加载状态,因为由于某些原因,可能导致url无法访问或者加载被取消掉了。

NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"duration"];
 
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
 
    NSError *error = nil;
    AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"duration" error:&error];
    switch (tracksStatus) {
        case AVKeyValueStatusLoaded:
            [self updateUserInterfaceForDuration];
            break;
        case AVKeyValueStatusFailed:
            [self reportError:error forAsset:asset];
            break;
        case AVKeyValueStatusCancelled:
            // Do whatever is appropriate for cancelation.
            break;
   }
}];

这个函数的keys 是该类的属性的名字。

生成单个图片

我们应该使用函数 copyCGImageAtTime:actualTime:error:(https://developer.apple.com/documentation/avfoundation/avassetimagegenerator/1387303-copycgimageattime)`

AVAsset *myAsset = <#An asset#>];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:myAsset];
 
Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
CMTime midpoint = CMTimeMakeWithSeconds(durationSeconds/2.0, 600);
NSError *error;
CMTime actualTime;
 
CGImageRef halfWayImage = [imageGenerator copyCGImageAtTime:midpoint actualTime:&actualTime error:&error];
 
if (halfWayImage != NULL) {
 
    NSString *actualTimeString = (NSString *)CMTimeCopyDescription(NULL, actualTime);
    NSString *requestedTimeString = (NSString *)CMTimeCopyDescription(NULL, midpoint);
    NSLog(@"Got halfWayImage: Asked for %@, got %@", requestedTimeString, actualTimeString);
 
    // Do something interesting with the image.
    CGImageRelease(halfWayImage);
}
生成一组图片

要生成一些列图片,调用generateCGImagesAsynchronouslyForTimes:completionHandler:方法。第一个参数是NSValue数组,每个对象包含一个CMtime结构,指定要生成的图像的asset时间。第二个参数是一个block,是生成每张图片的回调。我们在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来取消请求一些列图像。

裁剪或者转码视频

我们可以使用AVAssetExportSession 对象将影片从一种格式转码为另一种格式,并且可以修剪影片。工作流程如下


一个session是一个控制对象异步来管理一个asset的导出。我们用asset初始化session对象,并且可以指定导出的asset的名字或者一些转换参数。然后配置导出来的url或者文件类型,以及一些其他设置,例如源数据以及应针对网络使用.

我们可以使用exportPresetsCompatibleWithAsset检测给定的asset是否可以导出需要的preset;

AVAsset *anAsset = <#Get an asset#>;
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
        initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
    // Implementation continues.
}

我们可以通过输出的url来完成会话。(这个url必须是一个文件的url)。AVAssetExportSession可以从url的路径扩展中推断出文件类型;但是一般我们会使用outputFileType路径来直接设定他。我们还可以指定其他属性,例如时间范围,输出文件长度的限制,是否应针对网络使用优化导出的文件以及视频合成。

 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:。block是当导出完成后会回调;在该block中,我们应该检查会话的状态值以确定导出是否成功,失败还是已取消。

[exportSession exportAsynchronouslyWithCompletionHandler:^{
 
        switch ([exportSession status]) {
            case AVAssetExportSessionStatusFailed:
                NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"Export canceled");
                break;
            default:
                break;
        }
    }];

要是想取消导出操作,调用cancelExport ,取消导出。
The export will fail if you try to overwrite an existing file, or write a file outside of the application’s sandbox. It may also fail if:
如果我们试图覆盖一个已经存在的文件或者写一个文件到应用程序的沙盒之外可能导致失败。(不可以覆盖文件)
下列情况也可能失败:

  • 有电话来
  • 应用程序在后台,或者另一个程序在播放

以上情况失败,我们应该用户导出失败,然后运行用户重新开启导出。

AVAssetImageGenerator 获取帧图片
AVAssetExportSession 文件转码


播放

控制asset的播放,使用AVPlayer对象。在播放期间,我们可以使用AVPlayerItem对象来管理asset的状态,可以使用AVPlayerItemTrack对象来管理单个track的状态。要显示视频,请使用AVPlayerLayer对象。

播放asset

player是一个控制器对象,用于管理asset的播放,例如启动和停止,以及寻找特定时间。如果使用AVPlayer对象播放一个asset。我们也可以使用AVQueuePlayer对象按照顺序播放多个asset。(AVQueuePlayer是AVPlayer的子类)。

AVPlayer 为我们提供播放状态的信息。如果需要,我们可以将用户界面和播放器的状态同步。我们一般是把播放器的输出的内容定向的输送到coreAnimation层上(AVPlayerLayer或者AVSynchronizedLayer)。

我们可以创建很多的AVPlayerLayer,但是只有最后创建的AVPlayerLayer才能在屏幕上显示内容

当播放asset时候,我们不会直接将asset提供给avplayer,我们应该给AVPlayer提供AVPlayerItem对象。用AVPlayerItem管理asset的播放状态。而一个AVPlayerItem包含AVPlayerItemTrack对象-对应于asset中的track。


这种抽象意味着我们看而已同事使用不同的player播放同一个asset,但是每个player可以以不同的状态呈现。



入上图,两个不同的player使用不同的设置播放相同的asset,例如,使用AVPlayerItemTrack播放期间静音。

我们可以用asset初始化AVPlayerItem对象,或者用一个url直接初始化AVPlayerItem。但是,AVPlayerItem初始化完毕不意味着就可以用来播放了。我们应该观察asset的状态,来确定何时可以播放。

asset不同类型的处理

player对asset播放的方式取决于asset的类型。广义上讲,有两种asset类型。来自文件的asset,我们可以随机访问(本地文件,相机或者流媒体),我们可以播放基于stream的asset

基于本地文件的播放步骤
  • 1用AVURLAsset创建一个asset
  • 2.用asset实例化一个AVPlayerItem对象
  • 3.将AVPlayerItem对象绑定到AVPlayer上
  • 4.检查AVPlayerItem的状态是否可以进行播放。(我们可以通过kvo进行观察)
基于数据流的播放方式
  • 创建和准备数据流准备播放。
  • 使用URL初始化AVPlayerItem的实例
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];

当我们将AVPlayer和AVPlayerItem联系在一起的时候,这时候就开始准备播放了。当准备好播放时,AVPlayer将创建AVAsset和AVAssetTrack实例,我们可以用他们来时时检查数据流的内容。

比如我们想获取流媒体流的持续时间,我们可以观察playItem的duration属性。当 playItem准备播放时候,该属性就根据播放随时更新为正确的值。

[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];

如果我们只是想简单的播放下流媒体。我们也可以使用下列方式。

self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];

这里需要注意,当我们初始化了AVPlayer和AVPlayerItem,这里还不能播放,我们应该观察AVPlayer的status属性,当改初始化变成了
AVPlayerStatusReadyToPlay的时候,才可以播放。

如果我们不知道url的种类(本地文件还是流媒体),我们应该采用下面的步骤

+1 用url初始化AVURLAsset。然后加载track的key。如果track加载成功,然后我们用asset再创建 playeritem

  • 2如果失败,直接用URL创建playeritem。然后观察player的状态决定是否播放。

播放playItem

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

除了简单播放外,我们还可以管理播放的各个方面,例如播放头的速率和位置。我们还可以监控播放器的播放状态。

改变播放速率

aPlayer.rate = 0.5;
aPlayer.rate = 2.0;

rate=1.0代表正常播放,如果将rate=0.0,相当于暂停播放了。

playerItem是支持反向播放的。我们可以用负数的rate属性设置反向播放速率(这不就是快退么)。我么也可以设置playerItem属性canPlayReverse,canPlaySlowReverse,canPlayFastReverse来确定支持的反向播放类型。

查找播放头

如果想将播放头移动到特定时间,我们通常使用seekToTime:函数

CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn];

seekToTime方法是针对性能而不是精度进行调整的。如果我们需要很精确的移动播放头,使用下面的函数seekToTime:toleranceBefore:toleranceAfter:

CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

函数的具体含义这里不做介绍,只是介绍大概使用,具体的需要自己去摸索

如果使用零容忍的误差这就可能让框架解码大量数据。因此这里除非需要非常精确的控制流媒体的编辑。否则不要用

当播放结束后,播放器的头部就被设置在了item的结尾处。要是需要重播,那么我们需要将播放头放回到item的头部。我们可以通过注册通知AVPlayerItemDidPlayToEndTimeNotification来获取item是否播放完毕。

// 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];
}

多项目播放

我们使用AVQueuePlayer 按照顺序播放多个项目。

NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];

改对象会依次播放items,如果想跳过当前的item,调用advanceToNextItem函数即可。

因为涉及多个item么,因此我们就可以对这些item进行增删。api 是afterItem:,removeItem:和removeAllItems。但是当我们添加新的item时候,需要调用下 canInsertItem:afterItem:
检测下是否可以增加新的item,该函数第二个参数传入nil,就是相当于追加item到结尾。

AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
    [queuePlayer insertItem:anItem afterItem:nil];
}

监控播放

我们可以监控player的呈现状态和playItem的很多方面。这对于不受我们控制的状态更改特别有用。

  • 如果用户切换到其他应用去了,那么player的rate就更改成了0.0
  • 如果我们正在播放远程流媒体,那么plaer的loadedTimeRanges 和seekableTimeRanges 属性将会随着数据的增加而改变。(监控这里可以让我们知道还有多少流媒体可以使用,一遍我们暂停或者播放流媒体)

监控player item的下列属性是有效的

  • 当为流媒体创建player时候,player的currentItem属性会更改
  • 当播放流媒体时,player item的tracks属性会发生变化

数据流可以为内容提供不同的编码。如果player 切换了编码,那么track会发生变化。

  • player因为某些原因播放失败,player或者player item的status会发生变化。

我们可以用kvo观察上述变化。

我们应该在主线程里面注册KVO。AVFoundation调用KVO的回调函数是在主线程的。

响应状态的变化

当player或者playitem的status发生变化,kvo就会收到通知。如果一个对象由于某些原因不能播放。那么status就会改变成AVPlayerStatusFailed或者AVPlayerItemStatusFailed。那么我们可以从对象error属性获取到这个错误的原因。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context {
 
    if (context == <#Player status context#>) {
        AVPlayer *thePlayer = (AVPlayer *)object;
        if ([thePlayer status] == AVPlayerStatusFailed) {
            NSError *error = [<#The AVPlayer object#> error];
            // Respond to error: for example, display an alert sheet.
            return;
        }
        // Deal with other status change if appropriate.
    }
    // Deal with other change notifications if appropriate.
    [super observeValueForKeyPath:keyPath ofObject:object
           change:change context:context];
    return;
}
跟踪视觉展示的准备情况

我们可以观察AVPlayerLayer对象的readyForDisplay
属性,这样我们可以获取到layer是否有用户可见的内容。

跟踪时间

如果我们要跟踪AVPlayer对象的播放头的位置,我们可以使用addPeriodicTimeObserverForInterval:queue:usingBlock:或addBoundaryTimeObserverForTimes:queue:usingBlock:方法。我们可以执行上面函数获取已用时间或者剩余时间,将这些数据更新到用户界面。

  • 使用addPeriodicTimeObserverForInterval:queue:usingBlock 方法,block将会按照指定的时间调用。

  • 使用addBoundaryTimeObserverForTimes:queue:usingBlock:方法。可以传递一个CMTime结构体包装成NSValule的数组。当在这些指定之间到达时候,block将触发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);
}];
playeritem播放结束

我们可以注册 AVPlayerItemDidPlayToEndTimeNotification
通知检测 playeritem播放结束

[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
                                         selector:@selector(<#The selector name#>)
                                             name:AVPlayerItemDidPlayToEndTimeNotification
                                           object:<#A player item#>];

将上面内容放在一起,使用AVPlayerLayer 播放视频文件

播放步骤

  • 1用AVPlayerLayer 配置view的layer
  • 2 创建AVPlayer 对象
  • 3 创建AVPlayerItem对象,用kvo观察其状态
  • 4.当kov观察其可以播放,那么播放这个AVPlayerItem
播放view

重写view的+ (Class)layerClass 方法

#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

看到这里,我们可以用avfoundation进行简单的视频播放编码了。

找一个mp4 文件
简单播放代码如下

#import <AVFoundation/AVFoundation.h>
#import "UIPlayerView.h"
@interface ViewController ()
@property (nonatomic) AVPlayer *player;
@property (nonatomic) AVPlayerItem *playerItem;
@end



@implementation ViewController
-(AVURLAsset * )getAVURLAsset{
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"abc" withExtension:@"mp4"];
    AVURLAsset * asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    return  asset;
}

static const NSString *ItemStatusContext;

- (void)viewDidLoad {
    [super viewDidLoad];
    
   UIPlayerView *playerView= [[UIPlayerView alloc]initWithFrame: [UIScreen mainScreen].bounds];
    [self.view addSubview:playerView];
    AVAsset * asset = [self getAVURLAsset];
    
    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(event) forControlEvents:UIControlEventTouchDown];
    button.frame =  [UIScreen mainScreen].bounds;
    [self.view addSubview:button];
    
    NSString *tracksKey = @"tracks";
    [asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:^{
        NSError * error = nil;
        AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"tracks" error:&error];
        switch (tracksStatus) {
            case AVKeyValueStatusLoaded:
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    self.playerItem = [[AVPlayerItem alloc]initWithAsset:asset];
                    ///观察者
                    [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial context:&ItemStatusContext];
                    self.player = [[AVPlayer alloc]initWithPlayerItem:self.playerItem];
                    playerView.player = self.player;
                });
                
            }
                break;
            case AVKeyValueStatusFailed:

                break;
            case AVKeyValueStatusCancelled:

                break;
        }
    }];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context {
    
    if (context == &ItemStatusContext) {
        if ([self.playerItem status] == AVPlayerStatusFailed) {
        }else if ([self.playerItem status]==AVPlayerItemStatusReadyToPlay){
            [self.player play];
            return;
        }
    }
    return;
}




- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface UIPlayerView : UIView
@property (nonatomic) AVPlayer *player;

@end
#import "UIPlayerView.h"

@interface UIPlayerView()
@end

@implementation UIPlayerView

+ (Class)layerClass{
    return [AVPlayerLayer class];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
    }
    return self;
}

- (AVPlayer*)player {
    return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player {
    [(AVPlayerLayer *)[self layer] setPlayer:player];
}
@end

这里需要注意,我们将mp4文件拖到工程里面,用bundle获取是nil。解决方式看这里**点击项目 -> TARGETS -> Build Phases -> Copy Bundle Resource **

获取一帧图片
    AVAsset * asset = [self getAVURLAsset];
    if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
        AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
        CMTime midpoint = CMTimeMakeWithSeconds(10, 2);
        NSError *error;
        CMTime actualTime;
        CGImageRef halfWayImage = [imageGenerator copyCGImageAtTime:midpoint actualTime:&actualTime error:&error];
        if (halfWayImage != NULL) {
            
            NSString *actualTimeString = (NSString *)CFBridgingRelease(CMTimeCopyDescription(NULL, actualTime));
            NSString *requestedTimeString = (NSString *)CFBridgingRelease(CMTimeCopyDescription(NULL, midpoint));
            NSLog(@"Got halfWayImage: Asked for %@, got %@", requestedTimeString, actualTimeString);
            UIImageView * imageView=[[UIImageView alloc]initWithFrame:self.view.bounds];
            imageView.image = [UIImage imageWithCGImage:halfWayImage];
            [self.view addSubview:imageView  ];
            CGImageRelease(halfWayImage);
      
        }
    }

视频剪切

-(AVURLAsset * )getAVURLAsset{
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"abc" withExtension:@"mp4"];
    AVURLAsset * asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    return  asset;
}

-(AVURLAsset * )getTestAVURLAsset{
    NSURL *fileURL = [NSURL fileURLWithPath:self.path];
    AVURLAsset * asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    return  asset;
}


static const NSString *ItemStatusContext;

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
///这里需要注意,输出路径的文件一定要添加扩展名
    NSString *path = [[[paths objectAtIndex:0]stringByAppendingPathComponent:[NSUUID UUID].UUIDString] stringByAppendingString:@".mp4"];
    self.path = path;
}



-(void)preset{
    AVAsset * anAsset = [self getAVURLAsset];
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
    NSLog(@"%@",compatiblePresets);
    if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
        self.session = exportSession;
        exportSession.outputURL=[NSURL fileURLWithPath:self.path];
        exportSession.outputFileType = AVFileTypeMPEG4;
        CMTime start = CMTimeMakeWithSeconds(1.0, 600);
        CMTime duration = CMTimeMakeWithSeconds(6.0, 600);
        CMTimeRange range = CMTimeRangeMake(start, duration);
        exportSession.timeRange = range;
        NSOSStatusErrorDomain;
        [exportSession exportAsynchronouslyWithCompletionHandler:^{
            switch ([exportSession status]) {
                case AVAssetExportSessionStatusFailed:
                    NSLog(@"%@",[exportSession error]);
                    NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
                    break;
                case AVAssetExportSessionStatusCancelled:
                    NSLog(@"Export canceled");
                    break;
                case AVAssetExportSessionStatusCompleted:{
                    dispatch_async(dispatch_get_main_queue(), ^{
                     [self play];
                    });
                }
                default:
                    
                    break;
            }
        }];
    }
    
   
}


-(void)play{
    UIPlayerView *playerView= [[UIPlayerView alloc]initWithFrame: [UIScreen mainScreen].bounds];
    [self.view addSubview:playerView];
    AVAsset * asset = [self getTestAVURLAsset];
    
    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(event) forControlEvents:UIControlEventTouchDown];
    button.frame =  [UIScreen mainScreen].bounds;
    [self.view addSubview:button];
    
    NSString *tracksKey = @"tracks";
    [asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:^{
        NSError * error = nil;
        AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"tracks" error:&error];
        NSLog(@"%@",error);
        switch (tracksStatus) {
            case AVKeyValueStatusLoaded:
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    self.playerItem = [[AVPlayerItem alloc]initWithAsset:asset];
                    ///观察者
                    [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial context:&ItemStatusContext];
                    self.player = [[AVPlayer alloc]initWithPlayerItem:self.playerItem];
                    playerView.player = self.player;
                });
                
            }
                break;
            case AVKeyValueStatusFailed:
            
                break;
            case AVKeyValueStatusCancelled:
                
                break;
        }
    }];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context {
    
    if (context == &ItemStatusContext) {
        if ([self.playerItem status] == AVPlayerStatusFailed) {
        }else if ([self.playerItem status]==AVPlayerItemStatusReadyToPlay){
            [self.player play];
            return;
        }
    }
    return;
}

视频剪辑,我们输出的文件要有扩展名
输出文件路径上一定不能存在文件,要是输出的文件存在,那么会报错,因此,我们这里我们应该先检查文件是否存在,存在就删除,然后再开始剪切文件。

官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容