AVFoundation Programming Guide - Playback

Playback

为了控制资源(assets)的播放(playback),我们可以使用AVPlayer 对象,在播放期间,我们能够使用AVPlayerItem实例来管理所播放资源的呈现状态,AVPlayerItemTrack 对象能够管理一个单独轨道(individual track)的呈现状态。为了显示视频,我们需要使用AVPlayerLayer 对象。

Playing Assets

播放器(player )是一个控制对象(controller object),我们使用该控制对象管理资源的播放,例如:开始(starting)和停止播放(stopping playback),而且移动到(seeking to)具体的时间。我们能够使用AVPlayer来播放单一的资源。我们能够使用AVQueuePlayer(AVQueuePlayerAVPlayer的子类)播放序列(sequence)中的一系列对象。

播放器(player )提供了我们播放的状态信息,如果需要,我们能够根据播放的状态同步用户界面。代表性的就是播放器能够在专门的Core Animation layer进行输出播放。为了了解更多layers相关,可以看Core Animation Programming Guide
这里注意我们能够使用单一的AVPlayer实例创建多个AVPlayerLayer对象,但是只有最后创建的layer能够显示video

虽然我们的目的是想播放资源,但是我们不能够直接为AVPlayer对象提供assets。相反我们需要使用AVPlayerItem对象。因为player item对象管理着与它相关联的资源呈现的状态。一个player item包含多个player item tracks,是AVPlayerItemTrack的实例对象,相当于在资源(asset)中的tracks.下图显示了我们所描述的关系:

Figure 2-1  Playing an asset



我们能够使用不同的players同时播放给定的资源(asset),但是每一个player必须使用不同的渲染方式。Figure 2-2显示了一种可能性,两个不同的播放者使用不同的设置播放相同的资源。例如:在播放期间使特定的itemTrack失效(如:你不想播放声音部分)。

Figure 2-2  Playing the same asset in different ways



我们能够使用存在的资源(asset)初始化一个player item对象,或者直接使用URL初始化player item以致于我们能够在特定的位置进行播放资源(resource),这里AVPlayerItem将根据给定点的resource创建并配置一个asset。就AVAsset来说,简单的初始化player item对象,并不一定意味着准备好立马进行播放,我们可以使用观测者来观察player item的status属性来确定是否player item已经准备好播放。

Handling Different Types of Asset


配置资源(asset)进行播放的方式取决于我们使用的是哪种类型的资源(asset),一般来说,这里有两种类型:基于文件的资源(file-based assets),这些文件我们可以随意访问(比如:本地文件,照相机,媒体库),另外一种类型是基于流的资源(stream-based assets)(HTTP Live Streaming format).

为了加载并播放file-based assets,这里有一些步骤:
1:使用AVURLAsset创建一个asset
2:使用刚刚创建的asset来创建AVPlayerItem实例
3:使用AVPlayerItem实例来初始化AVPlayer
4:等待AVPlayerItem的status属性指示准备播放
该方法的具体实现将在后续 Putting It All Together: Playing a Video File Using AVPlayerLayer部分解释。

为了创建和准备 HTTP live stream用于播放,通过制定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];
当我们为player关联player item的时候,已经开始准备播放了,当准备开始播放的时候,player item将创建AVAssetAVAssetTrack实例,可以使用这些事例检测流媒体(live stream)的内容。

为了获取流媒体对象(streaming item)时间,我们能够观察player item的duration属性。当player item准备播放,该属性将为流媒体更新到正确的值。

注意:使用player item的duration属性要求iOS4.3或之后,可以使用观察者模式,观察属性 status当player item的 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和player items来说,初始化player并不意味着准备播放,我们应该观察status 属性,当属性变为AVPlayerItemStatusReadyToPlay就说明准备播放了,我们也可以观察currentItem 属性来获取为流媒体(stream)创建的player item对象。

 如果你并不知道你有什么类型的URL,可以使用如下步骤:
1:尝试着使用URL初始化AVURLAsset,这时候加载AVURLAssettracks key值,如果加载tracks成功,这时候能够为asset创建一个player item对象。
2:如果第一步失败,直接使用URL创建AVPlayerItem并观察 status属性来确定是否可以播放
以上两种方法任意一种成功,我们将使用player item对象创建player。

Playing an Item


为了开始播放,我们可以调用playerplay方法:

- (IBAction)play:(UIButton *)sender {
    [player play];
}
除了简单播放之外,我们可以管理播放的各个方面,比如:播放是速度和播放头的位置。我们也可以监控player播放的状态情况。更多可以看: Monitoring Playback

Changing the Playback Rate

我们能够通过设置playerrate 属性来改变播放的速度:

aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
该值为1.0意味这current item是以正常的速度进行播放,设置rate0.0相当于停止播放,也就是使用AVplayerpause方法。

AVplayeritem支持倒退播放(reverse playback),我们能够设置rate属性为负值来设置倒退播放的速度。我们能够使用playerItem的相关属性确定倒退播放的类型。如:canPlayReverse (supports a rate value of -1.0),  canPlaySlowReverse (supports rates between 0.0 and 1.0) and canPlayFastReverse (supports rate values less than -1.0).

Seeking—Repositioning the Playhead

为了移动播放头到特定的时间,我们经常使用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];
使用容忍度为0可能要求框架编码大量的数据,我们使用zero的情况:例如,写复杂的媒体编辑应用并要求精准的控制。

