iOS 教你使用MP、AVPlayer、AVPlayerVC構(gòu)建一個(gè)完整的視頻播放器

前言

標(biāo)題必須要浮夸!要感覺(jué)像是一個(gè)大新聞。長(zhǎng)者如是說(shuō)。
其實(shí)是前幾天去面試的時(shí)候,被要求說(shuō)必須做過(guò)視頻播放相關(guān)項(xiàng)目。有點(diǎn)鬧心之余,就花了點(diǎn)時(shí)間在家寫了一個(gè)簡(jiǎn)單播放器,基本實(shí)現(xiàn)了主流播放器的大致功能。之前項(xiàng)目沒(méi)有需求用到過(guò)視頻播放,所以寫的時(shí)候難免會(huì)遇到一些坑,花了一些時(shí)間解決。

蘋果在視頻播放方面提供了多個(gè)框架供我們選擇使用。分別為:

  • 基于mediaPlayer類庫(kù)的MPMediaPlayerController(iOS9后遭到廢棄,被AVPlayerViewController所替代)
  • 基于AVFounditon類庫(kù)的AVPlayer
  • 基于AVKit類庫(kù)的AVPlayerViewController(iOS8后才可使用)

正文

AVPlayer與MPMediaPlayerController比較:

  • AVplayer有更多的靈活性,當(dāng)然,也需要你去自定義構(gòu)建UI。還有一大優(yōu)勢(shì),例如其擴(kuò)展的AVQueuePlayer,可以實(shí)現(xiàn)視頻無(wú)縫隊(duì)列播放、多視頻同時(shí)播放、視頻轉(zhuǎn)換、編解碼等功能。
  • MPMediaPlayerController實(shí)際上是基于AVPlayer的簡(jiǎn)單UI封裝,對(duì)于一般的播放要求,幾行代碼就可實(shí)現(xiàn),省心省事。
    因?yàn)镸PMediaPlayerController是對(duì)AVPlayer進(jìn)行的單例封裝,所以不能進(jìn)行多視頻播放。
播放器Demo(全屏)已實(shí)現(xiàn)功能點(diǎn):
  • push到播放器頁(yè)面,橫屏顯示。
  • 單機(jī)隱藏or顯示上方標(biāo)題欄與下方操作欄。
  • 呼出右側(cè)設(shè)置欄。
  • 視頻播放操作與進(jìn)度條設(shè)置。
  • 在屏幕上左右拖動(dòng),進(jìn)行視頻快進(jìn)與快退。
  • 在屏幕左側(cè)上下拖動(dòng),進(jìn)行亮度調(diào)整。
  • 在屏幕右側(cè)上下拖動(dòng),進(jìn)行音量調(diào)整。
想到但是暫未實(shí)現(xiàn)的功能點(diǎn):(大多為優(yōu)化或與業(yè)務(wù)相關(guān))
  • 屏幕或進(jìn)度條拖動(dòng)快進(jìn)操作時(shí),添加提示框進(jìn)行快進(jìn)時(shí)間的實(shí)時(shí)提示。
  • 用戶無(wú)操作兩三秒之后自動(dòng)隱藏上下View。
  • 視頻清晰度調(diào)整按鈕。(更換視頻源)
  • 操作加鎖按鈕。(加鎖后未進(jìn)行解鎖操作之前不可進(jìn)行操作)
  • 彈幕相關(guān)。
  • 用戶允許橫屏狀態(tài)下,橫屏豎屏自動(dòng)進(jìn)行頁(yè)面切換與動(dòng)畫效果等。
  • 網(wǎng)絡(luò)視頻的緩存、下載等。
  • 軟硬解碼模式切換等。

筆者Demo選擇使用了AVPlayer進(jìn)行視頻播放器的構(gòu)建。由于UI的代碼實(shí)現(xiàn),加上略蛋疼的邏輯代碼,播放器頁(yè)面的代碼量達(dá)到400多行,之后有時(shí)間的話會(huì)再進(jìn)行優(yōu)化。這里只貼出部分代碼,想要查看或借鑒完整Demo,可以到本人github去下載。

