Objective-C AVPlayer播放视频的使用与封装

写在前面
弄了下个人站...防止内容再次被锁定...所有东西都在这里面
welcome~
个人博客

大致效果

不要介意。界面有点丑。。。

AVPlayer封装.gif

界面搭建

看下成员变量就知道我怎么搭建的了,这里我将video播放层的size作为参照量,对所有控件的size按照其video的size宽高进行比例缩放

@interface VideoPlayerView()
@property (nonatomic,copy) NSString *path;                  //播放地址 自动判断文件路径和网址路径
@property (nonatomic,strong) AVPlayer *player;              //播放类
@property (nonatomic,strong) AVPlayerLayer *playerlayer;    //显示区域
@property (nonatomic,strong) UIButton *playBtn;             //播放暂停
@property (nonatomic,strong) UIButton *stopBtn;             // 停止
@property (nonatomic,strong) UIButton *fullScreenBtn;       //全屏
@property (nonatomic,strong) UISlider *playSlider;          //进度选择
@property (nonatomic,strong) UIProgressView *progress;      //进度
@property (nonatomic,strong) UILabel *currentTimeLab;       //当前时间
@property (nonatomic,strong) UILabel *durationLab;          //总时间
@property (nonatomic,strong) UIView *toolView;              //工具栏view
@property (nonatomic,assign) BOOL isFullScreen;             //全屏判断
@property (nonatomic,assign) CGFloat videoHeight;           //video高
@property (nonatomic,assign) CGFloat videoWidth;            //video宽
@end

所有控件使用懒加载 如下

//播放暂停
- (UIButton *)playBtn {
    if (!_playBtn) {
        _playBtn = [[UIButton alloc] initWithFrame:CGRectMake(kLrMargin, kUIy, kBtnWidth, kUIHeight)];
        _playBtn.backgroundColor  =[UIColor greenColor];
        _playBtn.selected = NO;
        _playBtn.enabled = NO;
        _playBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
        [_playBtn setTitle:@"播放" forState:UIControlStateNormal];
        [_playBtn setTitle:@"暂停" forState:UIControlStateSelected];
        [_playBtn addTarget:self action:@selector(play) forControlEvents:UIControlEventTouchUpInside];
    }
    return _playBtn;
}

屏幕适配

由于涉及到屏幕的旋转和适配。我这里没有使用第三方框架来做约束,而是使用最基本的按百分比设置frame。旋转屏幕时通过调用本类- (void)resetFrame:(CGSize)size;方法来重设frame。所以需要重设frame的控件在懒加载中设置frame,调用时即刷新frame。

  • 先看下初始化 对video的size设置是时始终用最小的边来确定高度,宽度与屏幕当前宽度相当
//初始化
- (instancetype)initWithFrame:(CGRect)frame andPath:(NSString*)path {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.path = path;
        CGFloat width = [UIScreen mainScreen].bounds.size.width;
        CGFloat height = [UIScreen mainScreen].bounds.size.height;
        self.videoHeight = height > width ? width * 0.6 : height * 0.6;
        self.videoWidth = [UIScreen mainScreen].bounds.size.width-2*kLrMargin;
        [self.layer addSublayer:self.playerlayer];
        [self addSubview:self.toolView];
    }
    return self;
}
  • 屏幕旋转时做一些事
//屏幕旋转时触发 这里写在父类中
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {    
    [self.videoView resetFrame:size];
}
//旋转屏幕重设frame
- (void)resetFrame:(CGSize)size {
    CGFloat width = size.width;
    CGFloat height = size.height;
    self.videoHeight = height > width ? width * 0.6 : height * 0.6;
    self.videoWidth = size.width - 2 * kLrMargin;
    if (self.isFullScreen) {
        //全屏时旋转
        [self setPlayerWithPosition:CGPointZero andSize:size];
    } else {
        //普通旋转
        [self setPlayerWithPosition:CGPointMake(kLrMargin, kTopMargin) andSize:CGSizeMake(self.videoWidth, self.videoHeight)];
        //刷新frame
        [self toolView];
        [self playSlider];
        [self progress];
    }    
}
  • 懒加载刷新frame
//进度懒加载 调用时只刷新frame
- (UIProgressView *)progress {
    if (!_progress) {
        _progress = [[UIProgressView alloc] init];
        _progress.progress = 0;
    }
    _progress.frame = CGRectMake(2, self.playSlider.frame.size.height/2, self.playSlider.frame.size.width-2-2, kUIHeight);
    return _progress;
}

AVPlayer的基本操作

基本操作包括 播放 、暂停、 停止、 播放指定位置、缓存进度
播放网络地址时 在info.plist中添加 App Transport Security Settings字典中添加Allow Arbitrary Loads元素 值为YES

添加项.png

使用AVPlayer播放视频就必须用到AVPlayerlayer用来显示播放视图。

//加载显示层
- (AVPlayerLayer*)playerlayer {
    if (!_playerlayer) {
        _playerlayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
        _playerlayer.bounds = CGRectMake(0, 0, self.videoWidth, self.videoHeight);
        _playerlayer.anchorPoint = CGPointMake(0, 0);
        _playerlayer.position = CGPointMake(kLrMargin, kTopMargin);
        _playerlayer.backgroundColor = [UIColor blackColor].CGColor;
    }
    return _playerlayer;
}

//加载播放类
- (AVPlayer *)player {
    if (!_player) {
        _player = [AVPlayer playerWithURL:[self getUrlPath:self.path]];
        //kvo注册
        [self addObservers];
    }
    return _player;
}
  • 使用KVO对状态和缓存进行检测,添加KVO时养成习惯写好移除操作