在播放之后,播放头将显示在player item的最后并且进一步调用play播放方法无效。为了使用播放头的位置回到最item的开始,我们能够注册通知接受AVPlayerItemDidPlayToEndTimeNotification通知,在通知的回调中,我们调用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];
}


Playing Multiple Items

我们能够使用一个 AVQueuePlayer 对象来播放序列中的多个itemsAVQueuePlayerAVplayer的子类,我们使用player item数组来初始化queue player

NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
这时候我们能够使用play方法播放queue,就像一个AVPlayer对象,queue player将轮流播放每一个item,如果我们想跳过下一个item,我们可以向列队发送advanceToNextItem信息。

我们还可以使用 insertItem:afterItem:removeItem:, and removeAllItems方法来修改列队(queue),当我们添加一个新的item的时候,首先我们需要确认该item是否能够被插入到列队中,使用canInsertItem:afterItem:.方法,我们为第二个参数传入nil来测试新的item是否能够加入列队。

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

Monitoring Playback


我们能够监控许多方面,包括播放者(player)呈现的状态和player item播放时的状态,状态的改变是非常有用但是这种改变并不是在我们的控制之下。例如:

1:如果用户使用多任务并在不同的应用之间进行切换,player的rate 属性将下降到0.0
2:如果我们正在播放远程媒体内容,随着越来越多的可用数据,player item的loadedTimeRangesseekableTimeRanges属性将随着更多数据变得可使用而发生改变。这些属性告诉我们player item的时间轴(timeline)是可以使用了。
3player item创建使用了HTTP流媒体(HTTP live stream)playercurrentItem属性将改变
4:当正在播放HTTP live stream的时候,player itemtracks属性可能改变。比如:当流媒体(stream)为内容提供了不同的编码;或者player切换到不同的编码,tracks将改变
5:如果playback由于某些原因失败,player或者player itemstatus 属性可能改变。
我们能够使用key-value observing来检测属性的改变。

注意:我们应该在主线程注册KVO改变通知和移除KVO改变通知,如果改变是发生在其他线程并发主线程,这可以避免接收部分通知的可能性。AV Foundation将在主线程调用observeValueForKeyPath:ofObject:change:context:,即使是在其它线程发生改变。

Responding to a Change in Status


player或者player item的状态改变,将发送观察改变通知,如果一个对象由于某些原因不能够播放(例如:媒体服务被重置),播放状态将改变为 AVPlayerStatusFailed AVPlayerItemStatusFailed在这种情况,对象的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;
}

Tracking Readiness for Visual Display

我们能够观察AVPlayerLayer对象的 readyForDisplay属性接受当layer已经显示用户视觉内容的通知,在特定情景中,我们能够插入player layerlayer tree中,执行相应的过渡动画到用户所关注的内容。

Tracking Time

为了跟踪AVPlayer对象播放头位置的改变,我们能够使用addPeriodicTimeObserverForInterval:queue:usingBlock: 或者addBoundaryTimeObserverForTimes:queue:usingBlock:方法。例如:根据时间的消耗和时间的剩余更新用户的界面,或者执行一些其它用户界面同步操作。

 addPeriodicTimeObserverForInterval:queue:usingBlock:方法中的block将在我们所指定的具体时间调用,如果时间跳过,播放开始或停止
 addBoundaryTimeObserverForTimes:queue:usingBlock:方法,我们传递一个数组,数组中的值是包含结构体CMTimeNSValue对象,block将在任意指定值到达时进行。

上面两个方法都将返回一个不透明对象作为观察者。只要我们想player能够调用时间观察者的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);
}];

Reaching the End of an Item

我们能够注册通知AVPlayerItemDidPlayToEndTimeNotification notification,当player item完成播放。

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


Putting It All Together: Playing a Video File Using AVPlayerLayer


简短的代码就能够解释怎样使用AVPlayer对象播放video file,明白几点:
1:配置一个view来使用AVPlayerLayer layer
2
:创建一个AVPlayer对象
3
:创建一个AVPlayerItem对象基于文件的asset并且使用key-value observing 观察status属性状态变化
4:根据player item的响应准备播放
5:播放item并在播放完成使播放头回到最开始位置

注意:为了集中于大多数相关代码,该例子将省略完整应用的几个部分,比如:内存管理和去除观察者。


The Player View 

为了播放视觉内容,我们需要一个view来包含一个 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

A Simple 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

同步UI方法用于根据player的状态同步显示button的状态:

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


Creating the Asset

使用 AVURLAsset根据URL创建asset

- (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 实例,并且使用该AVPlayerItem 对象初始化AVplay,在为view的player赋值。当我们创建了asset,简单的创建player item并不意味着准备使用,我们需要确定什么时候可以播放,所有观察status属性。我们应该在为player实例赋值player 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]);
  }
});

Responding to the Player Item’s Status Change

当player 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;
}

Playing the Item

使用player的paly方法播放视频

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

一旦item被播放,在播放结束,player的播放头会被设置在item的最后位置,进一步调用play方法无效,为了调整播放头的位置到item的开始位置,我们需要注册AVPlayerItemDidPlayToEndTimeNotification通知,并在通知回调方法中,执行seekToTime:方法,参数为 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];
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值