使用AVPlayer構(gòu)建播放器

1.導(dǎo)入頭文件

#import <AVFoundation/AVFoundation.h>

2.其實(shí)沒(méi)什么可說(shuō)的,很簡(jiǎn)單,先初始化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;

//經(jīng)濟(jì)基礎(chǔ)
@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;//進(jìn)度條
@property (nonatomic,assign)CGFloat ProgressBeginToMove;
@property (nonatomic,assign)CGFloat totalMovieDuration;//視頻總時(shí)間

//核心軀干
@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;//系統(tǒng)音量值
@property (nonatomic,assign)float systemBrightness;//系統(tǒng)亮度
@property (nonatomic,assign)CGPoint startPoint;//起始位置坐標(biāo)

@property (nonatomic,assign)BOOL isTouchBeganLeft;//起始位置方向
@property (nonatomic,copy)NSString *isSlideDirection;//滑動(dòng)方向
@property (nonatomic,assign)float startProgress;//起始進(jìn)度條

#pragma mark - 播放器軀干
- (void)createAvPlayer{
    //設(shè)置靜音狀態(tài)也可播放聲音
    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);
    //獲取視頻總時(shí)長(zhǎng)
    _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];
    //需要設(shè)置自動(dòng)播放的直接play即可
    //[_player play];
}
屏幕單擊手勢(shì)與視頻快進(jìn)

屏幕單擊
1.符合條件的情況下(手指按下后離開(kāi)屏幕,并且沒(méi)有拖動(dòng))通過(guò)BOOL值判斷,隱藏或顯示上下View

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint point = [[touches anyObject] locationInView:self.view];
     if (_isShowView) {
        //上下View為顯示狀態(tài),此時(shí)點(diǎn)擊上下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.右側(cè)View顯示的狀態(tài)下,點(diǎn)擊屏幕左半空白區(qū)域,隱藏右側(cè)View

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

拖動(dòng)快進(jìn)
1.計(jì)算后得出拖動(dòng)方向?yàn)闄M向拖動(dòng)。

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 = @"橫向";//設(shè)置為橫向
}else if(fabs(changeY)>fabs(changeX)){
    _isSlideDirection = @"縱向";//設(shè)置為縱向
}else{
    _isSlideOrClick = NO;
    NSLog(@"不在五行中。");
}

2.根據(jù)手指按下與離開(kāi)屏幕后,橫向位移的坐標(biāo)值,對(duì)視頻播放進(jìn)度進(jìn)行刷新。

    if (_isSlideOrClick) {
        _isSlideDirection = @"";
         _isSlideOrClick = NO;
            
        CGFloat changeY = point.y - _startPoint.y;
        CGFloat changeX = point.x - _startPoint.x;
        //如果位置改變 刷新進(jìn)度條
        if(fabs(changeX) > fabs(changeY)){
            [self scrubberIsScrolling];
        }
        return;
    }
//拖動(dòng)進(jìn)度條
-(void)scrubberIsScrolling{
    //計(jì)算出拖動(dòng)的當(dāng)前秒數(shù)(總長(zhǎng)*當(dāng)前百分比)
    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的簡(jiǎn)單UI封裝,如果只是需要簡(jiǎn)單的視頻播放功能,可以使用這兩個(gè)類快速的構(gòu)建視頻播放器。

MPMediaPlayerController
1.導(dǎo)入頭文件

#import <MediaPlayer/MediaPlayer.h>

2.初始化mp,幾行代碼既可以實(shí)現(xiàn)。

@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屬性有三個(gè)值:

  • MPMovieControlStyleNone, //無(wú)控制
  • MPMovieControlStyleEmbedded, //有全屏按鈕與控制
  • MPMovieControlStyleFullscreen, // 默認(rèn)全屏,有退出和控制

當(dāng)然還有一些其他屬性,有需要可以自行進(jìn)行設(shè)置。

AVPlayerViewController
1.導(dǎo)入框架與頭文件

#import <AVKit/AVKit.h>

2.初始化AVPlayerViewController,創(chuàng)建一個(gè)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];

