iOS 教你使用MP、AVPlayer、AVPlayerVC构建一个完整的视频播放器

前言

标题必须要浮夸!要感觉像是一个大新闻。长者如是说。
其实是前几天去面试的时候,被要求说必须做过视频播放相关项目。有点闹心之余,就花了点时间在家写了一个简单播放器,基本实现了主流播放器的大致功能。之前项目没有需求用到过视频播放,所以写的时候难免会遇到一些坑,花了一些时间解决。

苹果在视频播放方面提供了多个框架供我们选择使用。分别为:

  • 基于mediaPlayer类库的MPMediaPlayerController(iOS9后遭到废弃,被AVPlayerViewController所替代)
  • 基于AVFounditon类库的AVPlayer
  • 基于AVKit类库的AVPlayerViewController(iOS8后才可使用)

正文

AVPlayer与MPMediaPlayerController比较:

  • AVplayer有更多的灵活性,当然,也需要你去自定义构建UI。还有一大优势,例如其扩展的AVQueuePlayer,可以实现视频无缝队列播放、多视频同时播放、视频转换、编解码等功能。
  • MPMediaPlayerController实际上是基于AVPlayer的简单UI封装,对于一般的播放要求,几行代码就可实现,省心省事。
    因为MPMediaPlayerController是对AVPlayer进行的单例封装,所以不能进行多视频播放
播放器Demo(全屏)已实现功能点:
  • push到播放器页面,横屏显示。
  • 单机隐藏or显示上方标题栏与下方操作栏。
  • 呼出右侧设置栏。
  • 视频播放操作与进度条设置。
  • 在屏幕上左右拖动,进行视频快进与快退。
  • 在屏幕左侧上下拖动,进行亮度调整。
  • 在屏幕右侧上下拖动,进行音量调整。
想到但是暂未实现的功能点:(大多为优化或与业务相关)
  • 屏幕或进度条拖动快进操作时,添加提示框进行快进时间的实时提示。
  • 用户无操作两三秒之后自动隐藏上下View。
  • 视频清晰度调整按钮。(更换视频源)
  • 操作加锁按钮。(加锁后未进行解锁操作之前不可进行操作)
  • 弹幕相关。
  • 用户允许横屏状态下,横屏竖屏自动进行页面切换与动画效果等。
  • 网络视频的缓存、下载等。
  • 软硬解码模式切换等。

笔者Demo选择使用了AVPlayer进行视频播放器的构建。由于UI的代码实现,加上略蛋疼的逻辑代码,播放器页面的代码量达到400多行,之后有时间的话会再进行优化。这里只贴出部分代码,想要查看或借鉴完整Demo,可以到本人github去下载。

使用AVPlayer构建播放器

1.导入头文件

#import <AVFoundation/AVFoundation.h>

2.其实没什么可说的,很简单,先初始化AVPlayer,然后添加到AVPlayerLayer,最后将其添加到视图的layer层。

#pragma mark - Demo中此视图的属性

#define TopViewHeight 55
#define BottomViewHeight 72
#define mainWidth [UIScreen mainScreen].bounds.size.width
#define mainHeight [UIScreen mainScreen].bounds.size.height

//上层建筑
@property (nonatomic,strong)UIView *topView;
@property (nonatomic,strong)UIButton *backBtn;
@property (nonatomic,strong)UILabel *titleLabel;
@property (nonatomic,strong)UIButton *settingsBtn;

//经济基础
@property (nonatomic,strong)UIView *bottomView;
@property (nonatomic,strong)UIButton *playBtn;
@property (nonatomic,strong)UILabel *textLabel;
@property (nonatomic,assign)BOOL isPlay;
@property (nonatomic,strong)UISlider *movieProgressSlider;//进度条
@property (nonatomic,assign)CGFloat ProgressBeginToMove;
@property (nonatomic,assign)CGFloat totalMovieDuration;//视频总时间

//核心躯干
@property (nonatomic,strong)AVPlayer *player;

//神之右手
@property (nonatomic,strong)UIView *settingsView;
@property (nonatomic,strong)UIView *rightView;
@property (nonatomic,strong)UIButton *setTestBtn;

//touch evens
@property (nonatomic,assign)BOOL isShowView;
@property (nonatomic,assign)BOOL isSettingsViewShow;
@property (nonatomic,assign)BOOL isSlideOrClick;

@property (nonatomic,strong)UISlider *volumeViewSlider;
@property (nonatomic,assign)float systemVolume;//系统音量值
@property (nonatomic,assign)float systemBrightness;//系统亮度
@property (nonatomic,assign)CGPoint startPoint;//起始位置坐标

