AVFoundation-实现一个简易的视频播放器

Demo

核心类

AVAsset--视频资源对象,需要传入本地或网络的URL

AVPlayerItem--获取视频时长及各种状态,需要传入创建好的AVAsset对象

AVPlayer--视频播放类,需要传入创建好的AVPlayerItem对象

AVPlayerLayer--视频显示layer,可以替换或插入到视图树中

AVAssetImageGenerator--获取视频某一帧的缩略图,可以获取某个时间的,也可以批量获取

第一步:初始化

1.创建AVAsset
_asset = [AVAsset assetWithURL:assetURL];

2.创建AVPlayerItem,可以传入所需要监听的属性,例如视频时长duration
NSArray *keys = @[
        @"tracks",
        @"duration",
        @"commonMetadata",
        @"availableMediaCharacteristicsWithMediaSelectionOptions"
    ];
self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset automaticallyLoadedAssetKeys:keys];

3.通过KVO给AVPlayerItem添加视频播放状态的监听
[self.playerItem addObserver:self forKeyPath:@"status" options:0 context:&PlayerItemStatusContext];   

4.创建AVPlayer
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];

5.创建显示视频用的AVPlayerLayer,并设置之前创建好的AVPlayer为视频播放器,插入到当前视图树中
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:player];
复制代码

第二步:首次播放

[self.player play];

在第一步的KVO监听中监听到AVPlayerItem的播放状态为AVPlayerItemStatusReadyToPlay,调用AVPlayer的play方法开始播放,这里还写了其它方法,例如监听当前播放时长,批量获取缩略图等,后面会放具体实现

if (context == &PlayerItemStatusContext) {

        dispatch_async(dispatch_get_main_queue(), ^{
            //移除监听
            [self.playerItem removeObserver:self forKeyPath:@"status"];

            if (self.playerItem.status == AVPlayerItemStatusReadyToPlay) {
                //添加视频当前播放时间的监听
                [self addPlayerItemTimeOberver];
                //视频总时长
                CMTime duration = self.playerItem.duration;
                [self.delegate playerDidChangeTime:CMTimeGetSeconds(kCMTimeZero) withAssertDuration:CMTimeGetSeconds(duration)];
                //播放
                [self.player play];
                //批量获取缩略图
                [self generateThumbnails];
            }
        });
    }
复制代码

第三步:暂停

[self.player pause];

第四步:暂停后继续播放,如果设置了倍速,暂停后会重置,这里需要重新设置一下速率,而且一定要放在play方法之后,不然不会生效

[self.player play]; self.player.rate = _playRate

第五步:获取当前播放时间,在初始化的KVO监听中调用AVPlayer的如下方法

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

- (void)addPlayerItemTimeOberver
{

    CMTime interval = CMTimeMakeWithSeconds(0.5f, NSEC_PER_SEC);

    dispatch_queue_t queue = dispatch_get_main_queue();

    __weak typeof(self)weakSelf = self;

    self.timeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:queue usingBlock:^(CMTime time) {

        NSTimeInterval currentTime = CMTimeGetSeconds(time);

        NSTimeInterval duration = CMTimeGetSeconds(weakSelf.playerItem.duration);

        [weakSelf.delegate playerDidChangeTime:currentTime withAssertDuration:duration];

    }];

}
复制代码

第六步:速率播放,设置AVPlayer的rate属性

self.player.rate = playRate

第七步:移动到某个时间点进行播放,这里有一个细节,当手指按下的时候,需要停止播放并移除播放时长通知,当手指离开,继续播放与监听当前播放时长

[self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];

//手指按下
- (void)progressSliderDidStartChange
{

    [self.player pause];

    [self.player removeTimeObserver:self.timeObserver];

    self.timeObserver = nil;

}
//开始移动
- (void)progressSliderSeekToTime:(NSTimeInterval)time
{

    [self.playerItem cancelPendingSeeks];

    [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];

}
//手指离开
- (void)progressSliderDidEnd
{

    [self addPlayerItemTimeOberver];

    [self.player play];

    self.player.rate = _playRate;

}
复制代码

第八步:批量获取缩略图,创建可视化进度条

创建AVAssetImageGenerator,传入AVAsset类型

AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];

批量获取缩略图方法

- (void)generateCGImagesAsynchronouslyForTimes:(NSArray<NSValue *> *)requestedTimes completionHandler:(AVAssetImageGeneratorCompletionHandler)handler

获取某个时间点的缩略图

- (nullable CGImageRef)copyCGImageAtTime:(CMTime)requestedTime actualTime:(nullable CMTime *)actualTime error:(NSError * _Nullable * _Nullable)outError

- (void)generateThumbnails
{

    AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];

    imageGenerator.maximumSize = CGSizeMake(200, 0);

    CMTime duration = self.asset.duration;

    NSMutableArray *times = [NSMutableArray array];

    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 NSInteger imageCount = times.count;

    __block NSMutableArray *images = [NSMutableArray array];

    [imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef  _Nullable imageRef, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {

        if (result == AVAssetImageGeneratorSucceeded) {

            UIImage *image = [UIImage imageWithCGImage:imageRef];

            QXThumbnail *thumbnail = [QXThumbnail thumbilWithImage:image time:actualTime];

            [images addObject:thumbnail];

        } else {

            NSLog(@"Error:%@",error.localizedDescription);

        }

        if (--imageCount == 0) {

            dispatch_async(dispatch_get_main_queue(), ^{

                [self.delegate playerDidGenerateThumbnails:[images copy]];

            });

        }

    }];

}
复制代码

到此,一个简易的视频播放器就完成了,当然了,还有很多的细节需要去处理,这个就需要小伙伴们自己去捉摸了,学习音视频一定是一个漫长又有趣的路,加油!

猜你喜欢

转载自juejin.im/post/7085551882071965709
今日推荐