同樣幾行代碼,即可實(shí)現(xiàn)。

音量調(diào)整

1.導(dǎo)入頭文件

#import <MediaPlayer/MediaPlayer.h>

2.借助MPVolumeView類來(lái)獲取到其音量進(jìn)度條,進(jìn)而進(jìn)行音量獲取與控制

@property (nonatomic,strong)UISlider *movieProgressSlider;//進(jìn)度條

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

3.觸摸屏幕時(shí),記錄手指按下的位置、獲取按下時(shí)系統(tǒng)的音量(實(shí)現(xiàn)touchesBegan方法)

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

4.手指在規(guī)定行為下(手指按下位置為視圖右半?yún)^(qū),且縱向滑動(dòng))持續(xù)滑動(dòng)時(shí),動(dòng)態(tài)改變系統(tǒng)音量(實(shí)現(xiàn)touchesMoved方法)

//手指持續(xù)滑動(dòng),此方法會(huì)持續(xù)調(diào)用
- (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];
    }
}
亮度調(diào)整

1.觸摸屏幕時(shí),記錄手指按下的位置、按下時(shí)屏幕的亮度(實(shí)現(xiàn)touchesBegan方法)
2.手指在規(guī)定行為下(手指按下位置為視圖左半?yún)^(qū),且縱向滑動(dòng))持續(xù)滑動(dòng)時(shí),不斷動(dòng)態(tài)處理(實(shí)現(xiàn)touchesMoved方法)
3.改變屏幕亮度:[UIScreen mainScreen].brightness = X (0~1);

//手指持續(xù)滑動(dòng),此方法會(huì)持續(xù)調(diào)用
- (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;
    }
}
屏幕旋轉(zhuǎn)

1.設(shè)置應(yīng)用支持橫屏(默認(rèn)支持)。
2.在根視圖中設(shè)置默認(rèn)豎屏(Nav、TabBar、VC基類)

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

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

//允許橫屏旋轉(zhuǎn)
- (BOOL)shouldAutorotate{
    return YES;
}

//支持左右旋轉(zhuǎn)
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscapeRight|UIInterfaceOrientationMaskLandscapeLeft;
}

//默認(rèn)為右旋轉(zhuǎn)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationLandscapeRight;
}

源碼

點(diǎn)此下載:github源碼
作者其他文章推薦:iOS 使用CIDetector掃描相冊(cè)二維碼、原生掃描

結(jié)語(yǔ)

怎么樣,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的視頻播放器是不是很簡(jiǎn)單。當(dāng)然,要做一個(gè)好的視頻播放器,需要更多的優(yōu)化處理、UI處理、業(yè)務(wù)功能擴(kuò)展、動(dòng)畫效果....
筆者Demo中關(guān)于timer與進(jìn)度條的互動(dòng)之間不甚完美,還有些小問(wèn)題。
總之,初次嘗試寫,水平有限,如有錯(cuò)誤,還望指正。

參考:
1.http://www.techotopia.com/index.php/iOS8AVPlayerAndPlayerViewController
2.http://stackoverflow.com/questions/8146942/avplayer-and-mpmovieplayercontroller-differences
3.http://www.itdecent.cn/p/e64fe3c7f9ab
4.http://www.th7.cn/Program/IOS/201504/439086.shtml
其他小問(wèn)題多在stackoverflow查詢解決。


PS:最后吐槽一下。因?yàn)閷懙纳晕⒂悬c(diǎn)多,想做個(gè)快速電梯樹進(jìn)行頁(yè)內(nèi)跳轉(zhuǎn),結(jié)果發(fā)現(xiàn)簡(jiǎn)書竟然不支持<span id="jump">正文</span>這樣使用錨點(diǎn)進(jìn)行頁(yè)內(nèi)跳轉(zhuǎn)的語(yǔ)法。簡(jiǎn)直坑爹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容