@property (nonatomic,assign)BOOL isTouchBeganLeft;//起始位置方向
@property (nonatomic,copy)NSString *isSlideDirection;//滑动方向
@property (nonatomic,assign)float startProgress;//起始进度条

#pragma mark - 播放器躯干
- (void)createAvPlayer{
    //设置静音状态也可播放声音
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
    
    CGRect playerFrame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
    
    AVURLAsset *asset = [AVURLAsset assetWithURL: _url];
    Float64 duration = CMTimeGetSeconds(asset.duration);
    //获取视频总时长
    _totalMovieDuration = duration;
    
    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset: asset];

    _player = [[AVPlayer alloc]initWithPlayerItem:playerItem];
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
    playerLayer.frame = playerFrame;
    playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.view.layer addSublayer:playerLayer];
    //需要设置自动播放的直接play即可
    //[_player play];
}
屏幕单击手势与视频快进

屏幕单击
1.符合条件的情况下(手指按下后离开屏幕,并且没有拖动)通过BOOL值判断,隐藏或显示上下View

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint point = [[touches anyObject] locationInView:self.view];
     if (_isShowView) {
        //上下View为显示状态,此时点击上下View直接return
        if ((point.y>CGRectGetMinY(self.topView.frame)&&point.y< CGRectGetMaxY(self.topView.frame))||(point.y<CGRectGetMaxY(self.bottomView.frame)&&point.y>CGRectGetMinY(self.bottomView.frame))) {
            return
         }
        _isShowView = NO;
        [UIView animateWithDuration:0.5 animations:^{
            _topView.alpha = 0;
            _bottomView.alpha = 0;
        }];
    }else{
        _isShowView = YES;
        [UIView animateWithDuration:0.5 animations:^{
            _topView.alpha = 1;
            _bottomView.alpha = 1;
        }];
    }
}

2.右侧View显示的状态下,点击屏幕左半空白区域,隐藏右侧View

if (_isSettingsViewShow) {
    if (point.x>CGRectGetMinX(_rightView.frame)&&point.x< CGRectGetMaxX(_rightView.frame)) {
        return;
    }
    _settingsView.alpha = 0;
    _isSettingsViewShow = NO;
}

拖动快进
1.计算后得出拖动方向为横向拖动。

CGPoint location = [[touches anyObject] locationInView:self.view];
CGFloat changeY = location.y - _startPoint.y;
CGFloat changeX = location.x - _startPoint.x;
if(fabs(changeX) > fabs(changeY)){
    _isSlideDirection = @"横向";//设置为横向
}else if(fabs(changeY)>fabs(changeX)){
    _isSlideDirection = @"纵向";//设置为纵向
}else{
    _isSlideOrClick = NO;
    NSLog(@"不在五行中。");
}

2.根据手指按下与离开屏幕后,横向位移的坐标值,对视频播放进度进行刷新。

    if (_isSlideOrClick) {
        _isSlideDirection = @"";
         _isSlideOrClick = NO;
            
        CGFloat changeY = point.y - _startPoint.y;
        CGFloat changeX = point.x - _startPoint.x;
        //如果位置改变 刷新进度条
        if(fabs(changeX) > fabs(changeY)){
            [self scrubberIsScrolling];
        }
        return;
    }
//拖动进度条
-(void)scrubberIsScrolling{
    //计算出拖动的当前秒数(总长*当前百分比)
    NSInteger dragedSeconds = floorf(_totalMovieDuration * _movieProgressSlider.value);
    CMTime newCMTime = CMTimeMake(dragedSeconds, 1);
    
    [_player seekToTime:newCMTime completionHandler:^(BOOL finished) {
        [_player play];
        [_playBtn setTitle:@"暂停" forState:UIControlStateNormal];
    }];
}

MPMediaPlayerController与AVPlayerViewController的使用介绍

MPMediaPlayerController与AVPlayerViewController,两者都是基于AVPlayer的简单UI封装,如果只是需要简单的视频播放功能,可以使用这两个类快速的构建视频播放器。

MPMediaPlayerController
1.导入头文件

#import <MediaPlayer/MediaPlayer.h>

2.初始化mp,几行代码既可以实现。

@property (nonatomic,strong)MPMoviePlayerController *mp;

NSURL *url1 = [[NSBundle mainBundle]URLForResource:@"chenyifaer" withExtension:@"mp4"];

_mp = [[MPMoviePlayerController alloc] initWithContentURL:url1];
_mp.controlStyle = MPMovieControlStyleNone;
_mp.view.frame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
[self.view addSubview:_mp.view];
[_mp play];

