AVFoundation编程指南04-Playback

写在前面

喜欢AVFoundation资料的同学可以关注我的专题:《AVFoundation》专辑
也可以关注我的简书账号

正文

要控制asset的播放,请使用AVPlayer对象。在playback期间,你可以使用 AVPlayerItem实例来管理整个asset的呈现状态,并使用AVPlayerItemTrack对象来管理单个track的呈现状态。要显示视频,请使用AVPlayerLayer对象。

播放 Assets

播放器是一个控制器对象,用于管理asset的播放,例如启动和停止播放,以及寻找特定时间。使用AVPlayer的实例来播放单个asset。你可以使用AVQueuePlayer对象按顺序播放多个item(AVQueuePlayer是AVPlayer的子类)。在OS X上,你可以选择使用AVKit框架的AVPlayerView类在视图中播放内容。

播放器为你提供有关播放状态的信息,因此,如果需要,你可以将用户界面与播放器的状态同步。通常将播放器的输出定向到专门的Core Animation层(AVPlayerLayerAVSynchronizedLayer的实例)。要了解有关图层的更多信息,请参阅Core Animation Programming Guide

多个播放器图层:你可以从单个AVPlayer实例创建许多AVPlayerLayer对象,但只有最近创建的此类图层才会在屏幕上显示任何视频内容。

虽然最终你想要播放asset,但是你不能直接向AVPlayer对象提供asset。相反,你提供了AVPlayerItem的实例。player item管理与其相关联的asset的呈现状态。player item包含player item track - AVPlayerItemTrack的实例 - 对应于asset中的track。各种对象之间的关系如图2-1所示。

图2-1 播放asset

这种抽象意味着你可以同时使用不同的播放器播放一个给定的asset,但每个播放器以不同的方式呈现。图2-2显示了一种可能性,两个不同的播放器使用不同的设置播放相同的asset。例如,使用item tracks可以在播放期间禁用特定track(例如,你可能不想播放声音)。

图2-2 以不同的方式播放相同的asset

你可以使用现有asset初始化播放器item,也可以直接从URL初始化播放器item,以便你可以在特定位置播放asset(然后AVPlayerItem将为resource创建和配置asset)。但是,与AVAsset一样,简单地初始化播放器项并不一定意味着它可以立即播放。你可以观察(使用键值观察)item的状态属性,以确定它是否以及何时可以播放。

处理不同类型的Asset

配置asset进行播放的方式可能取决于你要播放的asset类型。从广义上讲,有两种主要类型:基于文件的assets,你可以随机访问(例如来自本地文件,相机胶卷或媒体库),以及基于流的assets(HTTP实时流式传输格式-简称HLS)。

加载和播放基于文件的asset。播放基于文件的asset有几个步骤:

  • 使用AVURLAsset创建一个asset
  • 使用asset创建一个AVPlayerItem实例。
  • itemAVPlayer实例相关联。
  • 等到item的状态属性指示它已准备好播放(通常你使用KVO来在状态更改时接收通知)。

这种方法在将它们放在一起:Putting It All Together: Playing a Video File Using AVPlayerLayer

创建和准备HTTP实时流以进行播放。使用URL初始化AVPlayerItem的实例。 (你无法直接创建AVAsset实例来表示HTTP Live Stream中的媒体。)

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

当你将播放器item与播放器关联时,它开始准备播放。当它准备好播放时,播放器item将创建AVAssetAVAssetTrack实例,你可以使用它们来检查实时流的内容。

要获得流媒体item的持续时间,你可以观察播放器itemduration属性。当item准备好播放时,此属性将更新为流的正确值。

