美文网首页
AVFoundation编程指南(三)

AVFoundation编程指南(三)

作者: 洱舟 | 来源:发表于2020-05-20 16:41 被阅读0次

    官方文档:AVFoundation Programming Guide

    Playback

    要控制Asset的播放,请使用AVPlayer对象。 在playback期间,您可以使用AVPlayerItem实例管理整个Asset的显示状态,并使用AVPlayerItemTrack对象管理单个轨道的显示状态。 要显示视频,请使用AVPlayerLayer对象.

    播放Asset

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

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

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

    您想播放Asset,却不能直接将Asset提供给AVPlayer对象。 而是提供AVPlayerItem的实例的实例。 player item管理与其相关联的Asset的呈现状态。 player item包含player item track - AVPlayerItemTrack的实例 - 对应于Asset中的track。 各个对象之间的关系如图2-1所示

    image

    这种抽象意味着您可以同时使用不同的播放器来播放给定资产,但是每个播放器以不同的方式进行渲染。 图2-2显示了一种可能性,其中两个不同的玩家使用不同的设置来玩同一资产。 使用item tracks,例如,可以在播放过程中禁用特定轨道(例如,您可能不想播放声音组件)

    image

    您可以使用现有Asset初始化播放器项目,也可以直接从URL初始化播放器项目,以便可以在特定位置播放资源(然后AVPlayerItem将为该资源创建和配置资产)。 不过,与AVAsset一样,仅初始化播放器项目并不一定意味着它可以立即播放。 您可以观察(使用键值观察)项目的状态属性,以确定它是否以及何时可以播放。

    处理不同类型的Asset

    配置要播放的Asset的方式可能取决于您要播放的Asset的种类。广义上讲,主要有两种类型:您可以随机访问的基于文件的Asset(例如从本地文件,相机胶卷或媒体库)和基于流的Asset(HTTP Live Streaming格式)

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

    • 使用AVURLAsset创建Asset
    • 使用Asset创建AVPlayerItem的实例。
    • 将该itemAVPlayer的实例相关联。
    • 请等到该item的状态属性表明它已经可以播放为止(通常您可以使用键值观察在状态更改时接收通知)。
    • 将所有内容放在一起:使用AVPlayerLayer播放视频文件中说明了此方法。

    创建并准备要播放的HTTP实时流。使用URL初始化AVPlayerItem的实例。 (您不能直接创建AVAsset实例来表示HTTP Live Stream中的媒体。)

    static NSString  * playerItemContext = nil;
    
    - (void)setupPlayer{
        [self.view addSubview:self.playerView];
        
        NSURL *url = [NSURL URLWithString:@"http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4"];
        
        AVPlayerItem *playItem = [AVPlayerItem playerItemWithURL:url];
        self.playerItem = playItem;
        
        self.player = [AVPlayer playerWithPlayerItem:playItem];
        
        [self.playerView setPlayer:self.player];
        
        //KVO 观察是否已准备好播放
        [playItem addObserver:self forKeyPath:@"status" options:0 context:&playerItemContext];
        
        //监听视频播放结束状态
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
    }
    
    - (void)loadAsset{
        
        NSURL *url = [NSURL URLWithString:@"http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4"];
        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
        
        NSString *tracksKey = @"tracks";
        [asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                NSError *error;
                AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
    
                if (status == AVKeyValueStatusLoaded) {
                    self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
                     
                    //KVO观察视频播放状态
                    [self.playerItem addObserver:self forKeyPath:@"status"
                                options:NSKeyValueObservingOptionInitial context:&playerItemContext];
                    
                    //通知监听视频播放结束
                    [[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]);
                }
            });
        }];
        
        Float64 durationSeconds = CMTimeGetSeconds([asset duration]);
        self.slider.maximumValue = durationSeconds;
        
    }
    

    当您将播放器项目与播放器相关联时,它就开始可以播放了。 准备好播放时,播放器项目会创建AVAssetAVAssetTrack实例,您可以使用它们来检查实时流的内容。

    要获取流式播放项目的持续时间,您可以观察播放器项目上的duration属性。 当项目准备好播放时,此属性将更新为流的正确值。

    当状态变为AVPlayerItemStatusReadyToPlay时,可以使用以下代码行获取持续时间

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

    播放一个 Item

    //开始播放
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [self.player play];
    }
    
    //改变播放速率
    self.player.rate = 0.5;
    self.player.rate = 2.0;
    

    值1.0表示“以当前item的自然速率播放”。将速率设置为0.0与pause(暂停播放)相同 - 你也可以使用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对象按顺序播放多个item。 AVQueuePlayer类是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];
    }
    

    响应状态变化

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

    跟踪时间

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

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

    • 配置视图以使用AVPlayerLayer层。
    • 创建一个AVPlayer对象。
    • 为基于文件的asset创建AVPlayerItem对象,并使用KVO来观察其状态
    • 通过启用按钮响应item准备播放
    • 播放该item,然后将播放器的头部恢复到起始位置。
    • 注意:为了关注最相关的代码,此示例省略了完整应用程序的几个方面,例如内存管理和取消注册为观察者(用于键值观察或用于通知中心)。要使用AV Foundation,你应该有足够的经验使用·Cocoa·来推断缺失的部分。

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

    PlayerView

    #import <UIKit/UIKit.h>
    #import <AVFoundation/AVFoundation.h>
    NS_ASSUME_NONNULL_BEGIN
    
    @interface PlayerView : UIView
    
    @property (nonatomic, strong) AVPlayer *player;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "PlayerView.h"
    
    @implementation PlayerView
    
    //默认值为[CALayer class]。 为视图创建基础层时使用。
    + (Class)layerClass {
        return [AVPlayerLayer class];
    }
    
    - (AVPlayer *)player{
    
        return [(AVPlayerLayer *)[self layer] player];
    }
    
    - (void)setPlayer:(AVPlayer *)player{
            
         [(AVPlayerLayer *)[self layer] setPlayer:player];
    }
    @end
    

    HomeViewController.m

    #import "HomeViewController.h"
    #import <AVFoundation/AVFoundation.h>
    #import "PlayerView.h"
    
    #define SCREEN_WIDTH   [UIScreen mainScreen].bounds.size.width
    #define SCREEN_HEIGHT  [UIScreen mainScreen].bounds.size.height
    
    @interface HomeViewController ()
    
    @property (nonatomic, strong) AVPlayerItem *playerItem;
    
    @property (nonatomic, strong) AVPlayer *player;
    
    @property (nonatomic, strong) PlayerView *playerView;
    
    @property (nonatomic, strong) UISlider *slider;
    
    @end
    
    @implementation HomeViewController
    
    static NSString  * playerItemContext = nil;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        self.view.backgroundColor = [UIColor whiteColor];
        [self.view addSubview:self.playerView];
        [self.view addSubview:self.slider];
        
        
    //    [self setupPlayer];
        
        [self loadAsset];
        
    }
    
    //URL初始播放器
    - (void)setupPlayer{
        [self.view addSubview:self.playerView];
        
        NSURL *url = [NSURL URLWithString:@"http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4"];
        
        AVPlayerItem *playItem = [AVPlayerItem playerItemWithURL:url];
        self.playerItem = playItem;
        
        self.player = [AVPlayer playerWithPlayerItem:playItem];
        
        [self.playerView setPlayer:self.player];
        
        //KVO 观察是否已准备好播放
        [playItem addObserver:self forKeyPath:@"status" options:0 context:&playerItemContext];
        
        //监听视频播放结束状态
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
    }
    
    
    //Asset初始播放器
    - (void)loadAsset{
        
        NSURL *url = [NSURL URLWithString:@"http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4"];
        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
        
        NSString *tracksKey = @"tracks";
        
        [asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                NSError *error;
                AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
    
                if (status == AVKeyValueStatusLoaded) {
                    self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
                     
                    //KVO观察视频播放状态
                    [self.playerItem addObserver:self forKeyPath:@"status"
                                options:NSKeyValueObservingOptionInitial context:&playerItemContext];
                    
                    //通知监听视频播放结束
                    [[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]);
                }
            });
        }];
        
        Float64 durationSeconds = CMTimeGetSeconds([asset duration]);
        self.slider.maximumValue = durationSeconds;
        
    }
    
    //KVO 观察 status 
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        if (context == &playerItemContext) {
            NSLog(@"change = %@",change);
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    
    //播放视频
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [self.player play];
    }
    
    //视频播放结束
    - (void)playerItemDidReachEnd:(NSNotification *)notification {
        NSLog(@"视频播放结束 -- ");
        
        [self.player seekToTime:kCMTimeZero];
        [self.slider setValue:0];
    }
    
    //滑块的值
    - (void)clickWithSlider:(UISlider *)slider{
        NSLog(@"value = %f",slider.value);
        CMTime firstThird = CMTimeMakeWithSeconds(slider.value, 1);
        [self.player seekToTime:firstThird];
    }
    
    - (void)dealloc{
        
        [self removeObserver:self forKeyPath:@"status"];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
    }
    
    - (PlayerView *)playerView{
        if (!_playerView) {
            CGFloat height = SCREEN_WIDTH * 9 / 16;
            CGFloat y = (SCREEN_HEIGHT - height)/2;
            _playerView = [[PlayerView alloc]initWithFrame:CGRectMake(0, y,SCREEN_WIDTH , height)];
        }
        return _playerView;
    }
    
    - (UISlider *)slider{
        if (!_slider) {
            
            CGFloat y = CGRectGetMaxY(self.playerView.frame) - 30;
            _slider = [[UISlider alloc]initWithFrame:CGRectMake(0,y, SCREEN_WIDTH, 30)];
            
            [_slider addTarget:self action:@selector(clickWithSlider:) forControlEvents:UIControlEventValueChanged];
        }
        return _slider;
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:AVFoundation编程指南(三)

          本文链接:https://www.haomeiwen.com/subject/lxynohtx.html