controlStyle属性有三个值:

  • MPMovieControlStyleNone, //无控制
  • MPMovieControlStyleEmbedded, //有全屏按钮与控制
  • MPMovieControlStyleFullscreen, // 默认全屏,有退出和控制

当然还有一些其他属性,有需要可以自行进行设置。

AVPlayerViewController
1.导入框架与头文件

#import <AVKit/AVKit.h>

2.初始化AVPlayerViewController,创建一个AVPlayer添加上。然后将其添加到视图上,再将View添加到self.View上,然后play即可

NSURL *url1 = [[NSBundle mainBundle]URLForResource:@"chenyifaer" withExtension:@"mp4"];

AVPlayer * player = [AVPlayer playerWithURL:url1];
AVPlayerViewController *playerController = [[AVPlayerViewController alloc]init];
playerController.player = player;
    
[self addChildViewController:playerController];
[self.view addSubview:playerController.view];
playerController.view.frame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
[player play];

同样几行代码,即可实现。

音量调整

1.导入头文件

#import <MediaPlayer/MediaPlayer.h>

2.借助MPVolumeView类来获取到其音量进度条,进而进行音量获取与控制

@property (nonatomic,strong)UISlider *movieProgressSlider;//进度条

MPVolumeView *volumeView = [[MPVolumeView alloc] init];
_volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        _volumeViewSlider = (UISlider *)view;
        break;
}

3.触摸屏幕时,记录手指按下的位置、获取按下时系统的音量(实现touchesBegan方法)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        _startProgress = _movieProgressSlider.value;
}

4.手指在规定行为下(手指按下位置为视图右半区,且纵向滑动)持续滑动时,动态改变系统音量(实现touchesMoved方法)

//手指持续滑动,此方法会持续调用
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint location = [[touches anyObject] locationInView:self.view];
    int index = location.y - _startPoint.y;
    if(index>0){
        [_volumeViewSlider setValue:_systemVolume - (abs(index)/10 * 0.05) animated:YES];
        [_volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
    }else{
       [_volumeViewSlider setValue:_systemVolume + (abs(index)/10 * 0.05) animated:YES];
        [_volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}
亮度调整

1.触摸屏幕时,记录手指按下的位置、按下时屏幕的亮度(实现touchesBegan方法)
2.手指在规定行为下(手指按下位置为视图左半区,且纵向滑动)持续滑动时,不断动态处理(实现touchesMoved方法)
3.改变屏幕亮度:[UIScreen mainScreen].brightness = X (0~1);

//手指持续滑动,此方法会持续调用
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint location = [[touches anyObject] locationInView:self.view];
    int index = location.y - _startPoint.y;
    if(index>0){
        [UIScreen mainScreen].brightness = _systemBrightness - abs(index)/10 * 0.01;
    }else{
        _movieProgressSlider.value = _startProgress - abs(index)/10 * 0.008;
    }
}
屏幕旋转

1.设置应用支持横屏(默认支持)。
2.在根视图中设置默认竖屏(Nav、TabBar、VC基类)

- (BOOL)shouldAutorotate{
    return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskPortrait;
}

3.在需要横屏的VC中重写下列方法即可

//允许横屏旋转
- (BOOL)shouldAutorotate{
    return YES;
}

//支持左右旋转
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscapeRight|UIInterfaceOrientationMaskLandscapeLeft;
}

//默认为右旋转
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationLandscapeRight;
}

源码

点此下载:github源码
作者其他文章推荐:iOS 使用CIDetector扫描相册二维码、原生扫描

结语

怎么样,实现一个简单的视频播放器是不是很简单。当然,要做一个好的视频播放器,需要更多的优化处理、UI处理、业务功能扩展、动画效果....
笔者Demo中关于timer与进度条的互动之间不甚完美,还有些小问题。
总之,初次尝试写,水平有限,如有错误,还望指正。

参考:
1.http://www.techotopia.com/index.php/iOS8AVPlayerAndPlayerViewController
2.http://stackoverflow.com/questions/8146942/avplayer-and-mpmovieplayercontroller-differences
3.//www.greatytc.com/p/e64fe3c7f9ab
4.http://www.th7.cn/Program/IOS/201504/439086.shtml
其他小问题多在stackoverflow查询解决。


PS:最后吐槽一下。因为写的稍微有点多,想做个快速电梯树进行页内跳转,结果发现简书竟然不支持<span id="jump">正文</span>这样使用锚点进行页内跳转的语法。简直坑爹。

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

推荐阅读更多精彩内容