注意:在播放器item上使用duration属性需要iOS 4.3或更高版本。与所有iOS版本兼容的方法涉及观察播放器item的status属性。当状态变为AVPlayerItemStatusReadyToPlay时,可以使用以下代码行获取持续时间:
   [[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration]

如果你只想播放实时流,可以使用快捷方式直接使用URL创建播放器,使用以下代码:

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

assetsitems一样,初始化播放器并不意味着它已准备好播放。你应该观察播放器的status属性,该属性在准备播放时会更改为AVPlayerStatusReadyToPlay。你还可以观察currentItem属性以访问为流创建的播放器项。

如果你不知道自己的URL类型,请按以下步骤操作:

  • 尝试使用URL初始化AVURLAsset,然后加载其跟踪键。
    如果tracks成功加载,则为asset创建player item
  • 如果上面的方式结果是失败的,请直接从URL创建AVPlayerItem
    观察播放器的status属性以确定它是否可播放。

如果任一路线成功,你最终得到一个player item,然后你就可以与播放器进行关联了。

播放一个Item

要开始播放,需要向播放器发送play消息。

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

除了简单播放外,你还可以管理播放的各个方面,例如播放头的速率和位置。你还可以监控播放器的播放状态;如果你希望将用户界面与asset的表示状态同步,请参阅Monitoring Playback

改变播放速率

你可以通过设置播放器的费率属性来更改播放rate

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

1.0表示“以当前item的自然速率播放”。将速率设置为0.0pause(暂停播放)相同 - 你也可以使用pause

支持反向播放的item可以使用带负数的rate属性来设置反向播放速率。你可以使用playerItem属性canPlayReverse(支持速率值-1.0),canPlaySlowReverse(支持0.0-1.0之间的速率)和canPlayFastReverse(支持小于-1.0的速率值)来确定支持的反向播放类型。

Seeking - 重新定位播放头

要将播放头移动到特定时间,通常使用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];

使用零容差可能需要框架解码大量数据。例如,如果你正在编写需要精确控制的复杂媒体编辑应用程序,则应使用zero

播放后,播放器的头部设置为item的结尾,进一步的播放调用无效。要将播放头放回item的开头,你可以注册以从item接收AVPlayerItemDidPlayToEndTimeNotification通知。在通知的回调方法中,使用参数kCMTimeZero调用seekToTime :

 // 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对象按顺序播放多个itemAVQueuePlayer类是AVPlayer的子类。你使用一系列播放器项初始化队列播放器。

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

然后,你可以使用play来播放队列,就像使用AVPlayer对象一样。队列播放器依次播放每个item。如果要跳到下一个item,则向队列播放器发送advanceToNextItem消息。

你可以使用insertItem修改队列:insertItem:afterItem:, removeItem:removeAllItems。添加新的item时候,通常应使用canInsertItem:afterItem:检查是否可以将其插入队列。你传递nil作为第二个参数来测试是否可以将新item附加到队列。

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

监控playback

你可以监视播放器的演示状态和正在播放的播放器item的多个方面。这对于不受你直接控制的状态更改特别有用。例如:

  • 如果用户使用多任务切换到其他应用程序,则播放器的rate属性将降至0.0

  • 如果你正在播放远程媒体,则播放器项的loadedTimeRangesseekableTimeRanges属性将随着更多数据的可用而更改。
    这些属性告诉你播放器itemtimeline的哪些部分可用。

  • 当为HTTP直播流创建播放器项时,播放器的currentItem属性会更改。
    如果流为内容提供不同的编码,则可能发生这种情况;如果播放器切换到不同的编码,则tracks会改变。

  • 如果由于某种原因播放失败,播放器或播放器项的status属性可能会改变。

你可以使用KVO来监视对这些属性值的更改。

重要提示:你应该注册KVO更改通知,并从主线程上的KVO更改通知中取消注册。这避免了在另一个线程上进行更改时接收部分通知的可能性。 AV Foundation在主线程上调用observeValueForKeyPath:ofObject:change:context:即使在另一个线程上进行了更改操作。

响应状态变化

