AVFoundation-AVPlayer视频播放

3,919 阅读4分钟
原文链接: developerdoc.com

AV Player是一个用来播放给予时间的视听媒体的控制器对象。将视频资源导出到界面需要使用AVPlayerLayer类;AVPlayer只管理一个单独资源的播放,当需要播放多个音频资源可以使用子类AVQueuePlayer

不需要自定义播放器的情况可以使用 MPMoviewPlayerController

参考 www.cnblogs.com/kenshincui/…

初始化

示例:

NSURL *assetURL = [[NSBundle mainBundle] URLForResource:@"waves" withExtension:@"mp4"];
AVAsset *asset = [AVAsset assetWithURL:assetURL];
AVPlayerItem *playeritem = [AVPlayerItem playerItemWithAsset:asset];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayer playerLayerWithPlayer:self.player];
//playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//视频填充模式
[self.view.layer addSubLayer:playerLayer];

对播放进行控制

监听播放状态

监听AVPlayerItem的status属性,播放项开始时status为 AVPlayerItemStatusUnknow,当状态改变为 AVPlayerItemStatusReadyToPlay才可以开始播放,只有处于这个状态时才能获得视频时长等信息。

//监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if(status==AVPlayerStatusReadyToPlay){
            NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
        }
    } 
}

监控网络加载情况属性

监听AVPlayerItem的loadedTimeRanges属性,当loadedTimeRanges的改变时(每缓冲一部分数据就会更新此属性)可以获得本次缓冲加载的视频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。

//监控网络加载情况属性
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
 
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem *playerItem=object;
	if([keyPath isEqualToString:@"loadedTimeRanges"]){
        NSArray *array=playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
        NSLog(@"共缓冲:%.2f",totalBuffer);
//
    }
}

时间监听

AVPlayer提供了两种时间监听方法

定期监听

使用- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

//每秒
CMTime interval = CMTimeMake(1.0, 1.0);              // 1

// Main dispatch queue
dispatch_queue_t queue = dispatch_get_main_queue();                     // 2

// Create callback block for time observer
__weak THPlayerController *weakSelf = self;                             // 3
void (^callback)(CMTime time) = ^(CMTime time) {
    NSTimeInterval currentTime = CMTimeGetSeconds(time);
    NSTimeInterval duration = CMTimeGetSeconds(weakSelf.playerItem.duration);
    NSLog(@"当前已经播放%.2fs.", currentTime);  // 4
};

// Add observer and store pointer for future use
self.timeObserver =                                                     // 5
    [self.player addPeriodicTimeObserverForInterval:interval
                                              queue:queue
                                         usingBlock:callback];

边界时间监听

使用 - (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(void))block;

  • times : CMTime值组成的NSArray数组,定义了需要通知的边界点
  • queue : 调度队列,指定NULL等同设置主队列
  • block : 回调块

监听播放完成通知

注册 AVPlayerItemDidPlayToEndTimeNotification的通知

    //给AVPlayerItem添加播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
   
-(void)playbackFinished:(NSNotification *)notification{
    NSLog(@"视频播放完成.");
}

生成视频缩略图

使用 AVAssetImageGenerator类可以从 AVAsset视频中提取图片。

AVAssetImageGenerator有两个可以检索图片的方法:

  • - (void)generateCGImagesAsynchronouslyForTimes:(NSArray<NSValue *> *)requestedTimes completionHandler:(AVAssetImageGeneratorCompletionHandler)handler;
    • 生成一组图片
  • - (nullable CGImageRef)copyCGImageAtTime:(CMTime)requestedTime actualTime:(nullable CMTime *)actualTime error:(NSError * __nullable * __nullable)outError CF_RETURNS_RETAINED;
    • 在指定时间捕捉图片

生成一组图片示例:

	self.imageGenerator =                                                   // 1
       [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];
   
   //默认情况下捕捉的图片都是原始尺寸,设置(200.0f, 0.0f)可以让图片宽度固定,高度自适应
   self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f);             // 2

   CMTime duration = self.asset.duration;