//注册kvo
- (void)addObservers{
    [self.player.currentItem addObserver:self forKeyPath:kItemStatus options:NSKeyValueObservingOptionNew context:nil];
    [self.player.currentItem addObserver:self forKeyPath:kItemLoadedTimeRanges options:NSKeyValueObservingOptionNew context:nil];
}

//移除kvo
- (void)dealloc {
    [self.player.currentItem removeObserver:self forKeyPath:kItemStatus];
    [self.player.currentItem removeObserver:self forKeyPath:kItemLoadedTimeRanges];
}

//kvo回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:kItemStatus]) {
        AVPlayerItem *item = object;
        if (item.status == AVPlayerItemStatusReadyToPlay) {
            //准备播放
            [self readyToplayWithItem:item];
        }else if (item.status == AVPlayerItemStatusUnknown) {
            //播放失败
           //这里写个alertView提示下就行
        }else if (item.status == AVPlayerItemStatusFailed) {
            //播放失败
            //同上
        }
    } else if ([keyPath isEqualToString:kItemLoadedTimeRanges]) {
        AVPlayerItem *item = object;
        [self getLoadedTimeRanges:item];
    }
}
  • 基础功能
//播放 暂停
- (void)play {
    if (self.playBtn.selected) {
        self.playBtn.selected = NO;
        [self.player pause];
    } else {
        self.playBtn.selected = YES;
        [self.player play];
        [self timerStar];
    }
}

//停止
- (void)stop {
    [self.player pause];
    [self.player seekToTime:CMTimeMake(0, 1)];
    self.playBtn.selected = NO;
}
//全屏
- (void)fullScreen {
    self.toolView.hidden = YES;
    self.isFullScreen = YES;
    self.backgroundColor = [UIColor blackColor];
    self.playerlayer.bounds = [UIScreen mainScreen].bounds;
    self.playerlayer.anchorPoint = CGPointMake(0, 0);
    self.playerlayer.position = CGPointMake(0, 0);
}

//播放指定位置
- (void)playCurrentVideo {
    self.playBtn.selected = YES;
    NSTimeInterval second = self.playSlider.value;
    [self.player.currentItem seekToTime:CMTimeMake(second,1)];
    [self.player play];
    [self timerStar];
}
  • 具体操作
    • 包括格式化时间
    • 格式化路径
    • 播放准备
    • 缓存计算
    • 触摸关闭全屏
    • 设置video的大小位置
//设置video的frame
- (void)setPlayerWithPosition:(CGPoint)position andSize:(CGSize)size {
    self.playerlayer.anchorPoint = CGPointMake(0, 0);
    self.playerlayer.position = position;
    self.playerlayer.bounds = CGRectMake(0, 0, size.width, size.height);
}

//准备播放
- (void)readyToplayWithItem:(AVPlayerItem*)item {
    self.playBtn.enabled = YES;
    long long durationSecond = item.duration.value / item.duration.timescale;
    self.durationLab.text = [NSString stringWithFormat:@" / %@",[self getFormatDate:durationSecond]];
    self.playSlider.maximumValue = durationSecond;
    self.playSlider.minimumValue = 0;
    [self.playSlider addTarget:self action:@selector(playCurrentVideo) forControlEvents:UIControlEventValueChanged];

}

//获得缓存
- (void)getLoadedTimeRanges:(AVPlayerItem*)item {
    NSValue *value = [item.loadedTimeRanges lastObject];
    CMTimeRange range = [value CMTimeRangeValue];
    long long cacheSecond = range.start.value/range.start.timescale + range.duration.value/range.duration.timescale;
    long long currentSecond = item.currentTime.value / item.currentTime.timescale;
    self.progress.progress = (currentSecond + cacheSecond) * 0.1;
  
}


//格式化时间
- (NSString*)getFormatDate:(NSTimeInterval)time {
    int seconds = (int)time % 60;
    int minutes = (int)(time / 60) % 60;
    int hours = (int)time / 3600;
    return [NSString stringWithFormat:@"%02d:%02d:%02d",hours,minutes,seconds];
}

//格式化url路径
- (NSURL*)getUrlPath:(NSString*)path {
    NSURL *url;
    if ([self.path containsString:@"http"]) {
        url = [NSURL URLWithString:self.path];
    } else {
        url = [NSURL fileURLWithPath:self.path];
    }
    return url;
}

//开启定时
- (void)timerStar {
    //定时回调
    __weak typeof(self) weakSelf = self;
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) {
        long long current = weakSelf.player.currentItem.currentTime.value / weakSelf.player.currentItem.currentTime.timescale;
        weakSelf.playSlider.value = current;
        NSString *currentFormat = [weakSelf getFormatDate:current];
        weakSelf.currentTimeLab.text = [NSString stringWithFormat:@"%@",currentFormat];
    }];
    
}


//触摸关闭全屏
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    if (self.isFullScreen) {
        self.toolView.hidden = NO;
        self.backgroundColor = [UIColor clearColor];
        [self setPlayerWithPosition:CGPointMake(kLrMargin, kTopMargin) andSize:CGSizeMake(self.videoWidth, self.videoHeight)];
        
        [self toolView];
        [self playSlider];
        [self progress];
        self.isFullScreen = NO;
    }
}

这样一个简单AVPlayer的封装就做好了

Demo地址

https://github.com/gongxiaokai/AVPlayerDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容