当播放器或播放器item的状态发生变化时,它会发出KVO变更通知。如果某个对象由于某种原因无法播放(例如,如果重置了媒体服务),则状态将根据需要更改为AVPlayerStatusFailedAVPlayerItemStatusFailed。在这种情况下,对象的error属性的值将更改为一个错误对象,该对象描述对象不再能够播放的原因。

AV Foundation未指定发送通知的线程。如果要更新用户界面,则必须确保在主线程上调用任何相关代码。此示例使用dispatch_async在主线程上执行代码。

- (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属性,以便在图层具有用户可见内容时得到通知。特别是,只有在有东西供用户查看然后执行转换时,才可以将播放器图层插入到图层树中。

跟踪时间

要跟踪AVPlayer对象中播放头位置的变化,可以使用addPeriodicTimeObserverForInterval:queue:usingBlock:addBoundaryTimeObserverForTimes:queue:usingBlock:。例如,你可以执行此操作,使用有关已用时间或剩余时间的信息更新用户界面,或执行其他一些用户界面同步。

这两个方法都返回一个用作观察者的不透明对象。只要你希望播放器调用时间观察block,你就必须对返回的对象保持强引用。还必须平衡这些方法的每次调用以及对removeTimeObserver:的相应调用。

使用这两种方法,AV Foundation不保证为每个传递的间隔或边界调用block。如果先前调用的block的执行尚未完成,AV Foundation不会调用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);
}];

到达Item的结尾

你可以注册以在播放器item完成播放时接收AVPlayerItemDidPlayToEndTimeNotification通知。

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

综述:使用AVPlayerLayer播放视频文件

这个简短的代码示例说明了如何使用AVPlayer对象播放视频文件。它显示了如何:

  • 配置视图以使用AVPlayerLayer层。
  • 创建一个AVPlayer对象。
  • 为基于文件的asset创建AVPlayerItem对象,并使用KVO来观察其状态
  • 通过启用按钮响应item准备播放
  • 播放该item,然后将播放器的头部恢复到起始位置。

注意:为了关注最相关的代码,此示例省略了完整应用程序的几个方面,例如内存管理和取消注册为观察者(用于键值观察或用于通知中心)。要使用AV Foundation,你应该有足够的经验使用·Cocoa·来推断缺失的部分。

有关playback的概念性介绍,请跳至Playing Assets

播放器视图

要播放asset的可视组件,你需要一个包含AVPlayerLayer图层的视图,AVPlayer对象的输出可以定向到该图层。你可以创建UIView的简单子类来适应这种情况:

#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

View Controller示例

假设你有一个简单的视图控制器,声明如下:

@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

syncUI方法将按钮的状态与播放器的状态同步:

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

你可以在视图控制器的viewDidLoad方法中调用syncUI方法,以确保在首次显示视图时具有一致的用户界面。

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

其余属性和方法在其余部分中会有描述。

创建Asset

你可以使用AVURLAssetURL创建asset。 (以下示例假定你的item包含合适的视频资源。)

 - (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.
 }];
}

在完成block中,为asset创建AVPlayerItem实例,并将其设置为播放器视图的播放器。与创建asset一样,只需创建播放器item并不意味着它可以随时使用。要确定何时可以播放,你可以观察item的状态属性。在将player item实例与播放器本身关联之前,你应该配置这个观察者。

当你将播放器item与播放器关联时,可以触发播放器item的准备。

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

响应播放器Item的状态变化

当播放器item的状态改变时,视图控制器接收键值观察改变通知。 AV Foundation未指定发送通知的线程。如果要更新用户界面,则必须确保在主线程上调用任何相关代码。此示例使用dispatch_async在主线程上对消息进行排队以同步用户界面。

- (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;
}

播放Item

播放item涉及向播放器发送play消息。

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

item只播放一次。播放后,播放器的头部设置为item的结尾,并且播放方法的进一步调用将不起作用。要将播放头放回item的开头,你可以注册以从item接收AVPlayerItemDidPlayToEndTimeNotification。在通知的回调方法中,使用参数kCMTimeZero调用seekToTime:

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