//生成视频捕捉位置的CMTime集合
   NSMutableArray *times = [NSMutableArray array];                         // 3
   CMTimeValue increment = duration.value / 20;
   CMTimeValue currentValue = 2.0 * duration.timescale;
   while (currentValue <= duration.value) {
       CMTime time = CMTimeMake(currentValue, duration.timescale);
       [times addObject:[NSValue valueWithCMTime:time]];
       currentValue += increment;
   }

   __block NSUInteger imageCount = times.count;                            // 4
   __block NSMutableArray *images = [NSMutableArray array];

   AVAssetImageGeneratorCompletionHandler handler;                         // 5
   
   //requestedTime:请求的时间对应times数组中的值;
   //imageRef :生成的CGImageRef,若该时间点没有图片则为NULL
   //actualTime:图片实际生成的时间
   //result:表示生成图片失败还是成功
   handler = ^(CMTime requestedTime,
               CGImageRef imageRef,
               CMTime actualTime,
               AVAssetImageGeneratorResult result,
               NSError *error) {

       if (result == AVAssetImageGeneratorSucceeded) {                     // 6
           UIImage *image = [UIImage imageWithCGImage:imageRef];
           id thumbnail =
               [THThumbnail thumbnailWithImage:image time:actualTime];
           [images addObject:thumbnail];
       } else {
           NSLog(@"Error: %@", [error localizedDescription]);
       }

       // If the decremented image count is at 0, we're all done.
       if (--imageCount == 0) {                                            // 7
           dispatch_async(dispatch_get_main_queue(), ^{
               NSString *name = THThumbnailsGeneratedNotification;
               NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
               [nc postNotificationName:name object:images];
           });
       }
   };

   [self.imageGenerator generateCGImagesAsynchronouslyForTimes:times       // 8
                                             completionHandler:handler];

显示字幕(视频自带)

显示字幕需要用到AVMediaSelectionGroupAVMediaSelectionOption两个类

  1. 初始化 AVAsset 的时候加上 availableMediaCharacteristicsWithMediaSelectionOptions 属性
    • AVMediaSelectionOption的该属性会返回一个包含字符串的数组,这些字符串表示可用的媒体特性
      • AVMediaCharacteristicLegible (字幕或者隐藏式字幕)
      • AVMediaCharacteristicAudible (音频)
      • AVMediaCharacteristicVisual (字幕)
  2. 请求可用的媒体特性数据后,调用 AVAssetmediaSelectionGroupForMediaCharacteristic:方法为其传递需要的媒体特性,会返回 AVMediaSelectionGroup ,表示包含的备用媒体轨道

      NSString *mc = AVMediaCharacteristicLegible;                            // 1
      AVMediaSelectionGroup *group =
          [self.asset mediaSelectionGroupForMediaCharacteristic:mc];          // 2
      if (group) {
          NSMutableArray *subtitles = [NSMutableArray array];                 // 3
          for (AVMediaSelectionOption *option in group.options) {
              NSLog(@" %@ ",option.displayName);
          }
      }
      
      //输出 ,表示多个字幕轨道
    @"English"
    @"English Forced"
    @"Italian"
    @"Italian Forced"
    @"Portuguese"
    @"Portuguese Forced"
    @"Russian"
    @"Russian Forced"
    
  3. 显示字幕,通过调用 AVPlayerItemselectMediaOption:inMediaSelectionGroup:方法。

    - (void)subtitleSelected:(NSString *)subtitle {
       NSString *mc = AVMediaCharacteristicLegible;
       AVMediaSelectionGroup *group =
           [self.asset mediaSelectionGroupForMediaCharacteristic:mc];          // 1
       BOOL selected = NO;
       for (AVMediaSelectionOption *option in group.options) {
           if ([option.displayName isEqualToString:subtitle]) {
               [self.playerItem selectMediaOption:option                       // 2
                            inMediaSelectionGroup:group];
           }
       }
    
    }