iOS 实用音视频从入门到放弃IOS学习心得

iOS 基于AVPlayer的简易播放器

2019-07-29  本文已影响0人  邓布利多教授

简单介绍一下,AVPlayer是基于AVFoundation框架的一个类,很接近底层,灵活性强,方便自定义各种需求,使用之前需要先导入#import <AVKit/AVKit.h>

这个简易播放器非常简单,是我拿了练手玩的,功能只包括播放、暂停、滑动播放、显示缓冲进度。

好,我的需求就这么简单,我就是想要自己写一个这样的播放器,至于其他的更复杂更好的用户体验的功能,暂时不考虑。目标明确了,开工。

1、工具条

这个工具条上面要包括:
1、播放(暂停)按钮的UIButton
2、可以拖拽的进度条UISlider
3、显示当前播放时间和显示视频总时长的UILabel
4、显示缓冲进度的UIProgressView

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol VideoPlayerToolsViewDelegate <NSObject>

-(void)playButtonWithStates:(BOOL)state;

@end

@interface VideoPlayerToolsView : UIView

@property (nonatomic, strong) UIButton *bCheck;//播放暂停按钮
@property (nonatomic, strong) UISlider *progressSr;//进度条
@property (nonatomic, strong) UIProgressView *bufferPV;//缓冲条
@property (nonatomic, strong) UILabel *lTime;//时间进度和总时长

@property (nonatomic, weak) id<VideoPlayerToolsViewDelegate> delegate;

@end

NS_ASSUME_NONNULL_END
#import "VideoPlayerToolsView.h"

@interface VideoPlayerToolsView ()

@end

@implementation VideoPlayerToolsView

-(instancetype)initWithFrame:(CGRect)frame{
    
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
        [self createUI];//创建UI
    }
    return self;
    
}

#pragma mark - 创建UI
-(void)createUI{
    [self addSubview:self.bCheck];//开始暂停按钮
    [self addSubview:self.bufferPV];//缓冲条
    [self addSubview:self.progressSr];//创建进度条
    [self addSubview:self.lTime];//视频时间
}

#pragma mark - 视频时间
-(UILabel *)lTime{
    
    if (!_lTime) {
        _lTime = [UILabel new];
        _lTime.frame = CGRectMake(CGRectGetMaxX(_progressSr.frame) + 20, 0, self.frame.size.width - CGRectGetWidth(_progressSr.frame) - 40 - CGRectGetWidth(_bCheck.frame), self.frame.size.height);
        _lTime.text = @"00:00/00:00";
        _lTime.textColor = [UIColor whiteColor];
        _lTime.textAlignment = NSTextAlignmentCenter;
        _lTime.font = [UIFont systemFontOfSize:12];
        _lTime.adjustsFontSizeToFitWidth = YES;
    }
    return _lTime;
    
}

#pragma mark - 创建进度条
-(UISlider *)progressSr{
    
    if (!_progressSr) {
        _progressSr = [UISlider new];
        _progressSr.frame = CGRectMake(CGRectGetMinX(_bufferPV.frame) - 2, CGRectGetMidY(_bufferPV.frame) - 10, CGRectGetWidth(_bufferPV.frame) - 4, 20);
        _progressSr.maximumTrackTintColor = [UIColor clearColor];
        _progressSr.minimumTrackTintColor = [UIColor whiteColor];
        [_progressSr setThumbImage:[UIImage imageNamed:@"point"] forState:0];
    }
    return _progressSr;
    
}

#pragma mark - 缓冲条
-(UIProgressView *)bufferPV{
    
    if (!_bufferPV) {
        _bufferPV = [UIProgressView new];
        _bufferPV.frame = CGRectMake(CGRectGetMaxX(_bCheck.frame) + 20, CGRectGetMidY(_bCheck.frame) - 2, 200, 4);
        _bufferPV.trackTintColor = [UIColor grayColor];
        _bufferPV.progressTintColor = [UIColor cyanColor];
    }
    return _bufferPV;
    
}

