AVFoundation - Implementing a Simple Video Player

Demo

core class

AVAsset--Video resource object, you need to pass in a local or network URL

AVPlayerItem--Get the video duration and various states, you need to pass in the created AVAsset object

AVPlayer--Video playback class, you need to pass in the created AVPlayerItem object

AVPlayerLayer--Video display layer, which can be replaced or inserted into the view tree

AVAssetImageGenerator--Get the thumbnail of a certain frame of the video, you can get it at a certain time, or you can get it in batches

Step 1: Initialize

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];
复制代码

Step 2: Play for the first time

[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];
            }
        });
    }
复制代码

Step 3: Pause

[self.player pause];

Step 4: Continue playing after pausing . If the double speed is set, it will be reset after pausing. The speed needs to be reset here, and it must be placed after the play method, otherwise it will not take effect.

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

Step 5: Get the current playback time and call the following method of AVPlayer in the initialized KVO monitor

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

    }];

}
复制代码

Step 6: Rate playback , set the rate attribute of AVPlayer

self.player.rate = playRate

Step 7: Move to a certain point of time to play , here is a detail, when the finger is pressed, you need to stop the playback and remove the playback duration notification, when the finger leaves, continue to play and monitor the current playback duration

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

}
复制代码

Step 8: Get thumbnails in batches and create a visual progress bar

Create AVAssetImageGenerator, pass in the AVAsset type

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

Batch get thumbnail method

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

Get a thumbnail at a point in time

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

            });

        }

    }];

}
复制代码

At this point, a simple video player is completed. Of course, there are still many details to be dealt with. This needs to be figured out by the friends. Learning audio and video must be a long and interesting road, come on!

Guess you like

Origin juejin.im/post/7085551882071965709