#pragma mark - 开始暂停按钮
-(UIButton *)bCheck{
    
    if (!_bCheck) {
        _bCheck = [UIButton new];
        _bCheck.frame = CGRectMake(0, 0, self.frame.size.height, self.frame.size.height);
        [_bCheck setImage:[UIImage imageNamed:@"pause"] forState:0];
        [_bCheck addTarget:self action:@selector(btnCheckSelect:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _bCheck;
    
}

-(void)btnCheckSelect:(UIButton *)sender{
    
    sender.selected = !sender.isSelected;
    
    if (sender.selected) {
        [_bCheck setImage:[UIImage imageNamed:@"play"] forState:0];
    }else{
        [_bCheck setImage:[UIImage imageNamed:@"pause"] forState:0];
    }
    
    if ([_delegate respondsToSelector:@selector(playButtonWithStates:)]) {
        [_delegate playButtonWithStates:sender.selected];
    }
    
}

@end

2、网络状态监听器

这个网络监听器是网上找到的,本来想把原文地址留下来的,结果忘记了,在这里表示抱歉,至于这个工具怎么实现的,实话实说,我看不懂,我就知道它就是我想要的东西,是不是很尴尬……那也没办法,能力有限!这个工具的使用我单独拿出去写了个文章,这里不再重复黏贴代码了。

3、AVPlayer播放器

这里是重头戏了,首先,要知道AVPlayer是怎么用的。
AVPlayer是个播放器,但是呢,它又不能直接播放视频,它需要和AVPlayerLayer配合着使用,并且需要把AVPlayerLayer添加到视图的layer上才行,比如:[self.layer addSublayer:self.playerLayer];

AVPlayer加载视频地址的方式是什么呢?我得需要知道,查看api,control+command+鼠标左键,进去瞅瞅,发现系统有提供以下几种方式:

+ (instancetype)playerWithURL:(NSURL *)URL;
+ (instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item;
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithPlayerItem:(nullable AVPlayerItem *)item;

那么问题来了,上面的四种方法里面有两个是用AVPlayerItem初始化的,这个是什么东西。再继续看api,什么东西啊,乱七八糟一大推,于是乎,不看了,看看前辈们是咋玩的,后来发现,前辈们用了一个叫做:replaceCurrentItemWithPlayerItem:的方法给AVPlayer添加播放地址,从字面上的意思我的理解是:用PlayerItem替换当前的item??
完整代码是这样写的:

AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@"视频地址"]];
[self.player replaceCurrentItemWithPlayerItem:item];

然后AVPlayer怎么添加到AVPlayerLayer上呢?代码如下:

_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

好,这里假设各个控件的初始化啊布局什么的都完事了,接下来要考虑的是控件之间相互关联显示的问题了。

NSTimeInterval totalTime = CMTimeGetSeconds(self.player.currentItem.duration);//总时长
NSTimeInterval currentTime = CMTimeGetSeconds(self.player.currentTime);//当前时间进度

春夏秋冬,年复一年,日复一日,不知道经过了多少个岁月……

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface VideoPlayerContainerView : UIView

@property (nonatomic, strong) NSString *urlVideo;

-(void)dealloc;

@end

NS_ASSUME_NONNULL_END
#import "VideoPlayerContainerView.h"
#import <AVKit/AVKit.h>

#import "NetworkSpeedMonitor.h"
#import "VideoPlayerToolsView.h"

@interface VideoPlayerContainerView ()<VideoPlayerToolsViewDelegate>

@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@property (nonatomic, strong) NetworkSpeedMonitor *speedMonitor;//网速监听
@property (nonatomic, strong) UILabel *speedTextLabel;//显示网速Label
@property (nonatomic, strong) VideoPlayerToolsView *vpToolsView;//工具条

@property (nonatomic, strong) id playbackObserver;
@property (nonatomic) BOOL buffered;//是否缓冲完毕

@end

@implementation VideoPlayerContainerView

//设置播放地址
-(void)setUrlVideo:(NSString *)urlVideo{
    
    [self.player seekToTime:CMTimeMakeWithSeconds(0, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    [self.player play];//开始播放视频
    
    [self.speedMonitor startNetworkSpeedMonitor];//开始监听网速
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkSpeedChanged:) name:NetworkDownloadSpeedNotificationKey object:nil];
    
    AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:urlVideo]];
    [self vpc_addObserverToPlayerItem:item];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.player replaceCurrentItemWithPlayerItem:item];
        [self vpc_playerItemAddNotification];
    });
    
}

-(instancetype)initWithFrame:(CGRect)frame{
    
    self = [super initWithFrame:frame];
    if (self) {
        
        self.backgroundColor = [UIColor groupTableViewBackgroundColor];
        [self.layer addSublayer:self.playerLayer];
        [self addSubview:self.speedTextLabel];
        [self addSubview:self.vpToolsView];
        
    }
    return self;
    
}

- (void)networkSpeedChanged:(NSNotification *)sender {
    NSString *downloadSpped = [sender.userInfo objectForKey:NetworkSpeedNotificationKey];
    self.speedTextLabel.text = downloadSpped;
}

#pragma mark - 工具条
-(VideoPlayerToolsView *)vpToolsView{
    
    if (!_vpToolsView) {
        
        _vpToolsView = [[VideoPlayerToolsView alloc]initWithFrame:CGRectMake(0, CGRectGetHeight(self.frame) - 40, CGRectGetWidth(self.frame), 40)];
        _vpToolsView.delegate = self;
        
        [_vpToolsView.progressSr addTarget:self action:@selector(vpc_sliderTouchBegin:) forControlEvents:UIControlEventTouchDown];
        [_vpToolsView.progressSr addTarget:self action:@selector(vpc_sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
        [_vpToolsView.progressSr addTarget:self action:@selector(vpc_sliderTouchEnd:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _vpToolsView;
    
}

-(void)playButtonWithStates:(BOOL)state{
    
    if (state) {
        [self.player pause];
    }else{
        [self.player play];
    }
    
}

- (void)vpc_sliderTouchBegin:(UISlider *)sender {
    [self.player pause];
}

- (void)vpc_sliderValueChanged:(UISlider *)sender {
    
    NSTimeInterval currentTime = CMTimeGetSeconds(self.player.currentItem.duration) * _vpToolsView.progressSr.value;
    NSInteger currentMin = currentTime / 60;
    NSInteger currentSec = (NSInteger)currentTime % 60;
    _vpToolsView.lTime.text = [NSString stringWithFormat:@"%02ld:%02ld",currentMin,currentSec];
    
}

- (void)vpc_sliderTouchEnd:(UISlider *)sender {
    
    NSTimeInterval slideTime = CMTimeGetSeconds(self.player.currentItem.duration) * _vpToolsView.progressSr.value;
    if (slideTime == CMTimeGetSeconds(self.player.currentItem.duration)) {
        slideTime -= 0.5;
    }
    [self.player seekToTime:CMTimeMakeWithSeconds(slideTime, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    [self.player play];
    
}

#pragma mark - 网速监听器
- (NetworkSpeedMonitor *)speedMonitor {
    if (!_speedMonitor) {
        _speedMonitor = [[NetworkSpeedMonitor alloc] init];
    }
    return _speedMonitor;
}

#pragma mark - 显示网速Label
- (UILabel *)speedTextLabel {
    
    if (!_speedTextLabel) {
        _speedTextLabel = [UILabel new];
        _speedTextLabel.frame = CGRectMake(0, 0, self.frame.size.width, 20);
        _speedTextLabel.textColor = [UIColor whiteColor];
        _speedTextLabel.font = [UIFont systemFontOfSize:12.0];
        _speedTextLabel.textAlignment = NSTextAlignmentCenter;
        _speedTextLabel.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
    }
    return _speedTextLabel;
    
}

#pragma mark - AVPlayer
-(AVPlayer *)player{
    
    if (!_player) {
        _player = [[AVPlayer alloc] init];
        __weak typeof(self) weakSelf = self;
        // 每秒回调一次
        self.playbackObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) {
            [weakSelf vpc_setTimeLabel];
            NSTimeInterval totalTime = CMTimeGetSeconds(weakSelf.player.currentItem.duration);//总时长
            NSTimeInterval currentTime = time.value / time.timescale;//当前时间进度
            weakSelf.vpToolsView.progressSr.value = currentTime / totalTime;
        }];
    }
    return _player;
    
}

#pragma mark - AVPlayerLayer
-(AVPlayerLayer *)playerLayer{
    
    if (!_playerLayer) {
        _playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
        _playerLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
    }
    return _playerLayer;
    
}

#pragma mark ---------华丽的分割线---------

#pragma mark - lTime
- (void)vpc_setTimeLabel {
    
    NSTimeInterval totalTime = CMTimeGetSeconds(self.player.currentItem.duration);//总时长
    NSTimeInterval currentTime = CMTimeGetSeconds(self.player.currentTime);//当前时间进度
    
    // 切换视频源时totalTime/currentTime的值会出现nan导致时间错乱
    if (!(totalTime >= 0) || !(currentTime >= 0)) {
        totalTime = 0;
        currentTime = 0;
    }
    
    NSInteger totalMin = totalTime / 60;
    NSInteger totalSec = (NSInteger)totalTime % 60;
    NSString *totalTimeStr = [NSString stringWithFormat:@"%02ld:%02ld",totalMin,totalSec];
    
    NSInteger currentMin = currentTime / 60;
    NSInteger currentSec = (NSInteger)currentTime % 60;
    NSString *currentTimeStr = [NSString stringWithFormat:@"%02ld:%02ld",currentMin,currentSec];
    
    _vpToolsView.lTime.text = [NSString stringWithFormat:@"%@/%@",currentTimeStr,totalTimeStr];
    
}

#pragma mark - 观察者
- (void)vpc_playerItemAddNotification {
    // 播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(vpc_playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}

-(void)vpc_playbackFinished:(NSNotification *)noti{
    [self.player pause];
}

- (void)vpc_addObserverToPlayerItem:(AVPlayerItem *)playerItem {
    // 监听播放状态
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // 监听缓冲进度
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)vpc_playerItemRemoveNotification {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}

- (void)vpc_playerItemRemoveObserver {
    [self.player.currentItem removeObserver:self forKeyPath:@"status"];
    [self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if (status == AVPlayerStatusReadyToPlay) {
            [self vpc_setTimeLabel];
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        NSArray *array = self.player.currentItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
        NSTimeInterval startSeconds = CMTimeGetSeconds(timeRange.start);//本次缓冲起始时间
        NSTimeInterval durationSeconds = CMTimeGetSeconds(timeRange.duration);//缓冲时间
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
        float totalTime = CMTimeGetSeconds(self.player.currentItem.duration);//视频总长度
        float progress = totalBuffer/totalTime;//缓冲进度
        NSLog(@"progress = %lf",progress);
        
        //如果缓冲完了,拖动进度条不需要重新显示缓冲条
        if (!self.buffered) {
            if (progress == 1.0) {
                self.buffered = YES;
            }
            [self.vpToolsView.bufferPV setProgress:progress];
        }
        NSLog(@"yon = %@",self.buffered ? @"yes" : @"no");
    }
}

- (void)dealloc {
    
    [self.speedMonitor stopNetworkSpeedMonitor];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NetworkDownloadSpeedNotificationKey object:nil];
    
    [self.player removeTimeObserver:self.playbackObserver];
    [self vpc_playerItemRemoveObserver];
    [self.player replaceCurrentItemWithPlayerItem:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
}

@end

4、整合

最后全部封装完了之后,调用的时候,只需要引入头文件#import "VideoPlayerContainerView.h",在需要用的地方,直接声明,传值就ok了

VideoPlayerContainerView *vpcView = [[VideoPlayerContainerView alloc]initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 200)];
[self.view addSubview:vpcView];
    
vpcView.urlVideo = @"https://www.apple.com/105/media/cn/researchkit/2016/a63aa7d4_e6fd_483f_a59d_d962016c8093/films/carekit/researchkit-carekit-cn-20160321_848x480.mp4";
效果图.gif

5、全剧终

上一篇下一篇

猜你喜欢

热点阅读